Using dynamic() to load components breaks HMR - is there a better way?
Unanswered
Sun bear posted this in #help-forum
Sun bearOP
I am trying to make a decoupled frontend for a headless CMS backend using
So I did something like this, in
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:
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?
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.tsxexport 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 HMRyou can try importing the component without the
I did this to import blog pages and it worked, but they were .mdx files so I am not sure how it works with
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@Rafael Almeida you can try importing the component without the `dynamic` call, only with this:
js
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
Sun bearOP
Does not work to import a tsx module like that, causes build errors unfortunately
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 individuallyif 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 mapI 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 😮