694bd3e21d
- 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>
244 lines
7.7 KiB
Text
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>
|