Search code examples
javascriptnext.jsnext.js13react-server-componentsapp-router

hooks in statically generated pages with NextJS app router


Short question: how do I get an action to take effect on page-load for the app router equivalent of statically generated pages from static paths in NextJS?

Longer version, my page generation is working fine:

// app/layout.js
import Providers from '@/app/Providers';

export default function RootLayout({ children }) {
    return (
        <html lang='en'>
            <body>
                <Providers>
                        {children}
                </Providers>
            </body>
        </html>
    )
}

// app/Providers.js
'use client';

import TrackProvider from '@/utils/trackProvider';

function Providers({children}) {
    return (
        <TrackProvider>
            {children}
        </TrackProvider>
    )
}

export default Providers;

// app/sample/page.js
import { getLayoutData } from '@/utils/getLayoutData';

async function getPage() {
    const allData = await getLayoutData();
    const { page } = allData;
  
    return { page };
}

export default async function Page() {
    const { page } = await getPage();

    return <div>{page}</div>
}

The issue I have is that TrackProvider provides context for React Tracking. With the old pages router, I would register a page view with a custom hook:

// pages/[slug].js
const Page = (props) => {
  usePageTracking(props.page.slug);

  return props.actionPage ? <ActionLayout {...props} /> : <Layout {...props} />;
}

where usePageTracking is a wrapper around useEffect and useTracking from React Tracking:

// utils/trackPage.js
'use client';

import { useTracking } from 'react-tracking';
import { useEffect } from 'react';

export function usePageTracking(pageSlug) {
    const { trackEvent } = useTracking();

    useEffect(() => {
        trackEvent({
            eventType: 'pageView',
            page: pageSlug
        });
    }, [pageSlug]);
}

But I can't use this same methodology in the new app router, because the page generation is async:

export default async function Page() {
    const { page } = await getPage();
    usePageTracking(page.slug); // <-- this won't work :(

    return <div>{page}</div>
}

What's the right way to accomplish this migration?


Solution

  • You probably need to wrap that functionality in a client component:

    export const PageTracking = ({ slug }) => {
      usePageTracking(slug)
    
      return null;
    } 
    

    So your page will end up looking like this:

    export default async function Page() {
      const { page } = await getPage();
    
      return (
        <>
          <PageTracking slug={ page.slug } />
    
          <div>{ page }</div>
        </>
      )
    }
    

    There's a similar example / recommendation in Next.js useRouter migration documentation:

    import { Suspense } from 'react'
    import { NavigationEvents } from './components/navigation-events'
    
    export default function Layout({ children }) {
      return (
        <html lang="en">
          <body>
            {children}
    
            <Suspense fallback={null}>
              <NavigationEvents />
            </Suspense>
          </body>
        </html>
      )
    }