Skip to main content
Lazy loading in Next.js helps reduce the initial JavaScript bundle by deferring the load of components and libraries until they’re needed. There are two main approaches:
  1. dynamic() from next/dynamic — the primary API for lazy loading in the App Router.
  2. React.lazy() with Suspense — works in Client Components.

dynamic()

next/dynamic is a combination of React.lazy() and Suspense. It works in both Server and Client Components.

Basic usage

import dynamic from 'next/dynamic'

const DynamicComponent = dynamic(() => import('./heavy-component'))

export default function Page() {
  return (
    <div>
      <DynamicComponent />
    </div>
  )
}

Loading UI

Show a fallback while the component loads:
import dynamic from 'next/dynamic'

const DynamicComponent = dynamic(
  () => import('./heavy-component'),
  {
    loading: () => <p>Loading...</p>,
  }
)

export default function Page() {
  return <DynamicComponent />
}

Disabling SSR

To prevent a component from rendering on the server, use ssr: false. This is useful for components that depend on browser APIs:
'use client'

import dynamic from 'next/dynamic'

const DynamicComponent = dynamic(
  () => import('./browser-only-component'),
  { ssr: false }
)

export default function Page() {
  return <DynamicComponent />
}
Using ssr: false prevents any server-rendered HTML for that component. Use only when necessary, such as for components that access window or other browser-only globals.

Named exports

To lazy load a named export from a module, return it from the Promise returned by the dynamic import:
import dynamic from 'next/dynamic'

const DynamicChart = dynamic(() =>
  import('./charts').then((mod) => mod.BarChart)
)

export default function Page() {
  return <DynamicChart />
}

Lazy loading libraries

Third-party libraries can also be loaded on demand using import() inside an event handler:
app/page.tsx
'use client'

import { useState } from 'react'

export default function Page() {
  const [results, setResults] = useState(null)

  const handleSearch = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault()
    const { default: Fuse } = await import('fuse.js')
    const fuse = new Fuse(['item1', 'item2', 'item3'])
    const result = fuse.search('item')
    setResults(result)
  }

  return (
    <form onSubmit={handleSearch}>
      <button type="submit">Search</button>
      {results && <pre>{JSON.stringify(results, null, 2)}</pre>}
    </form>
  )
}

React.lazy() with Suspense

Inside Client Components, you can use React.lazy() and Suspense directly:
app/page.tsx
'use client'

import { lazy, Suspense } from 'react'

const DynamicComponent = lazy(() => import('./heavy-component'))

export default function Page() {
  return (
    <Suspense fallback={<p>Loading...</p>}>
      <DynamicComponent />
    </Suspense>
  )
}
dynamic() from next/dynamic supports additional options like ssr: false that React.lazy() does not. Prefer dynamic() for full Next.js feature support.

dynamic() options reference

loader
function
required
A function that returns a Promise resolving to the component module. Usually an import() call.
dynamic(() => import('./my-component'))
loading
function
A component to render while the dynamic component is loading.
{ loading: () => <Skeleton /> }
ssr
boolean
default:"true"
Whether to server-side render the component. Set to false to skip SSR for browser-only components.

Build docs developers (and LLMs) love