Lazy Loading & next/dynamic
next/dynamic as a thin wrapper around React.lazy and Suspense to make that easy.A worked example
components/HeavyChart.tsx
export default function HeavyChart({ data }: { data: number[] }) {
// imagine this pulls in a large charting library
return <div>{/* render chart from data */}</div>
}app/dashboard/page.tsx
import dynamic from 'next/dynamic'
const HeavyChart = dynamic(() => import('../../components/HeavyChart'), {
loading: () => <p>Loading chart...</p>,
})
export default function DashboardPage() {
return (
<div>
<h1>Dashboard</h1>
<HeavyChart data={[1, 2, 3]} />
</div>
)
}HeavyChart into the page's main JavaScript, Next.js emits it as a separate chunk that's only fetched when DashboardPage actually renders it. The loading option lets you show a placeholder while that chunk downloads.Common use cases
Situation | Why lazy load |
|---|---|
A modal, drawer, or dialog | Its code is dead weight until the user triggers the action that opens it |
A library that only works in the browser | Load it with |
A rarely visited admin or settings page | Keeps its dependencies out of the bundle for every visitor who never goes there |
Disabling server rendering for a browser-only component
import dynamic from 'next/dynamic'
const MapWithNoSSR = dynamic(() => import('../../components/Map'), {
ssr: false,
loading: () => <p>Loading map...</p>,
})ssr: false is only valid in a Client Component context — a component that relies on browser-only globals like window or document would throw during server rendering otherwise, so skipping SSR for it avoids that crash entirely.Dynamically importing a modal on demand
A modal that only loads its code when opened
'use client'
import { useState } from 'react'
import dynamic from 'next/dynamic'
const ConfirmModal = dynamic(() => import('./ConfirmModal'))
export default function DeleteButton() {
const [open, setOpen] = useState(false)
return (
<>
<button type="button" onClick={() => setOpen(true)}>
Delete
</button>
{open && <ConfirmModal onClose={() => setOpen(false)} />}
</>
)
}ConfirmModal's code never reaches the browser until open becomes true — for a page where most visitors never click "Delete," that's bundle weight most users never pay for.@next/bundle-analyzer against your production build to see which chunks are actually large, and target next/dynamic at the components responsible — splitting a component that's already tiny just adds an extra network request for no benefit.next/dynamicsplits a component into its own chunk, fetched only when it actually renders.Good candidates: modals, heavy libraries, and rarely visited routes' dependencies.
ssr: falseskips server rendering for components that depend on browser-only APIs.The
loadingoption renders a placeholder while the chunk downloads.Use bundle analysis, not intuition, to find which components are worth lazy loading.