mlynch / nextjs-tailwind-ionic-capacitor-starter

A starting point for building an iOS, Android, and Progressive Web App with Tailwind CSS, React w/ Next.js, Ionic Framework, and Capacitor
https://dev.to/ionic/build-mobile-apps-with-tailwind-css-next-js-ionic-framework-and-capacitor-3kij
MIT License
1.69k stars 341 forks source link

Is this a React SPA reliant on React Router being loaded by a Next JS server? #64

Open laurencefass opened 3 months ago

laurencefass commented 3 months ago

I found this looking for a mobile theme for my nextjs site. Please forgive my ignorance in making my assumptions but Im trying to better understand the routing in this app and to understand the level of integration with Next app routing.

It looks like its loading everything on the client and using React Router. From my initial experiments I dont think IonApp is compatible with layout files as it needs to load on the client, making ionic reliant on react-router. Is this effectively a CRA React SPA being using Next for initial page load?

The root of this project seems to demonstate the reliance on client side loading.

import dynamic from 'next/dynamic';

const App = dynamic(() => import('../components/AppShell'), {
  ssr: false,
});

export default function Page() {
  return <App />;
}

Possible I am misunderstanding but the fact that IonApp needs to be rendered on the client makes Ionic components and Next JS fundamentally incompatible unless using it only to deliver SPA using React Router.

My initial conclusion (Id love to be provied wrong) is that I think Ionic may be designed for SPA not for partial page renders.

Thanks

laurencefass commented 3 months ago

Related Ionic forum post

Add first class support to @ionic/react for Next JS SSR + Partial page pre-rendering: https://github.com/ionic-team/ionic-framework/issues/29690

nathanchapman commented 2 months ago

Hey @laurencefass! You're correct that Ionic currently relies on react-router and is not compatible with the router provided by Next.js Capacitor also requires a static bundle and thus, can only handle CSR. This means Next.js needs to run in export mode to generate the bundle for use with Capacitor. This still allows you to use a single codebase between web and native mobile, but only have SSR capabilities on web. Once the issue you linked is resolved, this will greatly simplify the routing requirements, e.g. you'd no longer need distinct (and likely duplicative) routers between web and mobile. Hope this helps!

laurencefass commented 2 months ago

Progress!

I have successfully integrated Ionic component library with Next JS app router (Im using 14.something) without having installed React-Router or IonicReactRouter using the Provider pattern ReduxToolkit uses to share context between client components.

I can navigate from the IonMenu via Next/Links and use (seemingly) any Ionic components in my Next app routed client components.

The crux of the solution was to create a client-side IonAppProvider and wrap {children} in my root layout.

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>
          <IonAppProvider>
            {children}
          </IonAppProvider>
      </body>
    </html>
  );
}

and the IonAppProvider looks something like this (note its a client component)

'use client'
...
imports etc
...
export function IonAppProvider({ children }: { children: React.ReactNode }) {
  const [isClient, setIsClient] = useState(false);

  useEffect(() => {
    setIsClient(true);
  }, []);

  return <>
    {isClient && <IonApp key={Math.random()}>
      <IonNextMenu />
      <IonPage>
        <IonHeader>
          <IonToolbar className="custom-toolbar">
            <IonTitle>NextJS and Ionic Page routing layout</IonTitle>
          </IonToolbar>
        </IonHeader>
        <IonContent id="main-content" className="ion-padding">
          <IonHeader>
            <IonToolbar>
              <IonButtons slot="start">
                <IonMenuButton />
              </IonButtons>
              <IonTitle>Menu</IonTitle>
            </IonToolbar>
          </IonHeader>
          <div className="page">
            {children}
          </div>
        </IonContent >
      </IonPage>
    </IonApp>}
  </>
}

This sets up Next app routing and pages "just load" from <Link>s

Note: The IonNextMenu is an IonMenu wrapper that registers callbacks on <Link> clicks to close the menu at the right time. The close animation just appears to work.

Now we can write pages without thought to the IonicApp.... Next pages now just become "normal" pages.

export function Page() {
  return <p>this is Next page wrapped in an IonicApp->Page layout.

Of course if one needs to override the root layout, one can just create multiple root layouts...

laurencefass commented 2 months ago

Nested layouts also work and you can use server components but any components containing Ionic stuff need to be 'use client' (for now).