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

Поздравляем, пришло время выкладывать в продакшн.

Вы можете развернуть управляемый Next.js с Vercel или разместить на собственном сервере Node.js, Docker-образе или даже в виде статических HTML-файлов. При развертывании с использованием next start поддерживаются все функции Next.js.

Продакшн-сборки

Запуск next build генерирует оптимизированную версию вашего приложения для продакшна. Создаются HTML, CSS и JavaScript файлы на основе ваших страниц. JavaScript компилируется, а бандлы для браузера минифицируются с помощью Next.js Compiler для достижения лучшей производительности и поддержки всех современных браузеров.

Next.js создает стандартный выходной формат для развертывания, используемый как управляемыми, так и самостоятельными решениями. Это гарантирует поддержку всех функций в обоих методах развертывания. В следующей мажорной версии этот вывод будет преобразован в нашу спецификацию Build Output API.

Управляемый Next.js с Vercel

Vercel, создатели и разработчики Next.js, предоставляют управляемую инфраструктуру и платформу для разработчиков ваших Next.js приложений.

Развертывание на Vercel не требует настройки и обеспечивает дополнительные улучшения масштабируемости, доступности и производительности по всему миру. Однако все функции Next.js по-прежнему поддерживаются при самостоятельном размещении.

Узнайте больше о Next.js на Vercel или разверните шаблон бесплатно, чтобы попробовать.

Самостоятельное размещение

Вы можете разместить Next.js самостоятельно тремя способами:

Сервер Node.js

Next.js можно развернуть на любом хостинг-провайдере, поддерживающем Node.js. Убедитесь, что ваш package.json содержит скрипты "build" и "start":

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}

Затем выполните npm run build для сборки вашего приложения. Наконец, запустите npm run start для старта сервера Node.js. Этот сервер поддерживает все функции Next.js.

Docker-образ

Next.js можно развернуть на любом хостинг-провайдере, поддерживающем Docker контейнеры. Этот подход полезен при развертывании в оркестраторах контейнеров, таких как Kubernetes, или при запуске внутри контейнера у любого облачного провайдера.

  1. Установите Docker на вашу машину
  2. Клонируйте наш пример (или пример с несколькими окружениями)
  3. Соберите контейнер: docker build -t nextjs-docker .
  4. Запустите контейнер: docker run -p 3000:3000 nextjs-docker

Next.js через Docker поддерживает все функции Next.js.

Статический HTML-экспорт

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

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

Работа в режиме статического экспорта не поддерживает функции Next.js, требующие сервера. Узнайте больше.

Полезно знать:

Функции

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

Оптимизация изображений через next/image работает при самостоятельном размещении без дополнительной настройки при использовании next start. Если вы предпочитаете использовать отдельный сервис для оптимизации изображений, вы можете настроить загрузчик изображений.

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

Полезно знать:

Middleware

Middleware работает при самостоятельном размещении без дополнительной настройки при использовании next start. Поскольку требуется доступ к входящему запросу, она не поддерживается при статическом экспорте.

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

Если вам нужно добавить логику (или использовать внешний пакет), требующую всех API Node.js, вы можете перенести эту логику в layout как серверный компонент. Например, проверка заголовков и редирект. Вы также можете использовать заголовки, куки или параметры запроса для редиректа или перезаписи через next.config.js. Если это не работает, вы также можете использовать пользовательский сервер.

Переменные окружения

Next.js поддерживает переменные окружения как во время сборки, так и во время выполнения.

По умолчанию переменные окружения доступны только на сервере. Чтобы сделать переменную окружения доступной в браузере, она должна иметь префикс NEXT_PUBLIC_. Однако эти публичные переменные окружения будут встроены в JavaScript-бандл во время next build.

Для чтения переменных окружения во время выполнения мы рекомендуем использовать getServerSideProps или постепенно переходить на App Router. С App Router можно безопасно читать переменные окружения на сервере во время динамического рендеринга. Это позволяет использовать единый Docker-образ, который можно продвигать через несколько окружений с разными значениями.

import { unstable_noStore as noStore } from 'next/cache';

export default function Component() {
  noStore();
  // cookies(), headers() и другие динамические функции
  // также включают динамический рендеринг,
  // что делает эту переменную окружения вычисляемой во время выполнения
  const value = process.env.MY_VALUE
  ...
}

Полезно знать:

  • Вы можете выполнять код при запуске сервера с помощью функции register.
  • Мы не рекомендуем использовать опцию runtimeConfig, так как она не работает с режимом standalone output. Вместо этого мы рекомендуем постепенно переходить на App Router.

Кэширование и ISR

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

Кэширование и ревалидация страниц (с использованием Incremental Static Regeneration (ISR) или новых функций в App Router) используют общий кэш. По умолчанию этот кэш хранится в файловой системе (на диске) на вашем сервере Next.js. Это работает автоматически при самостоятельном размещении как с Pages Router, так и с App Router.

Вы можете настроить расположение кэша Next.js, если хотите сохранять кэшированные страницы и данные в долговременное хранилище или делиться кэшем между несколькими контейнерами или экземплярами вашего приложения Next.js.

Автоматическое кэширование

  • Next.js устанавливает заголовок Cache-Control в значение public, max-age=31536000, immutable для действительно неизменяемых ресурсов. Это нельзя переопределить. Эти неизменяемые файлы содержат SHA-хеш в имени файла, поэтому их можно безопасно кэшировать бессрочно. Например, Статический импорт изображений. Вы можете настроить TTL для изображений.
  • Incremental Static Regeneration (ISR) устанавливает заголовок Cache-Control в значение s-maxage: <revalidate в getStaticProps>, stale-while-revalidate. Время ревалидации определяется в вашей функции getStaticProps в секундах. Если вы установите revalidate: false, будет использоваться кэширование на один год.
  • Динамически рендерящиеся страницы устанавливают заголовок Cache-Control в значение private, no-cache, no-store, max-age=0, must-revalidate, чтобы предотвратить кэширование пользовательских данных. Это применяется как к App Router, так и к Pages Router. Это также включает Режим черновика.

Статические ресурсы

Если вы хотите размещать статические ресурсы на другом домене или CDN, вы можете использовать конфигурацию assetPrefix в next.config.js. Next.js будет использовать этот префикс при загрузке JavaScript или CSS файлов. Разделение ресурсов на другой домен имеет недостаток в виде дополнительного времени на разрешение DNS и TLS.

Узнайте больше о assetPrefix.

Настройка кэширования

По умолчанию сгенерированные кэшированные ресурсы хранятся в памяти (по умолчанию 50 МБ) и на диске. Если вы размещаете Next.js с использованием платформы оркестрации контейнеров, такой как Kubernetes, каждый pod будет иметь свою копию кэша. Чтобы предотвратить показ устаревших данных, так как кэш по умолчанию не разделяется между pod'ами, вы можете настроить обработчик кэша Next.js и отключить кэширование в памяти.

Чтобы настроить расположение кэша ISR/Data Cache при самостоятельном размещении, вы можете настроить пользовательский обработчик в файле next.config.js:

next.config.js
module.exports = {
  cacheHandler: require.resolve('./cache-handler.js'),
  cacheMaxMemorySize: 0, // отключить кэширование в памяти по умолчанию
}

Затем создайте cache-handler.js в корне вашего проекта, например:

cache-handler.js
const cache = new Map()

module.exports = class CacheHandler {
  constructor(options) {
    this.options = options
  }

  async get(key) {
    // Это может храниться где угодно, например в долговременном хранилище
    return cache.get(key)
  }

  async set(key, data, ctx) {
    // Это может храниться где угодно, например в долговременном хранилище
    cache.set(key, {
      value: data,
      lastModified: Date.now(),
      tags: ctx.tags,
    })
  }

  async revalidateTag(tag) {
    // Перебрать все записи в кэше
    for (let [key, value] of cache) {
      // Если теги значения включают указанный тег, удалить эту запись
      if (value.tags.includes(tag)) {
        cache.delete(key)
      }
    }
  }
}

Использование пользовательского обработчика кэша позволит вам обеспечить согласованность между всеми pod'ами, на которых размещено ваше приложение Next.js. Например, вы можете сохранять кэшированные значения где угодно, например в Redis или AWS S3.

Полезно знать:

  • revalidatePath - это удобный слой поверх тегов кэша. Вызов revalidatePath вызовет функцию revalidateTag с особым тегом по умолчанию для указанной страницы.

Кэш сборки

Next.js генерирует ID во время next build для идентификации версии вашего приложения. Одна и та же сборка должна использоваться и запускаться в нескольких контейнерах.

Если вы пересобираете для каждого этапа вашего окружения, вам нужно сгенерировать постоянный ID сборки для использования между контейнерами. Используйте команду generateBuildId в next.config.js:

next.config.js
module.exports = {
  generateBuildId: async () => {
    // Это может быть что угодно, например последний git-хеш
    return process.env.GIT_HASH
  },
}

Расхождение версий

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

При перезагрузке приложения может произойти потеря состояния приложения, если оно не предназначено для сохранения между переходами по страницам. Например, использование состояния URL или локального хранилища сохранит состояние после перезагрузки страницы. Однако состояние компонентов, такое как useState, будет потеряно при таких переходах.

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

Вы можете вручную настроить свойство deploymentId в файле next.config.js, чтобы гарантировать, что каждый запрос использует либо строку запроса ?dpl, либо заголовок x-deployment-id.

Ручное корректное завершение работы

При самостоятельном хостинге (self-hosting) может потребоваться выполнить код при завершении работы сервера по сигналам SIGTERM или SIGINT.

Вы можете установить переменную окружения NEXT_MANUAL_SIG_HANDLE в значение true и зарегистрировать обработчик этого сигнала в файле _document.js. Переменную окружения необходимо указать непосредственно в скрипте package.json, а не в файле .env.

Полезно знать: Ручная обработка сигналов недоступна в режиме next dev.

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "NEXT_MANUAL_SIG_HANDLE=true next start"
  }
}
pages/_document.js
if (process.env.NEXT_MANUAL_SIG_HANDLE) {
  process.on('SIGTERM', () => {
    console.log('Received SIGTERM: cleaning up')
    process.exit(0)
  })
  process.on('SIGINT', () => {
    console.log('Received SIGINT: cleaning up')
    process.exit(0)
  })
}