Dynamic Importing of an unknown component - NextJs
Asked Answered
J

6

20

I want to load a component dynamically based on the route. I'm trying to make a single page which can load any individual component for testing purposes.

However whenever I try to do import(path) it shows the loader but never actually loads. If I hard code the exact same string that path contains then it works fine. What gives? How can I get nextjs to actually dynamically import the dynamic import?

// pages/test/[...component].js

const Test = () => {
  const router = useRouter();
  const { component } = router.query;
  const path = `../../components/${component.join('/')}`;

  console.log(path === '../../components/common/CircularLoader'); // prints true

  // This fails to load, despite path being identical to hard coded import
  const DynamicComponent = dynamic(() => import(path), {
    ssr: false,
    loading: () => <p>Loading...</p>,
  });

  // This seems to work
  const DynamicExample = dynamic(() => import('../../components/Example'), {
    ssr: false,
    loading: () => <p>Loading...</p>,
  });

  return (
    <Fragment>
      <h1>Testing {path}</h1>
      <div id="dynamic-component">
        <DynamicComponent />      <!-- this always shows "Loading..." -->
        <DynamicExample /> <!-- this loads fine. -->
      </div>
    </Fragment>
  );
};

export default Test;
Jegger answered 16/7, 2020 at 20:11 Comment(0)
P
23

I put dynamic outside of the component, and it work fine.

const getDynamicComponent = (c) => dynamic(() => import(`../components/${c}`), {
  ssr: false,
  loading: () => <p>Loading...</p>,
});

const Test = () => {
  const router = useRouter();
  const { component } = router.query;
  
  const DynamicComponent = getDynamicComponent(component);

  return <DynamicComponent />
}
Plato answered 17/7, 2020 at 1:43 Comment(8)
It turns out the issue is because webpacknlooks at the string in the import and packs files based on a path it can infer. If it's totally an expression like I have it a warning is emitted and nothing happens. If you do it like you have it there ../components/${c} then it will pack all the files under components and will work. However in my case the project is big enough where the compiler crashes with oom so basically I cannot do this expression based importing. Real surprising to me.Jegger
I am trying to get the same thing but I want the dynamic component pre-rendered. However, even with ssr:true it is only doing client-side rendering. Any ideas?Gyatt
If you set ssr:true, the component should be render in server-side only.Plato
Still getting waning on console with this solution. The only solution that works for the was the "Another solution" from @suther.Feldt
I used this in nextjs 12 with page router and it worked fine, but when i migrated to nextjs 14 with App router the deployed version is not loading the page. it is throwing a client side exception. anyone have any ideas why the error is thrown at client side,Nevillenevin
@Aflahvp I'm having the same issue reported by you, the dev version is working fine but the started one not. NextJS version is 14.2.3. Did you find a way to solve it?Kelwunn
@Kelwunn i found a work around, do you solved the error already?Nevillenevin
@Aflahvp Did not solve it, we changed the way to render the file content. In our case it was tsx component, we are now transforming it into html on the backend and render it straight away on the fe with dangerouslySetInnerHTML. What was your solution?Kelwunn
A
15

I had the same issue like the thread opener. The Documentation describe, that it's not possible to use template strings in the import() inside dynamic: enter image description here

In my case it was also impossible to add an general variable with the path there...

Solution

I've found an easy trick to solve this issue:

// getComponentPath is a method which resolve the path of the given Module-Name
const newPath = `./${getComponentPath(subComponent)}`;
const SubComponent = dynamic(() => import(''+newPath));

All the MAGIC seems to be the concatenation of an empty String with my generated Variable newPath: ''+newPath

Another Solution:

Another Solution (posted by bjn from the nextjs-Discord-Channel):

const dynamicComponents = {
  About: dynamic(() => import("./path/to/about")),
  Other: dynamic(() => import("./path/to/other")),
  ...
};

// ... in your page or whatever
const Component = dynamicComponents[subComponent];
return <Component />

This example might be useful, if you know all dynamically injectable Components. So you can list them all and use it later on in your code only if needed)

Asiaasian answered 24/12, 2020 at 19:50 Comment(3)
That second solution would have worked for me actually except that there's just a boat load of components... and it would have been annoying adding them but I think it would actually be worth it.Jegger
The best solution I've found, worked perfectly without warnings. The only point I'm asking myself is that maybe is a little too heavy load all components at once.Feldt
I guess only the Second solution works. The first one doesn't. FOlder structures are not preserved after compilation.Blare
E
3

The below code worked for me with dynamic inside the component function.

import dynamic from "next/dynamic";

export default function componentFinder(componentName, componentPath) {

    const path = componentPath; // example : "news/lists"

    const DynamicComponent = dynamic(() => import(`../components/${path}`), 
    {
       ssr: false,
       loading: () => <p>Loading Content...</p>,
    });

    return <DynamicComponent />;
}
Ernest answered 29/3, 2022 at 9:30 Comment(0)
E
0

It happens because router.query is not ready and router.query.component is undefined at the very first render of dynamic page.

This would print false at first render and true at the following one.

 console.log(path === '../../components/common/CircularLoader');

You can wrap it with useEffect to make sure query is loaded.

const router = useRouter();

useEffect(() => {
  if (router.asPath !== router.route) {
    // router.query.component is defined
  }
}, [router])

SO: useRouter receive undefined on query in first render

Github Issue: Add a ready: boolean to Router returned by useRouter

Elane answered 17/7, 2020 at 6:41 Comment(0)
D
0

I just solved it by using turbopack instead, so add this in package.json

"dev": "next dev --turbo",

and in the code my code :

async function importBlog(blogFileNames: any) {
 const modulePath = "../../app/blog/" + blogFileNames;
 let { meta, default: component } = await import(
       "" + modulePath
     )};
Dylane answered 19/6 at 12:59 Comment(1)
What does --turbo do differently?Jegger
S
-2

As it was said here before the dynamic imports need to be specifically written without template strings. So, if you know all the components you need beforehand you can dynamically import them all and use conditionals to render only those you want.

import React from 'react';
import dynamic from 'next/dynamic';

const Component1 = dynamic(() => import('./Component1').then((result) => result.default));

const Component2 = dynamic(() => import('./Component2').then((result) => result.default));

interface Props {
  slug: string;
  [prop: string]: unknown;
}

export default function DynamicComponent({ slug, ...rest }: Props) {
  switch (slug) {
    case 'component-1':
      return <Component1 {...rest} />;
    case 'component-2':
      return <Component2 {...rest} />;
    default:
      return null;
  }
}
Sac answered 7/4, 2022 at 9:36 Comment(1)
If i have 10 dynamic components, is it affect the performance?Nevillenevin

© 2022 - 2024 — McMap. All rights reserved.