Как создать статический экспорт вашего Next.js приложения

Next.js позволяет начать разработку со статического сайта или одностраничного приложения (SPA), а затем при необходимости добавить серверные функции.

При выполнении next build Next.js генерирует HTML-файл для каждого маршрута. Разбивая SPA на отдельные HTML-файлы, Next.js избегает загрузки ненужного JavaScript-кода на стороне клиента, уменьшая размер бандла и ускоряя загрузку страниц.

Поскольку Next.js поддерживает статический экспорт, приложение можно развернуть на любом веб-сервере, способном обслуживать статические файлы HTML/CSS/JS.

Конфигурация

Для включения статического экспорта измените режим вывода в next.config.js:

next.config.js
/**
 * @type {import('next').NextConfig}
 */
const nextConfig = {
  output: 'export',

  // Опционально: Изменяет ссылки `/me` -> `/me/` и создаёт `/me.html` -> `/me/index.html`
  // trailingSlash: true,

  // Опционально: Отключает автоматическое преобразование `/me` -> `/me/`, сохраняя оригинальный `href`
  // skipTrailingSlashRedirect: true,

  // Опционально: Изменяет выходную директорию `out` -> `dist`
  // distDir: 'dist',
}

module.exports = nextConfig

После выполнения next build Next.js создаст папку out с HTML/CSS/JS-файлами вашего приложения.

Поддерживаемые возможности

Ядро Next.js разработано с поддержкой статического экспорта.

Серверные компоненты

При выполнении next build для статического экспорта серверные компоненты в директории app выполняются во время сборки, аналогично традиционной генерации статических сайтов.

Результат рендеринга компонентов сохраняется в виде статического HTML для начальной загрузки страницы и статического payload для клиентской навигации между маршрутами. Серверные компоненты не требуют изменений при использовании статического экспорта, за исключением случаев использования динамических серверных функций.

export default async function Page() {
  // Этот fetch выполнится на сервере во время `next build`
  const res = await fetch('https://api.example.com/...')
  const data = await res.json()

  return <main>...</main>
}

Клиентские компоненты

Для получения данных на клиенте используйте клиентский компонент с SWR для мемоизации запросов.

'use client'

import useSWR from 'swr'

const fetcher = (url: string) => fetch(url).then((r) => r.json())

export default function Page() {
  const { data, error } = useSWR(
    `https://jsonplaceholder.typicode.com/posts/1`,
    fetcher
  )
  if (error) return 'Ошибка загрузки'
  if (!data) return 'Загрузка...'

  return data.title
}
'use client'

import useSWR from 'swr'

const fetcher = (url) => fetch(url).then((r) => r.json())

export default function Page() {
  const { data, error } = useSWR(
    `https://jsonplaceholder.typicode.com/posts/1`,
    fetcher
  )
  if (error) return 'Ошибка загрузки'
  if (!data) return 'Загрузка...'

  return data.title
}

Поскольку переходы между маршрутами происходят на клиенте, это работает как традиционное SPA. Например, следующая индексная страница позволяет переходить между постами на клиенте:

import Link from 'next/link'

export default function Page() {
  return (
    <>
      <h1>Главная страница</h1>
      <hr />
      <ul>
        <li>
          <Link href="/post/1">Пост 1</Link>
        </li>
        <li>
          <Link href="/post/2">Пост 2</Link>
        </li>
      </ul>
    </>
  )
}
import Link from 'next/link'

export default function Page() {
  return (
    <>
      <h1>Главная страница</h1>
      <p>
        <Link href="/other">Другая страница</Link>
      </p>
    </>
  )
}

Оптимизация изображений

Оптимизация изображений через next/image доступна при статическом экспорте с использованием кастомного загрузчика в next.config.js. Например, можно оптимизировать изображения через Cloudinary:

next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export',
  images: {
    loader: 'custom',
    loaderFile: './my-loader.ts',
  },
}

module.exports = nextConfig

Кастомный загрузчик определяет способ получения изображений. Например, следующий загрузчик формирует URL для Cloudinary:

export default function cloudinaryLoader({
  src,
  width,
  quality,
}: {
  src: string
  width: number
  quality?: number
}) {
  const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`]
  return `https://res.cloudinary.com/demo/image/upload/${params.join(
    ','
  )}${src}`
}
export default function cloudinaryLoader({ src, width, quality }) {
  const params = ['f_auto', 'c_limit', `w_${width}`, `q_${quality || 'auto'}`]
  return `https://res.cloudinary.com/demo/image/upload/${params.join(
    ','
  )}${src}`
}

Теперь можно использовать next/image в приложении, указывая относительные пути к изображениям в Cloudinary:

import Image from 'next/image'

export default function Page() {
  return <Image alt="черепахи" src="/turtles.jpg" width={300} height={300} />
}
import Image from 'next/image'

export default function Page() {
  return <Image alt="черепахи" src="/turtles.jpg" width={300} height={300} />
}

Обработчики маршрутов

Обработчики маршрутов (Route Handlers) генерируют статический ответ при выполнении next build. Поддерживается только HTTP-метод GET. Это можно использовать для генерации статических HTML, JSON, TXT или других файлов. Например:

export async function GET() {
  return Response.json({ name: 'Lee' })
}
export async function GET() {
  return Response.json({ name: 'Lee' })
}

Файл app/data.json/route.ts будет преобразован в статический файл data.json с содержимым { name: 'Lee' } при выполнении next build.

Если нужно читать динамические значения из запроса, статический экспорт использовать нельзя.

Браузерные API

Клиентские компоненты предварительно рендерятся в HTML при next build. Поскольку Web API типа window, localStorage и navigator недоступны на сервере, обращаться к ним нужно только в браузере. Например:

'use client';

import { useEffect } from 'react';

export default function ClientComponent() {
  useEffect(() => {
    // Теперь доступен `window`
    console.log(window.innerHeight);
  }, [])

  return ...;
}

Неподдерживаемые возможности

Функции, требующие Node.js сервера или динамической логики, которая не может быть вычислена во время сборки, не поддерживаются:

Попытка использовать эти функции с next dev вызовет ошибку, аналогичную установке dynamic в error в корневом layout.

export const dynamic = 'error'

Развертывание

Со статическим экспортом Next.js можно развернуть на любом веб-сервере, поддерживающем статические файлы HTML/CSS/JS.

При выполнении next build Next.js создаёт статический экспорт в папке out. Например, для маршрутов:

  • /
  • /blog/[id]

Будут сгенерированы файлы:

  • /out/index.html
  • /out/404.html
  • /out/blog/post-1.html
  • /out/blog/post-2.html

Для статических хостов типа Nginx можно настроить реврайты запросов:

nginx.conf
server {
  listen 80;
  server_name acme.com;

  root /var/www/out;

  location / {
      try_files $uri $uri.html $uri/ =404;
  }

  # Необходимо при `trailingSlash: false`.
  # Можно опустить при `trailingSlash: true`.
  location /blog/ {
      rewrite ^/blog/(.*)$ /blog/$1.html break;
  }

  error_page 404 /404.html;
  location = /404.html {
      internal;
  }
}

История версий

ВерсияИзменения
v14.0.0next export удалён в пользу "output": "export"
v13.4.0App Router (стабильный) добавляет улучшенную поддержку статического экспорта, включая серверные компоненты и обработчики маршрутов.
v13.3.0next export объявлен устаревшим и заменён на "output": "export"