docs/01-app/02-guides/lazy-loading.mdx
Lazy loading in Next.js helps improve the initial loading performance of an application by decreasing the amount of JavaScript needed to render a route.
<AppOnly>It allows you to defer loading of Client Components and imported libraries, and only include them in the client bundle when they're needed. For example, you might want to defer loading a modal until a user clicks to open it.
There are two ways you can implement lazy loading in Next.js:
next/dynamicReact.lazy() with SuspenseBy default, Server Components are automatically code split, and you can use streaming to progressively send pieces of UI from the server to the client. Lazy loading applies to Client Components.
next/dynamicnext/dynamic is a composite of React.lazy() and Suspense. It behaves the same way in the app and pages directories to allow for incremental migration.
'use client'
import { useState } from 'react'
import dynamic from 'next/dynamic'
// Client Components:
const ComponentA = dynamic(() => import('../components/A'))
const ComponentB = dynamic(() => import('../components/B'))
const ComponentC = dynamic(() => import('../components/C'), { ssr: false })
export default function ClientComponentExample() {
const [showMore, setShowMore] = useState(false)
return (
<div>
<ComponentA />
{showMore && <ComponentB />}
<button onClick={() => setShowMore(!showMore)}>Toggle</button>
<ComponentC />
</div>
)
}
Note: When a Server Component dynamically imports a Client Component, automatic code splitting is currently not supported.
When using React.lazy() and Suspense, Client Components will be prerendered (SSR) by default.
Note:
ssr: falseoption will only work for Client Components, move it into Client Components ensure the client code-splitting working properly.
If you want to disable prerendering for a Client Component, you can use the ssr option set to false:
const ComponentC = dynamic(() => import('../components/C'), { ssr: false })
If you dynamically import a Server Component, only the Client Components that are children of the Server Component will be lazy-loaded - not the Server Component itself. It will also help preload the static assets such as CSS when you're using it in Server Components.
import dynamic from 'next/dynamic'
// Server Component:
const ServerComponent = dynamic(() => import('../components/ServerComponent'))
export default function ServerComponentExample() {
return (
<div>
<ServerComponent />
</div>
)
}
Note:
ssr: falseoption is not supported in Server Components. You will see an error if you try to use it in Server Components.ssr: falseis not allowed withnext/dynamicin Server Components. Please move it into a Client Component.
External libraries can be loaded on demand using the import() function. This example uses the external library fuse.js for fuzzy search. The module is only loaded on the client after the user types in the search input.
'use client'
import { useState } from 'react'
const names = ['Tim', 'Joe', 'Bel', 'Lee']
export default function Page() {
const [results, setResults] = useState()
return (
<div>
<input
type="text"
placeholder="Search"
onChange={async (e) => {
const { value } = e.currentTarget
// Dynamically load fuse.js
const Fuse = (await import('fuse.js')).default
const fuse = new Fuse(names)
setResults(fuse.search(value))
}}
/>
<pre>Results: {JSON.stringify(results, null, 2)}</pre>
</div>
)
}
'use client'
import dynamic from 'next/dynamic'
const WithCustomLoading = dynamic(
() => import('../components/WithCustomLoading'),
{
loading: () => <p>Loading...</p>,
}
)
export default function Page() {
return (
<div>
<WithCustomLoading />
</div>
)
}
To dynamically import a named export, you can return it from the Promise returned by import() function:
'use client'
export function Hello() {
return <p>Hello!</p>
}
import dynamic from 'next/dynamic'
const ClientComponent = dynamic(() =>
import('../components/hello').then((mod) => mod.Hello)
)
Next.js supports magic comments to control how dynamic imports are handled by the bundler. These comments work with dynamic import(), require(), require.resolve(), and new Worker() expressions.
Good to know: Magic comments do not work with static
importstatements (import x from 'y'). They only work with dynamic expressions.
webpackIgnore / turbopackIgnoreUse these comments to skip bundling a dynamic import. The import expression will be left as-is in the output, useful for runtime-only modules:
// Skip bundling - import happens at runtime
const runtime = await import(/* webpackIgnore: true */ 'runtime-module')
// Turbopack-specific variant
const plugin = await import(/* turbopackIgnore: true */ pluginPath)
// Also works with require
const mod = require(/* webpackIgnore: true */ 'runtime-module')
turbopackOptional (Turbopack only)Use this comment to suppress build errors when a module might not exist. The import will still throw at runtime if the module is missing:
// No build error if './optional-feature' doesn't exist
// Runtime will throw MODULE_NOT_FOUND if executed
const feature = await import(/* turbopackOptional: true */ './optional-feature')
// Also works with require
const mod = require(/* turbopackOptional: true */ './optional-module')
This is useful for:
</AppOnly> <PagesOnly>Good to know:
webpackOptionalis not supported. UseturbopackOptionalinstead when using Turbopack.
next/dynamicnext/dynamic is a composite of React.lazy() and Suspense. It behaves the same way in the app and pages directories to allow for incremental migration.
In the example below, by using next/dynamic, the header component will not be included in the page's initial JavaScript bundle. The page will render the Suspense fallback first, followed by the Header component when the Suspense boundary is resolved.
import dynamic from 'next/dynamic'
const DynamicHeader = dynamic(() => import('../components/header'), {
loading: () => <p>Loading...</p>,
})
export default function Home() {
return <DynamicHeader />
}
Good to know: In
import('path/to/component'), the path must be explicitly written. It can't be a template string nor a variable. Furthermore theimport()has to be inside thedynamic()call for Next.js to be able to match webpack bundles / module ids to the specificdynamic()call and preload them before rendering.dynamic()can't be used inside of React rendering as it needs to be marked in the top level of the module for preloading to work, similar toReact.lazy.
To dynamically import a named export, you can return it from the Promise returned by import():
export function Hello() {
return <p>Hello!</p>
}
// pages/index.js
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() =>
import('../components/hello').then((mod) => mod.Hello)
)
To dynamically load a component on the client side, you can use the ssr option to disable server-rendering. This is useful if an external dependency or component relies on browser APIs like window.
'use client'
import dynamic from 'next/dynamic'
const DynamicHeader = dynamic(() => import('../components/header'), {
ssr: false,
})
This example uses the external library fuse.js for fuzzy search. The module is only loaded in the browser after the user types in the search input.
import { useState } from 'react'
const names = ['Tim', 'Joe', 'Bel', 'Lee']
export default function Page() {
const [results, setResults] = useState()
return (
<div>
<input
type="text"
placeholder="Search"
onChange={async (e) => {
const { value } = e.currentTarget
// Dynamically load fuse.js
const Fuse = (await import('fuse.js')).default
const fuse = new Fuse(names)
setResults(fuse.search(value))
}}
/>
<pre>Results: {JSON.stringify(results, null, 2)}</pre>
</div>
)
}