RSS feed iconGitHub logo

Next.js getServerSideProps interception

2023-01-294 minutes readJavaScript, React, Next.js

When a Next.js page has getServerSideProps defined, Next will always fetch it before rendering the page. This happens even if it was fetched already. While it is nice to have the assurance that the page will always show the most up-to-date information, this behavior does not suit highly-interactive web applications which need instant feedback on navigation. The problem is exacerbated when getServerSideProps takes a long time to return the data.

This behavior can be circumvented on a per-route basis. We need to hook into the Next router and lie to it that a given page does not have getServerSideProps. Next will assume that it does not need to make that network request and will instead render the page. It is up to the page component to render previous data in that case or show a loading indicator while loading the data it would have otherwise received from getServerSideProps.

The main idea behind this interception is implemented in the following useNextServerSidePropsInterception hook (source code, GitHub repository, CodeSandbox, demo page).

export const useNextServerSidePropsInterception = ({
  enabled,
}: {
  enabled: boolean;
}) => {
  useEffect(() => {
    // NOTE: remove existing information about the /pizza route
    // so Next does not "remember" whether it had getServerSideProps or not
    // @see https://github.com/vercel/next.js/blob/13b32ba98179aa94ac2e402f272e5c6a3356d310/packages/next/src/shared/lib/router/router.ts#L971
    delete SingletonRouter.router?.components["/pizza"];

    if (!enabled) {
      return;
    }

    const pageLoader = SingletonRouter.router?.pageLoader;
    if (!pageLoader) {
      return;
    }

    const { loadPage: originalLoadPage } = pageLoader;

    // NOTE: intercept `loadPage` calls to prevent fetching Next data when
    // navigating to some pages
    // @see https://github.com/vercel/next.js/blob/9c6d56122bfe7cc6aef066cad88ee477a60a340a/packages/next/src/client/page-loader.ts#L155-L169
    pageLoader.loadPage = async (...args) => {
      return originalLoadPage.apply(pageLoader, args).then((pageCache) => ({
        ...pageCache,
        mod: {
          ...pageCache.mod,
          // NOTE: behave as if there is no `getServerSideProps` for the
          // page so Next won't fetch the result from the server
          // @see https://github.com/vercel/next.js/blob/9c6d56122bfe7cc6aef066cad88ee477a60a340a/packages/next/src/shared/lib/router/router.ts#L2165
          __N_SSP: false,
        },
      }));
    };

    return () => {
      pageLoader.loadPage = originalLoadPage;
    };
  }, [enabled]);
};

The main idea is to disable __N_SSP for a specific page. This lets Next know that it does not need to fetch the result of getServerSideProps by having shouldFetchData equal to false.

We also need to wipe cached __N_SSP information in SingletonRouter.router.components for pages we care about. This is only relevant if you enable this interception conditionally. If it is always enabled for some pages, the delete call is unnecessary.

The code above will disable fetching getServerSideProps for all pages. When used in your app, you probably want to add some condition to the monkey-patched loadPage so it only bypasses the requests on some routes that you care about.

When the getServerSideProps interception is enabled, the /pizza page will show:

  • a loading indicator, and start loading the data in the background, if it is the first time the user is visiting the site,
  • previous results, if the page was visited by the user before.

Why not remove getServerSideProps

We could remove getServerSideProps to achieve similar in-browser data-fetching behavior. However, such a page would become a static site and would not include the data in the server-sent HTML. It would likely hurt SEO, as the data would be only available for clients with JavaScript enabled, and only after the page is loaded.

Streaming in Next app/ directory

This problem of having loading indicator during navigation could be solved by using React Streaming. It is available in the Next app directory.

The solution shown above is for pages that still use the Next pages directory and next/router.