ByteGuide
← Все статьи🇷🇺 Русский

Next.js переменные окружения в Docker: Почему NEXT_PUBLIC_ не работает?

Решаем распространенную проблему с переменными окружения при деплое Next.js в Docker. Разбираем разницу между build-time и runtime переменными.

next.jsdockerdeploymenttroubleshooting
⏱️ 5 мин. чтения

Next.js переменные окружения в Docker: Почему NEXT_PUBLIC_ не работает?

🤔 Проблема

Представьте: вы добавили переменные окружения в .env, настроили docker-compose.yml, пересобрали контейнер... И всё равно в консоли видите:

⚠️ NEXT_PUBLIC_YANDEX_METRIKA_ID не указан на production

Хотя в .env переменная точно есть! Что не так?

🎯 Реальный случай

Недавно я подключал Яндекс.Метрику к своему Next.js проекту. Код был простой:

// components/analytics/YandexMetrika.tsx
export default function YandexMetrika() {
  const metrikaId = process.env.NEXT_PUBLIC_YANDEX_METRIKA_ID
  
  if (!metrikaId) {
    console.warn('⚠️ NEXT_PUBLIC_YANDEX_METRIKA_ID не указан')
    return null
  }
  
  // ... код счётчика
}

Локально всё работало отлично. Но после деплоя в Docker — пусто. Переменная undefined.

🔍 В чём проблема?

Build-time vs Runtime

Это ключевой момент, который многие упускают:

Переменные NEXT_PUBLIC_* встраиваются в JavaScript bundle во время сборки (npm run build), а НЕ в runtime!

# ❌ Неправильно (слишком поздно)
docker run -e NEXT_PUBLIC_API_URL=https://api.example.com my-app

# ✅ Правильно (во время сборки)
docker build --build-arg NEXT_PUBLIC_API_URL=https://api.example.com .

Как это работает в Next.js?

Когда Next.js собирает проект, он:

  1. Читает все переменные NEXT_PUBLIC_*
  2. Заменяет их прямо в коде на конкретные значения
  3. Создаёт статические JS файлы с этими значениями

Пример:

// Ваш код
const url = process.env.NEXT_PUBLIC_API_URL

// После сборки (упрощённо)
const url = "https://api.example.com"

Поэтому переменные должны быть доступны именно во время npm run build, а не когда контейнер запускается.

✅ Решение

Dockerfile

Нужно добавить ARG переменные в stage builder и передать их как ENV:

# ========================================
# Stage 2: Builder
# ========================================
FROM node:22-alpine AS builder
WORKDIR /app

# Копируем зависимости
COPY --from=deps /app/node_modules ./node_modules
COPY . .

# Устанавливаем dev зависимости
RUN npm ci

# ⚠️ ВАЖНО: ARG для build-time переменных
ARG NEXT_PUBLIC_YANDEX_METRIKA_ID
ARG NEXT_PUBLIC_API_URL
ARG NEXT_PUBLIC_SITE_URL

# Передаем ARG как ENV для процесса сборки Next.js
ENV NEXT_PUBLIC_YANDEX_METRIKA_ID=$NEXT_PUBLIC_YANDEX_METRIKA_ID
ENV NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL
ENV NEXT_PUBLIC_SITE_URL=$NEXT_PUBLIC_SITE_URL

# Отключаем телеметрию
ENV NEXT_TELEMETRY_DISABLED=1

# 🚀 Собираем приложение (здесь переменные встраиваются в bundle)
RUN npm run build

# ========================================
# Stage 3: Runner
# ========================================
FROM node:22-alpine AS runner
WORKDIR /app

# ... остальная настройка runner stage

docker-compose.yml

Передаём переменные через build: args:, а НЕ environment::

services:
  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile
      args:
        # ✅ Build-time переменные (встраиваются в JS bundle)
        NEXT_PUBLIC_YANDEX_METRIKA_ID: ${NEXT_PUBLIC_YANDEX_METRIKA_ID}
        NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL}
        NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL}
    environment:
      # ❌ Эти переменные доступны только в runtime
      # Для NEXT_PUBLIC_* это бесполезно!
      - NODE_ENV=production

.env файл

Создаём .env в корне проекта (рядом с docker-compose.yml):

# .env (НЕ коммитить в Git!)
NEXT_PUBLIC_YANDEX_METRIKA_ID=12345678
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_SITE_URL=https://example.com

Пересборка

После изменений нужно обязательно пересобрать образ:

# Пересборка с очисткой кэша
docker-compose build --no-cache frontend

# Перезапуск контейнера
docker-compose up -d frontend

# Проверка логов
docker-compose logs -f frontend

🎓 Важные моменты

1. Когда использовать ARG/ENV?

Тип переменной Где объявить Когда доступна Пример
NEXT_PUBLIC_* ARG + ENV в builder Build-time API URL, публичные ключи
Серверные ENV в runner Runtime Database URL, секретные ключи

2. Безопасность

// ❌ ОПАСНО! Секретный ключ попадёт в публичный JS bundle
const secretKey = process.env.NEXT_PUBLIC_SECRET_KEY

// ✅ Правильно: используем на сервере без NEXT_PUBLIC_
// В API Route или Server Component
const secretKey = process.env.SECRET_KEY

Правило: Всё с префиксом NEXT_PUBLIC_ будет видно в браузере!

3. Разные значения для разных окружений

# docker-compose.yml (production)
args:
  NEXT_PUBLIC_API_URL: https://api.production.com

# docker-compose.dev.yml (staging)
args:
  NEXT_PUBLIC_API_URL: https://api.staging.com

4. Проверка значений

Добавьте скрипт для проверки:

// lib/env.ts
export function validateEnv() {
  const required = [
    'NEXT_PUBLIC_API_URL',
    'NEXT_PUBLIC_SITE_URL',
  ]
  
  const missing = required.filter(key => !process.env[key])
  
  if (missing.length > 0) {
    throw new Error(`Missing environment variables: ${missing.join(', ')}`)
  }
}

// app/layout.tsx
import { validateEnv } from '@/lib/env'

if (process.env.NODE_ENV === 'production') {
  validateEnv()
}

🐛 Частые ошибки

❌ Ошибка 1: Передача через environment

# docker-compose.yml
environment:
  - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}  # ❌ Не работает!

Почему: environment применяется в runtime, когда bundle уже собран.

❌ Ошибка 2: Забыли пересобрать

docker-compose up -d  # ❌ Использует старый образ

Правильно:

docker-compose build --no-cache  # ✅ Пересборка
docker-compose up -d

❌ Ошибка 3: ARG только в runner stage

FROM node:22-alpine AS runner
ARG NEXT_PUBLIC_API_URL  # ❌ Слишком поздно!

Правильно: ARG должен быть в builder stage, где выполняется npm run build.

📊 Диаграмма процесса

┌─────────────┐
│   .env      │
│ файл        │
└──────┬──────┘
       │
       ▼
┌─────────────────────────────────────┐
│  docker-compose build               │
│  (передаёт ARG → ENV в builder)     │
└──────┬──────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────┐
│  npm run build                      │
│  (встраивает NEXT_PUBLIC_* в JS)    │
└──────┬──────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────┐
│  Готовый образ с bundle             │
│  (переменные уже встроены!)         │
└──────┬──────────────────────────────┘
       │
       ▼
┌─────────────────────────────────────┐
│  docker-compose up                  │
│  (запуск контейнера)                │
└─────────────────────────────────────┘

🎯 Выводы

  1. NEXT_PUBLIC_ переменные встраиваются в build-time*, не runtime
  2. Используйте ARG + ENV в builder stage Dockerfile
  3. Передавайте через build: args: в docker-compose.yml
  4. Обязательно пересобирайте образ после изменений
  5. Не храните секреты в NEXT_PUBLIC_* переменных

🔗 Полезные ссылки


Было полезно? Делитесь опытом в комментариях!

Остались вопросы? Пишите в Issues на GitHub.