.
This commit is contained in:
+306
@@ -0,0 +1,306 @@
|
||||
---
|
||||
title: How to lazy load Client Components and libraries
|
||||
nav_title: Lazy Loading
|
||||
description: Lazy load imported libraries and React Components to improve your application's loading performance.
|
||||
---
|
||||
|
||||
{/* The content of this doc is shared between the app and pages router. You can use the `<PagesOnly>Content</PagesOnly>` component to add content that is specific to the Pages Router. Any shared content should not be wrapped in a component. */}
|
||||
|
||||
[Lazy loading](https://developer.mozilla.org/docs/Web/Performance/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:
|
||||
|
||||
1. Using [Dynamic Imports](#nextdynamic) with `next/dynamic`
|
||||
2. Using [`React.lazy()`](https://react.dev/reference/react/lazy) with [Suspense](https://react.dev/reference/react/Suspense)
|
||||
|
||||
By default, Server Components are automatically [code split](https://developer.mozilla.org/docs/Glossary/Code_splitting), and you can use [streaming](/docs/app/guides/streaming) to progressively send pieces of UI from the server to the client. Lazy loading applies to Client Components.
|
||||
|
||||
## `next/dynamic`
|
||||
|
||||
`next/dynamic` is a composite of [`React.lazy()`](https://react.dev/reference/react/lazy) and [Suspense](https://react.dev/reference/react/Suspense). It behaves the same way in the `app` and `pages` directories to allow for incremental migration.
|
||||
|
||||
## Examples
|
||||
|
||||
### Importing Client Components
|
||||
|
||||
```jsx filename="app/page.js"
|
||||
'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>
|
||||
{/* Load immediately, but in a separate client bundle */}
|
||||
<ComponentA />
|
||||
|
||||
{/* Load on demand, only when/if the condition is met */}
|
||||
{showMore && <ComponentB />}
|
||||
<button onClick={() => setShowMore(!showMore)}>Toggle</button>
|
||||
|
||||
{/* Load only on the client side */}
|
||||
<ComponentC />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** When a Server Component dynamically imports a Client Component, automatic [code splitting](https://developer.mozilla.org/docs/Glossary/Code_splitting) is currently **not** supported.
|
||||
|
||||
### Skipping SSR
|
||||
|
||||
When using `React.lazy()` and Suspense, Client Components will be [prerendered](https://github.com/reactwg/server-components/discussions/4) (SSR) by default.
|
||||
|
||||
> **Note:** `ssr: false` option 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`:
|
||||
|
||||
```jsx
|
||||
const ComponentC = dynamic(() => import('../components/C'), { ssr: false })
|
||||
```
|
||||
|
||||
### Importing Server Components
|
||||
|
||||
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.
|
||||
|
||||
```jsx filename="app/page.js"
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
// Server Component:
|
||||
const ServerComponent = dynamic(() => import('../components/ServerComponent'))
|
||||
|
||||
export default function ServerComponentExample() {
|
||||
return (
|
||||
<div>
|
||||
<ServerComponent />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** `ssr: false` option is not supported in Server Components. You will see an error if you try to use it in Server Components.
|
||||
> `ssr: false` is not allowed with `next/dynamic` in Server Components. Please move it into a Client Component.
|
||||
|
||||
### Loading External Libraries
|
||||
|
||||
External libraries can be loaded on demand using the [`import()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/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.
|
||||
|
||||
```jsx filename="app/page.js"
|
||||
'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>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Adding a custom loading component
|
||||
|
||||
```jsx filename="app/page.js"
|
||||
'use client'
|
||||
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const WithCustomLoading = dynamic(
|
||||
() => import('../components/WithCustomLoading'),
|
||||
{
|
||||
loading: () => <p>Loading...</p>,
|
||||
}
|
||||
)
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<div>
|
||||
{/* The loading component will be rendered while <WithCustomLoading/> is loading */}
|
||||
<WithCustomLoading />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Importing Named Exports
|
||||
|
||||
To dynamically import a named export, you can return it from the Promise returned by [`import()`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Operators/import) function:
|
||||
|
||||
```jsx filename="components/hello.js"
|
||||
'use client'
|
||||
|
||||
export function Hello() {
|
||||
return <p>Hello!</p>
|
||||
}
|
||||
```
|
||||
|
||||
```jsx filename="app/page.js"
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const ClientComponent = dynamic(() =>
|
||||
import('../components/hello').then((mod) => mod.Hello)
|
||||
)
|
||||
```
|
||||
|
||||
## Magic Comments
|
||||
|
||||
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 `import` statements (`import x from 'y'`). They only work with dynamic expressions.
|
||||
|
||||
### `webpackIgnore` / `turbopackIgnore`
|
||||
|
||||
Use these comments to skip bundling a dynamic import. The import expression will be left as-is in the output, useful for runtime-only modules:
|
||||
|
||||
```js
|
||||
// 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:
|
||||
|
||||
```js
|
||||
// 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:
|
||||
|
||||
- Conditional features that may not be installed
|
||||
- Plugin systems where modules are optional
|
||||
- Gradual migrations where some files may not exist yet
|
||||
|
||||
> **Good to know:** `webpackOptional` is not supported. Use `turbopackOptional` instead when using Turbopack.
|
||||
|
||||
</AppOnly>
|
||||
|
||||
<PagesOnly>
|
||||
|
||||
## `next/dynamic`
|
||||
|
||||
`next/dynamic` is a composite of [`React.lazy()`](https://react.dev/reference/react/lazy) and [Suspense](https://react.dev/reference/react/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.
|
||||
|
||||
```jsx
|
||||
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 the `import()` has to be inside the `dynamic()` call for Next.js to be able to match webpack bundles / module ids to the specific `dynamic()` 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 to `React.lazy`.
|
||||
|
||||
## Examples
|
||||
|
||||
### With named exports
|
||||
|
||||
To dynamically import a named export, you can return it from the [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise) returned by [`import()`](https://github.com/tc39/proposal-dynamic-import#example):
|
||||
|
||||
```jsx filename="components/hello.js"
|
||||
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)
|
||||
)
|
||||
```
|
||||
|
||||
### With no SSR
|
||||
|
||||
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`.
|
||||
|
||||
```jsx
|
||||
'use client'
|
||||
|
||||
import dynamic from 'next/dynamic'
|
||||
|
||||
const DynamicHeader = dynamic(() => import('../components/header'), {
|
||||
ssr: false,
|
||||
})
|
||||
```
|
||||
|
||||
### With external libraries
|
||||
|
||||
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.
|
||||
|
||||
```jsx
|
||||
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>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</PagesOnly>
|
||||
Reference in New Issue
Block a user