React
Dynamic import and SSR: what happens
next/dynamic splits the bundle, not the rendering: the server still renders the component. The exception is ssr: false, with one constraint.
Key points
- next/dynamic creates a separate chunk; it does not skip server rendering.
- ssr: false only works inside a client component.
- A fallback with no reserved height causes layout shift.
- The next build table tells you whether the split cut the weight.
A dynamic import in Next.js splits the bundle, not the rendering: the component ships in a separate chunk but the server still renders it. Only ssr: false changes that, and in the App Router the option comes with a constraint.
What next/dynamic does
next/dynamic wraps React.lazy plus Suspense. The module travels in its own chunk and hydrates when needed, but the HTML of the first request already holds the markup, unless you turn server rendering off.
// app/(marketing)/page.tsx
import dynamic from "next/dynamic";
const Chart = dynamic(() => import("@/components/app/revenue-chart"), {
loading: () => ,
});
export default function Page() {
return ;
}
The ssr: false constraint
In the App Router ssr: false is not allowed in a server component: the build stops with an explicit error. Move the call into a file with "use client" and the constraint disappears.
"use client";
import dynamic from "next/dynamic";
const MapPanel = dynamic(() => import("./map-panel"), { ssr: false });
export function MapShell() {
return ;
}
When it is actually needed
- Libraries that read
windowon first import: maps, editors, canvas. - Heavy widgets below the fold, that nobody sees before the first scroll.
- Components with large dependencies and rare use, such as a code editor.
- It is not for a 2 KB component: the extra chunk costs a request.
The cost you do not see
With ssr: false the first paint shows the fallback. If the fallback is not as tall as the content, the page jumps: reserve the space with min-height or a skeleton of the same size.
| Option | Server render | First paint | Use |
|---|---|---|---|
dynamic(import) | Yes | Full content | Default |
ssr: false | No | Fallback | Only in a client component |
React.lazy | Client components only | Fallback | Outside the App Router |
direct import() | Yes | Full content | Non-React modules, load on event |
For the fallback the catalogue has skeletons; for heavy widgets there are charts and editors.
The cost of a library is in the bundle size of a UI library; load order in preload, prefetch and priority; waiting in streaming and Suspense. Comparison: Next.js vs Vite for a marketing site.
npx next build
Known limitation: ssr: false moves the component to the client, so that content is missing from the first HTML and cannot read server data. If it must be indexed, keep it on the server.
Frequently asked questions
Does a dynamic import remove server rendering?
No. The component stays in the server render: the chunk changes, not the HTML of the first response. Only ssr: false skips the server.
Why does ssr: false throw in the App Router?
Because the page is a server component. Move the call into a file marked use client and the error goes away.
Does a dynamic import reduce First Load JS?
Yes, if the component is genuinely rare. The route weight drops, but the chunk stays in the build and downloads when the component appears.
Can I use React.lazy instead of next/dynamic?
Only inside a client component. next/dynamic also handles the loading fallback and, when needed, turns server rendering off.
How do I check the split works?
Read the next build table: the First Load JS column per route must drop against the previous build.