Next.js Discord

Discord Forum

Using dynamic() to load components breaks HMR - is there a better way?

Unanswered
Sun bear posted this in #help-forum
Open in Discord
Sun bearOP
I am trying to make a decoupled frontend for a headless CMS backend using
nextjs 13
and
app router
I get data via an API call for the current route I'm at. The response contains the "contentType", I want to load a component that matches that name.

So I did something like this, in
app > [[...slug]] > page.tsx

export default async function ApplicationContentResolver({ params }: { params: { slug: string[] } }) {
  const slugs = params.slug || [];
  const path = '/' + slugs.join('/');
  const contentResolver = new ContentResolver(settings here);
  const model = await contentResolver.resolveContent(path).then((resolvedContent) => {
    return resolvedContent.content;
  }
  
  const DynamicComponent = dynamic<any>(() => import(`../content/pages/${model.contentType}`));
  return <DynamicComponent model={model} />
}


This works, except for the fact that it completely breaks HMR (or "Fast Refresh"?). Now that's a feature I don't want to break, but I suspect using dynamic() is the culprit here. If I manually map out the possible components with some conditionals like "if" or "switch" then everything will work fine, but I want to avoid that manual step:

  if (model.contentType === 'ApplePage') return <ApplePage />
  if (model.contentType === 'BananaPage') return <BananaPage />


Do you have any ideas on how to achieve this in a different way? Or if it's possible to use dynamic() without breaking HMR?

8 Replies

afaik the dynamic call should be in the root of the file so its expected this breaks HMR
you can try importing the component without the dynamic call, only with this:
const DynamicComponent = await import(`../content/pages/${model.contentType}`);

I did this to import blog pages and it worked, but they were .mdx files so I am not sure how it works with .tsx files
ah so I am not sure there's another way besides having a different dynamic call for each contentType outside the component and rendering them individually
if you have a lot of components, an alternative would be to create a simple script to generate an import map, so in your page you only need to do this:
import contentTypeMap from './content-type-map.tsx'
...
export default async function Page() {
  const contentType = await getContentType()
  return contentTypeMap[contentType]
}
and content-type-map would just be all the dynamic calls exported in a single map (everything generated by the script):
const Foo = dynamic(() => import(...))
const Bar = dynamic(() => import(...))

const map = { Foo, Bar }
export default map
I did this in a project some time ago and it worked pretty well, but if you don't have a lot of components it might be easier to just copy and paste stuff
Sun bearOP
that approach doesnt seem much different from just mapping out the components conditionally. Also I tried using dynamic() with a "static" string (no variable) and it also breaks hmr 😮