Lazy Loading
Lazy loading defers loading of non-critical resources until they're needed. In Next.js, this means code-splitting components and loading them on demand.
Dynamic Imports
Use next/dynamic to lazy-load components:
import dynamic from 'next/dynamic'
// Component loads only when rendered
const HeavyChart = dynamic(() => import('@/components/HeavyChart'))
export default function Dashboard() {
return (
<div>
<h1>Dashboard</h1>
<HeavyChart /> {/* Loaded in separate chunk */}
</div>
)
}
With Loading State
Show a placeholder while loading:
const HeavyComponent = dynamic(
() => import('@/components/HeavyComponent'),
{
loading: () => <Skeleton className="h-64 w-full" />,
}
)
Disabling SSR
For client-only components (using browser APIs):
const MapComponent = dynamic(
() => import('@/components/Map'),
{
ssr: false, // Don't render on server
loading: () => <MapPlaceholder />,
}
)
Use ssr: false when the component:
- Uses
windowordocument - Depends on browser-only libraries
- Has no meaningful server-rendered content
Named Exports
For components that aren't default exports:
// components/Charts.tsx
export function BarChart() { ... }
export function LineChart() { ... }
// Usage
const BarChart = dynamic(
() => import('@/components/Charts').then(mod => mod.BarChart)
)
Lazy Loading Patterns
Conditional Loading
'use client'
import { useState } from 'react'
import dynamic from 'next/dynamic'
const Modal = dynamic(() => import('@/components/Modal'))
export function App() {
const [showModal, setShowModal] = useState(false)
return (
<>
<button onClick={() => setShowModal(true)}>
Open Modal
</button>
{showModal && <Modal onClose={() => setShowModal(false)} />}
</>
)
}
The Modal code only loads when the user clicks the button.
Below-the-Fold Content
import dynamic from 'next/dynamic'
const CommentsSection = dynamic(
() => import('@/components/CommentsSection'),
{
loading: () => <CommentsSkeleton />,
}
)
export default function BlogPost({ post }) {
return (
<article>
<h1>{post.title}</h1>
<div>{post.content}</div>
{/* Loaded after main content */}
<CommentsSection postId={post.id} />
</article>
)
}
Route-Based Code Splitting
Next.js automatically code-splits by route. Each page is a separate chunk:
app/
├── page.tsx → chunk for /
├── about/
│ └── page.tsx → chunk for /about
└── dashboard/
└── page.tsx → chunk for /dashboard
React.lazy vs next/dynamic
| Feature | React.lazy | next/dynamic |
|---|---|---|
| SSR support | No | Yes |
| Loading state | Suspense required | Built-in option |
| Named exports | Native | Requires .then() |
| Preloading | Manual | Automatic on hover |
Use next/dynamic in Next.js - it's optimized for the framework.
Preloading Dynamic Components
Preload on user intent:
'use client'
import dynamic from 'next/dynamic'
import { useState } from 'react'
const Modal = dynamic(() => import('@/components/Modal'))
// Get the preload function
const preloadModal = () => {
import('@/components/Modal')
}
export function App() {
const [showModal, setShowModal] = useState(false)
return (
<button
onMouseEnter={preloadModal} // Preload on hover
onClick={() => setShowModal(true)}
>
Open Modal
</button>
)
}
When to Lazy Load
| Lazy Load | Don't Lazy Load |
|---|---|
| Heavy chart libraries | Navigation components |
| Modals and dialogs | Core layout elements |
| Below-the-fold content | Above-the-fold content |
| Admin-only features | Primary page content |
| Rarely used features | Common interactions |
Summary
- Use
next/dynamicfor lazy-loading components - Add
loadingprop for placeholder UI - Use
ssr: falsefor browser-only components - Handle named exports with
.then(mod => mod.Name) - Lazy load modals, heavy libraries, and below-fold content
- Don't lazy load critical above-fold content

