0

My company have inherited a Next.js application for a client who used a different software provider for the initial build of their website, which uses Next.js + ISR to fetch data from a contentful CMS backend. Thus far, all we've done is bug fixing and some reasonably simple new functionality.

However, the latest request is off the back of the client running a PageSpeed insights test on their site, which is currently performing very badly. There were quite a few issues flagged by PageSpeed insights, but the main issue I'm currently trying to resolve is around reducing main thread work.

Before I start interrogating each and every component within the app (there are many) to try and optimize each one individually, I believe one of the main factors contributing to this issue is that they have the concept of dynamically creating pages using the CMS, where they can add as many components to these pages as they wish. So the app has a wildcard route to handle loading a page dynamically via the URL slug, and it then loops over each component returned via the CMS to render these into the page:

{/* Components */}
   {components &&
      components.map((component: any, key: number) => {
         /* Draft components will not have contentType */
         if (component.sys.contentType) {
            let contentTypeId: string = component.sys.contentType.sys.id;
            return (
               <Suspense key={key} fallback={<Loader />}>
                  <div>{findComponent(contentTypeId, component, theme)}</div>
               </Suspense>
            );
          }
       })}

The findComponent function internally simply looks up the component using the name of the component in the CMS, and renders it:

export const findComponent = (
  componentName: string,
  component: any,
  theme?: string | undefined,
  extraFields?: any
) => {
  const router = useRouter();
  if (componentName) {
    const Component = getComponent(componentName);
    if (Component) {
      return (
        <Component
          key={router.asPath}
          {...component}
          theme={theme}
          {...extraFields}
        />
      );
    } else {
      console.warn(`No component with name ${componentName} has been defined.`);
    }
  }
};

And finally getComponent simply switches over the componentName and returns one of many previously imported components from this file:

function getComponent(componentName: string) {
  switch (componentName) {
    case "carousel":
      return CarouselContainer;
    case "featuredHeroContainer":
      return FeaturedHeroContainer;
    case "callToAction":
      return CallToAction;
    ...etc...
  }
}

As I said earlier, as we inherited this app I won't profess to be an expert in how Next.js / webpack bundles code, but my understanding from this is that because every component within the app is imported into the file that contains these 2 functions, any file that then imports these functions (i.e. every dynamically created page within the site) will have every component included in their JS chunk created by Next.js/webpack? So going back to the insights test, this would be contributing massively to the main thread work, i.e. script evaluation and parsing etc?

If this understanding is correct, what I really need to work out how to do, is only include the necessary components for each page in their respective bundle. However, everything I have tried around dynamic import / lazy loading components has resulted in nothing being rendered onto the page. For example, if I try creating a map of component names with their respective import paths, and use this to dynamically import the component like so:

function getComponent(componentName: string) {
  const componentPath = componentMap.get(componentName);
  if (!componentPath) return null;
  const Component = dynamic(() => import(componentPath));
  return Component;
}

The app fails to build due to many Module not found console errors. I've also tried changing all the hard coded imports of these components into both React.lazy and next/dynamic imports (although I realise the dynamic import is built on top of React.lazy anyway...) and although this does work, I end up with a host of errors at runtime around Suspense boundaries receiving updates before the component has hydrated, which results in switching to CSR.

So the question is, should one of these solutions work? If they should, am I on the right tracks to resolving the bundle issue, and if not, are there any suggestions on how to handle this scenario of dynamically created pages not knowing which components are needed?

Unfortunately the client are very strict with budget on these things, and typically they only want to pay for around 5 days of development effort per month, which won't go far when it comes to really diving deeply into how each component works and trying to resolve the issues individually.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.