Розробка високонадійного та продуктивного застосунку на Next.js вимагає глибокого розуміння його архітектури розгортання в продакшені. Сучасний Next.js App Router — це не просто бібліотека клієнтського рендерингу, а розподілений програмний комплекс, що поєднує статичний файловий веб-сервер, Node.js-середовище для потокового рендерингу (Streaming SSR), сервіс оптимізації зображень та безсерверні обробники Server Actions і Route Handlers.
Класичний підхід із запуском єдиного довгоживучого процесу Node.js (pnpm run start) на традиційному віртуальному сервері (VPS) швидко стає вузьким місцем під час різких сплесків трафіку та вимагає складної ручної конфігурації балансування навантаження.
Платформа Vercel забезпечує автоматичну декомпозицію застосунку на статичний рівень глобальної CDN, Serverless Functions та надлегковагові обчислювальні вузли Edge Runtime. Проте в Enterprise-сегменті суворі вимоги безпеки, захист персональних даних та корпоративні політики часто вимагають розгортання у власних ізольованих кластерах Kubernetes. Розуміння режиму Standalone Mode та оптимізації багатоетапних Docker-образів є критично важливим для побудови надійних CI/CD пайплайнів.
Під час виконання команди next build компілятор здійснює комплексний статичний аналіз дерева маршрутів:
cookies(), headers(), некешованих запитів) пререндеdownляться у статичний HTML і віддаються безпосередньо з кешу CDN з нульовим навантаженням на сервер.Платформа Vercel автоматично розподіляє артефакти збірки між спеціалізованими інфраструктурними рівнями:
.next/static, шрифти, зображення) публікуються в глобальній мережі Edge CDN.middleware.ts) виконується на глобальних вузлах Edge Network з мінімальною мережевою затримкою.При самостійному хостингу ви керуєте довгоживучими контейнерами. Щоб запобігти потраплянню сотень мегабайтів непотрібних dev-залежностей у фінальний образ, Next.js використовує аналізатор Node File Trace (NFT). Він створює ізольовану теку .next/standalone, що містить лише задіяні модулі та вбудований веб-сервер server.js.
Копіюються package.json та pnpm-lock.yaml. Виконується pnpm install --frozen-lockfile для гарантії незмінності оточення.
Запускається next build. Turbopack компілює чанки, а Node File Trace збирає мінімальну теку .next/standalone.
У чистий alpine-образ копіюються виключно тека standalone, скомпільована статика (.next/static) та каталог public.
Створюється непривілейований користувач nextjs:nodejs, що унеможливлює отримання root-прав на хості у разі вразливості.
Контейнер запускає node server.js напряму. Застосунок готовий приймати трафік та обробляти системні сигнали (SIGTERM).
next.config.ts#Для активації генерації мінімального самостійного продакшен-сервера встановіть параметр output: 'standalone':
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
// Активує збірку ізольованого мінімального сервера в .next/standalone
output: 'standalone',
// Опціонально: підключення зовнішнього Cache Handler для кластера Kubernetes (Redis)
// cacheHandler: process.env.NODE_ENV === 'production' ? require.resolve('./cache-handler.js') : undefined,
};
export default nextConfig;pnpm#Наведений нижче багатоетапний Dockerfile відповідає інженерним стандартам контейнеризації з використанням Node.js 22, Alpine Linux та Corepack для pnpm:
# 1. Базовий шар з активацією Corepack та pnpm
FROM node:22-alpine AS base
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
# 2. Встановлення залежностей (агресивно кешований шар)
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
# 3. Етап компіляції застосунку
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Вимкнення збору телеметрії під час збірки
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
RUN pnpm run build
# 4. Мінімальний продакшен-образ виконання (Runner)
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV PORT=3000
# Критично важливо: прив'язка до всіх мережевих інтерфейсів контейнера
ENV HOSTNAME="0.0.0.0"
ENV NEXT_TELEMETRY_DISABLED=1
# Безпека: створення непривілейованого користувача та групи
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
# Копіювання статичних ресурсів та скомпільованого standalone-сервера
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
# Запуск автономного Node.js сервера без завантаження важкого Next.js CLI
CMD ["node", "server.js"]Команда DevOps розгорнула Next.js у Kubernetes з опцією output: 'standalone'. Користувачі скаржаться, що сайт відкривається без стилів (помилки 404 для CSS), а Ingress повертає 502 Bad Gateway. У логах контейнера server.js успішно запустився. У чому причина?
В архітектурі Next.js змінні оточення строго розділені за фазами життєвого циклу:
NEXT_PUBLIC_*): Вшиваються безпосередньо в JavaScript-код клієнтських бандлів під час компіляції (next build). Їхня зміна в рантаймі контейнера неможлива без повної перезбірки.DATABASE_URL, AUTH_SECRET): Зчитуються динамічно з process.env на сервері для кожного вхідного HTTP-запиту.import 'server-only';
import { z } from 'zod';
const ServerEnvSchema = z.object({
DATABASE_URL: z.string().url(),
REDIS_URL: z.string().url().optional(),
AUTH_SECRET: z.string().min(32),
});
// Безпечна валідація змінних під час запуску контейнера
export const env = ServerEnvSchema.parse(process.env);Під час горизонтального масштабування (HPA) у кластері Kubernetes запускаються десятки реплік подів. Локальний файловий кеш .next/cache на кожному поді повністю ізольований. Виклик revalidateTag() скидає кеш лише на одному поді, викликаючи розсинхронізацію сторінок між користувачами. Рішенням є винесення кешу в Redis через кастомний cacheHandler.
const { IncrementalCache } = require('@neshca/cache-handler');
const createRedisHandler = require('@neshca/cache-handler/redis-strings').default;
const { createClient } = require('redis');
IncrementalCache.onCreation(async () => {
const client = createClient({ url: process.env.REDIS_URL });
await client.connect();
const redisHandler = await createRedisHandler({ client });
return {
cacheHandlers: [redisHandler],
};
});
module.exports = IncrementalCache;У кластері Kubernetes працюють 5 реплік подів Next.js. Редактор натискає 'Опублікувати', що викликає Server Action з revalidateTag('articles'). Проте частина користувачів продовжує бачити старі версії статей. Одночасно запити клієнтського API падають, оскільки NEXT_PUBLIC_API_URL передали через ConfigMap у deployment.yaml під час запуску контейнера. Як виправити обидві проблеми?
За замовчуванням standalone-сервер server.js слухає інтерфейс 127.0.0.1 (localhost). Усередині Docker-контейнера мережевий міст не може перенаправити зовнішній трафік, через що Ingress повертає помилку 502 Bad Gateway.
Рішення: Завжди вказуйте в Dockerfile змінну оточення: ENV HOSTNAME="0.0.0.0".
Змінні з префіксом NEXT_PUBLIC_ жорстко вшиваються в клієнтський JS під час виконання next build. Передача цих змінних у контейнер під час запуску (docker run -e NEXT_PUBLIC_API_URL=...) не вплине на клієнтську частину, яка продовжить використовувати значення часу збірки.
Рішення: Передавайте значення NEXT_PUBLIC_ через Docker ARG на етапі збірки образу або використовуйте динамічні серверні ендпоінти конфігурації (/api/config).
При запуску кількох подів за балансувальником навантаження кожен контейнер записує кеш у власну ізольовану файлову систему (.next/cache). Виклик revalidateTag() оновить дані лише на одному поді.
Рішення: Налаштуйте розподілений cacheHandler у next.config.ts, що зберігає Data Cache у спільному кластері Redis.
Режим output: 'standalone' містить виключно серверний код. Статичні файли з public/ та скомпільовані клієнтські чанки з .next/static/ навмисно не включаються в standalone-папку.
Рішення: У багатоетапному Dockerfile обов'язково вручну скопіюйте ці каталоги:
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static.
Копіювання всієї теки node_modules у Docker-образ та запуск через pnpm run start роздуває розмір контейнера до 1.5 ГБ і зберігає в продакшені пакети з потенційними вразливостями.
Рішення: Завжди використовуйте output: 'standalone' та запускайте сервер через node server.js.
Файл middleware.ts виконується в Edge Runtime. Використання модулів fs, child_process або сокетних клієнтів баз даних призведе до фатальної помилки компіляції.
Рішення: Обмежуйте логіку Middleware стандартними веб-API (Fetch, Request, Response, Headers, Web Crypto).