Next.jsJuniorTechnical

Как управлять переменными окружения в Next.js? В чём разница между серверными и клиентскими переменными?

Переменные без префикса NEXT_PUBLIC_ доступны только на сервере; с префиксом — встраиваются в клиентский бандл при сборке. Секреты никогда не добавляют NEXT_PUBLIC_.

Переменные окружения в Next.js

Next.js читает переменные из файлов .env.local, .env.development, .env.production и .env. Приоритет: .env.local > .env.development/.env.production > .env.

Серверные переменные

Любая переменная без префикса NEXT_PUBLIC_ доступна только на сервере — в Server Components, Route Handlers, Server Actions, getServerSideProps и getStaticProps. В браузер она не попадает: Next.js просто не включает её в клиентский бандл.

# .env.local
DATABASE_URL=postgresql://user:pass@localhost:5432/mydb
SECRET_API_KEY=supersecret123
NEXT_PUBLIC_APP_URL=https://example.com
// app/api/users/route.ts — серверный код, DATABASE_URL доступна
import { NextResponse } from 'next/server';

export async function GET() {
  const dbUrl = process.env.DATABASE_URL; // OK
  const appUrl = process.env.NEXT_PUBLIC_APP_URL; // тоже доступна на сервере
  return NextResponse.json({ appUrl });
}

Клиентские переменные (NEXT_PUBLIC_)

Переменные с префиксом NEXT_PUBLIC_ встраиваются в JS-бандл на этапе сборки через статическую подстановку (inlining). Обращение process.env.NEXT_PUBLIC_APP_URL превращается буквально в строку "https://example.com" при компиляции.

// components/Footer.tsx — Client Component
'use client';

export function Footer() {
  // process.env.NEXT_PUBLIC_APP_URL встроена в бандл
  return <footer>Сайт: {process.env.NEXT_PUBLIC_APP_URL}</footer>;
}

Переопределение на этапе выполнения (runtime env)

Если нужна переменная, известная только в runtime (например, при деплое в Kubernetes), используйте next.config.ts с serverRuntimeConfig / publicRuntimeConfig или передавайте значения через серверный компонент в клиентский через пропсы.

// next.config.ts
import type { NextConfig } from 'next';

const config: NextConfig = {
  serverRuntimeConfig: {
    mySecret: process.env.MY_SECRET,
  },
  publicRuntimeConfig: {
    apiUrl: process.env.NEXT_PUBLIC_API_URL,
  },
};

export default config;
// lib/config.ts
import getConfig from 'next/config';

const { serverRuntimeConfig, publicRuntimeConfig } = getConfig();

export const apiUrl = publicRuntimeConfig.apiUrl as string;
export const secret = serverRuntimeConfig.mySecret as string; // только сервер

Типизация переменных окружения

Создайте env.d.ts в корне проекта, чтобы TypeScript знал о ваших переменных:

// env.d.ts
declare namespace NodeJS {
  interface ProcessEnv {
    DATABASE_URL: string;
    SECRET_API_KEY: string;
    NEXT_PUBLIC_APP_URL: string;
  }
}

Подводные камни

  • Секрет без префикса попал в Client Component — Next.js выбросит ошибку на этапе сборки или просто вернёт undefined, но секрет не утечёт. Тем не менее логика сломается молча.
  • Динамическое обращение к process.env — конструкция process.env[key] с переменной key не работает для NEXT_PUBLIC_: подстановка статическая, парсер ищет именно литерал process.env.NEXT_PUBLIC_XXX.
  • Изменение .env.local требует рестарта — hot reload не подхватывает новые значения без перезапуска next dev.
  • .env.local не должен коммититься — добавьте его в .gitignore. Добавляйте только .env.example с заглушками.
  • Порядок файлов.env.local имеет наивысший приоритет и перекрывает все остальные файлы, что может сбивать с толку в CI/CD где .env.local отсутствует.
  • Пустая строка vs отсутствие переменнойDATABASE_URL= и отсутствие строки — разные вещи: в первом случае process.env.DATABASE_URL === "", во втором — undefined.
  • Не используйте publicRuntimeConfig с App Router — он работает только с Pages Router; в App Router передавайте публичные значения через серверный компонент или переменные NEXT_PUBLIC_.

Common mistakes

  • Класть секрет с префиксом NEXT_PUBLIC_
  • Полагать, что process.env.X в клиентском файле — runtime
  • Коммитить .env.local или .env.production
  • Менять секрет в проде без перезапуска процесса

What the interviewer is testing

  • Понимает, что только NEXT_PUBLIC_* идут в бандл
  • Знает порядок загрузки env-файлов
  • Использует server-only для критичных модулей
  • Различает build-time inline и runtime read

Sources

Related topics