Ленивая загрузка (Lazy Loading)

Ленивая загрузка в Next.js помогает улучшить начальную производительность загрузки приложения за счёт уменьшения количества JavaScript, необходимого для рендеринга маршрута.

Она позволяет отложить загрузку Клиентских компонентов (Client Components) и импортируемых библиотек, включая их в клиентский бандл только тогда, когда они действительно нужны. Например, вы можете отложить загрузку модального окна до момента, когда пользователь кликнет для его открытия.

В Next.js есть два способа реализации ленивой загрузки:

  1. Использование Динамических импортов с next/dynamic
  2. Использование React.lazy() с Suspense

По умолчанию Серверные компоненты (Server Components) автоматически подвергаются разделению кода (code splitting), и вы можете использовать потоковую передачу (streaming) для постепенной отправки частей интерфейса с сервера на клиент. Ленивая загрузка применяется к Клиентским компонентам.

next/dynamic

next/dynamic представляет собой комбинацию React.lazy() и Suspense. Он работает одинаково в директориях app и pages, что позволяет постепенно мигрировать.

Примеры

При использовании next/dynamic компонент заголовка не будет включён в начальный JavaScript-бандл страницы. Сначала страница отобразит fallback из Suspense, а затем компонент Header, когда граница Suspense будет разрешена.

import dynamic from 'next/dynamic'

const DynamicHeader = dynamic(() => import('../components/header'), {
  loading: () => <p>Загрузка...</p>,
})

export default function Home() {
  return <DynamicHeader />
}

Важно знать: В import('path/to/component') путь должен быть явно указан. Нельзя использовать шаблонные строки или переменные. Кроме того, import() должен находиться внутри вызова dynamic(), чтобы Next.js мог сопоставить вебпак-бандлы/идентификаторы модулей с конкретным вызовом dynamic() и предзагрузить их перед рендерингом. dynamic() нельзя использовать внутри React-рендеринга, так как он должен быть помечен на верхнем уровне модуля для работы предзагрузки, аналогично React.lazy.

С именованными экспортами

Для динамического импорта именованного экспорта вы можете вернуть его из Promise, возвращаемого import():

components/hello.js
export function Hello() {
  return <p>Привет!</p>
}

// pages/index.js
import dynamic from 'next/dynamic'

const DynamicComponent = dynamic(() =>
  import('../components/hello').then((mod) => mod.Hello)
)

Без SSR

Для динамической загрузки компонента на стороне клиента можно использовать опцию ssr для отключения серверного рендеринга. Это полезно, если внешняя зависимость или компонент полагается на браузерные API, такие как window.

import dynamic from 'next/dynamic'

const DynamicHeader = dynamic(() => import('../components/header'), {
  ssr: false,
})

С внешними библиотеками

В этом примере используется внешняя библиотека fuse.js для нечёткого поиска. Модуль загружается в браузере только после ввода пользователем поискового запроса.

import { useState } from 'react'

const names = ['Tim', 'Joe', 'Bel', 'Lee']

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

  return (
    <div>
      <input
        type="text"
        placeholder="Поиск"
        onChange={async (e) => {
          const { value } = e.currentTarget
          // Динамическая загрузка fuse.js
          const Fuse = (await import('fuse.js')).default
          const fuse = new Fuse(names)

          setResults(fuse.search(value))
        }}
      />
      <pre>Результаты: {JSON.stringify(results, null, 2)}</pre>
    </div>
  )
}