rsnext/docs/02-app/01-building-your-application/06-optimizing/07-lazy-loading.mdx
Stephanie Dietz 694bd3e21d
docs: Add "Going to production" page for App Router (#59304)
- Add **Going to Prod** docs for `app`
- Add bundle-analyzer page
- Merge `app` and `pages` docs

---------

Co-authored-by: Delba de Oliveira <32464864+delbaoliveira@users.noreply.github.com>
Co-authored-by: Tim Neutkens <tim@timneutkens.nl>
Co-authored-by: Michael Novotny <manovotny@gmail.com>
Co-authored-by: Delba de Oliveira <delbabrown@gmail.com>
Co-authored-by: Rich Haines <hello@richardhaines.dev>
Co-authored-by: Lee Robinson <me@leerob.io>
2024-01-05 07:46:00 -06:00

244 lines
7.7 KiB
Text

---
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.
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/building-your-application/routing/loading-ui-and-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
<AppOnly>
### 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>
)
}
```
### Skipping SSR
When using `React.lazy()` and Suspense, Client Components will be pre-rendered (SSR) by default.
If you want to disable pre-rendering 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.
```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>
)
}
```
### 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"
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)
)
```
</AppOnly>
<PagesOnly>
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`.
## 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
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>