Next.js переменные окружения в Docker: Почему NEXT_PUBLIC_ не работает?
Решаем распространенную проблему с переменными окружения при деплое Next.js в Docker. Разбираем разницу между build-time и runtime переменными.
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 собирает проект, он:
- Читает все переменные
NEXT_PUBLIC_* - Заменяет их прямо в коде на конкретные значения
- Создаёт статические 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 │
│ (запуск контейнера) │
└─────────────────────────────────────┘
🎯 Выводы
- NEXT_PUBLIC_ переменные встраиваются в build-time*, не runtime
- Используйте
ARG+ENVв builder stage Dockerfile - Передавайте через
build: args:в docker-compose.yml - Обязательно пересобирайте образ после изменений
- Не храните секреты в
NEXT_PUBLIC_*переменных
🔗 Полезные ссылки
Было полезно? Делитесь опытом в комментариях!
Остались вопросы? Пишите в Issues на GitHub.