Ленивая загрузка клиентских компонентов и библиотек

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

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

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

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

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

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 мог сопоставить бандлы webpack / идентификаторы модулей с конкретным вызовом 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.

'use client'

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>
  )
}