Next.js allows you to lazy load client components, deferring their loading time and only include them in the client bundle when needed. This can be achieved by importing the component and libraries using the dynamic()
method instead of the standard import
statement.
For example, here is how you can dynamically import a client component:
1src
2├── app
3│ ├── error.jsx
4│ ├── favicon.ico
5│ ├── globals.css
6│ ├── layout.jsx
7│ └── page.jsx <===== Webpage
8└── components
9 └── clientComponent.jsx <===== Client component
app/page.js
1import dynamic from "next/dynamic";
2
3const ClientComponent = dynamic(() => import("@/components/clientComponent"));
4
5export default async function Page() {
6 return (
7 <>
8 <p>Hello, World!</p>
9 <ClientComponent />
10 </>
11 );
12}
Try this example on your own computer, and you'll notice that the client component and the rest of the webpage are rendered at the same time. Seems like the client component is not being lazy loaded at all.
This is because of server-side rendering (SSR). As we've discussed before, in Next.js, server components are rendered on the server, and client components are rendered on the client. But in reality, that is not the full story.
By default, in order to provide better SEO and user experience, Next.js will SSR the client component as well. A HTML document will be rendered on the server and when the user requests the page, this HTML document will be sent to the user and Next.js will then inject the corresponding JavaScript code to make it dynamic.
In reality, you can think of it as Next.js server and Next.js client, instead of the actual server and client machines.
For Next.js server components, everything happens on the server, while for the Next.js client components, the rendering happens on the server by default, and Next.js will make the component dynamic on the client by injecting JavaScript code.
Disable server side rendering (SSR)
This default behavior can be disabled by setting the ssr
option to false
like this:
app/page.js
1"use client";
2
3import dynamic from "next/dynamic";
4import ServerComponent from "@/components/serverComponent";
5
6const ClientComponent = dynamic(() => import("@/components/clientComponent"), {
7 ssr: false,
8});
9
10export default function Page() {
11 return (
12 <div className="m-4">
13 <p>Hello, World!</p>
14 <ClientComponent />
15 </div>
16 );
17}
And this time, notice that the Hello, World!
is loaded first, and the client component only appears afterward.
There is one thing we need to pay attention to here. In order to disable SSR, the page itself must be a client component, notice that the page is marked with "use client"
. ssr: false
is not yet supported in a server component.
Custom loading UI
You can also define a temporary loading UI that will be displayed until the client component is loaded.
app/page.js
1"use client";
2
3import dynamic from "next/dynamic";
4
5const ClientComponent = dynamic(() => import("@/components/clientComponent"), {
6 ssr: false,
7 loading: () => <p>Loading...</p>,
8});
9
10export default function Page() {
11 return (
12 <div className="m-4">
13 <p>Hello, World!</p>
14 <ClientComponent />
15 </div>
16 );
17}
Dynamically importing server components
When you dynamically import a server component, the component itself will not be lazy loaded, but the client components that are descendants of this server component will be lazy loaded.
1import dynamic from "next/dynamic";
2
3const ClientComponent = dynamic(() => import("@/components/clientComponent"));
4
5const ServerComponent = dynamic(() => import("@/components/serverComponent"));
6
7export default async function Page() {
8 return (
9 <div className="m-4">
10 <ClientComponent />
11 <ServerComponent /> {/* <===== Client components that are descendants of this server component will be lazy loaded. */}
12 </div>
13 );
14}