Environment
Любая переменная с префиксом NEXT_PUBLIC_, VITE_ и аналогами попадает в клиентский бандл и доступна в браузере любому пользователю.
Правила
MUST
- Использовать серверные переменные (без публичного префикса) для секретов
- Проверять финальный бандл на наличие случайно утекших секретов
- Валидировать env-переменные при старте приложения
FORBIDDEN
Хранить в клиентских переменных (NEXT_PUBLIC_*, VITE_*):
- Секретные ключи API и токены
- Private API keys
- Ключи подписи (JWT secrets)
- Database credentials
- Encryption keys
MAY
Хранить в клиентских переменных:
- Публичные идентификаторы (Sentry DSN, Google Analytics ID)
- Публичные URL API
- Флаги функциональности
- Публичные ключи (Stripe Publishable Key)
Примеры
Запрещённые практики
# ❌ FORBIDDEN: Секретные ключи в клиентских переменных
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_live_xxx # Опасно!
NEXT_PUBLIC_JWT_SECRET=xxx # Критически опасно!
NEXT_PUBLIC_DATABASE_PASSWORD=xxx # Критически опасно!
NEXT_PUBLIC_OPENAI_API_KEY=sk-xxx # Опасно!
// ❌ FORBIDDEN: Передача серверной переменной в клиентский компонент
// app/page.tsx (Server Component)
export default function Page() {
const secret = process.env.JWT_SECRET; // На сервере — OK
return <ClientComponent secret={secret} />; // Утечёт в HTML!
}
Правильный подход
# ✅ Клиентские (публичные) — попадают в бандл
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_xxx
NEXT_PUBLIC_SENTRY_DSN=https://[email protected]/xxx
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
# ✅ Серверные (секретные) — только на сервере
STRIPE_SECRET_KEY=sk_live_xxx
DATABASE_URL=postgresql://xxx
JWT_SECRET=xxx
OPENAI_API_KEY=sk-xxx
// ✅ Секретные ключи только на сервере
// app/api/payment/route.ts
import Stripe from 'stripe';
export async function POST(request: Request) {
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: '2023-10-16'
});
// ...
}
// ✅ Правильно: использовать только результат на клиенте
export default function Page() {
const data = await validateToken(process.env.JWT_SECRET); // Только сервер
return <ClientComponent data={data} />; // Передаём только результат
}
Валидация переменных при старте
// ✅ MUST: Валидация env-переменных
// lib/env.ts
import { z } from 'zod';
const envSchema = z.object({
// Клиентские переменные
NEXT_PUBLIC_API_URL: z.string().url(),
NEXT_PUBLIC_SENTRY_DSN: z.string().url().optional(),
NEXT_PUBLIC_GA_ID: z.string().startsWith('G-').optional(),
// Серверные переменные
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32),
STRIPE_SECRET_KEY: z.string().startsWith('sk_'),
});
export const env = envSchema.parse(process.env);
Проверка бандла на утечку секретов
# ✅ SHOULD: Проверка после сборки
npm run build
grep -r "sk_live" .next/static/ # Stripe secret keys
grep -r "sk-" .next/static/ # OpenAI keys
grep -r "postgresql://" .next/static/ # Database URLs
# Если что-то найдено — это ошибка!
Автоматизация
MUST: Pre-commit hook — запрет коммита .env файлов
# lefthook.yml
pre-commit:
commands:
no-env-files:
run: |
if git diff --cached --name-only | grep -E '\.env($|\.)'; then
echo "❌ Попытка закоммитить .env файл! Удалите его из индекса."
exit 1
fi
Пример конфигурации: lefthook.yml
# ✅ .env файлы всегда в .gitignore
echo ".env*" >> .gitignore
echo "!.env.example" >> .gitignore
SHOULD: .env.example как шаблон
# .env.example (коммитится — шаблон для разработчиков)
NEXT_PUBLIC_API_URL=https://api.example.com
NEXT_PUBLIC_SENTRY_DSN=
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
STRIPE_SECRET_KEY=sk_test_your_key_here
JWT_SECRET=your_jwt_secret_here_minimum_32_chars
Таблица классификации
| Переменная | Тип | Префикс | Где использовать |
|---|---|---|---|
| Stripe Publishable Key | Публичный | NEXT_PUBLIC_ | Клиент |
| Stripe Secret Key | Секрет | — | Только сервер |
| API URL | Публичный | NEXT_PUBLIC_ | Клиент и сервер |
| Database URL | Секрет | — | Только сервер |
| JWT Secret | Секрет | — | Только сервер |
| Sentry DSN | Публичный | NEXT_PUBLIC_ | Клиент |
| Google Analytics ID | Публичный | NEXT_PUBLIC_ | Клиент |
| OpenAI API Key | Секрет | — | Только сервер |
| Feature Flags | Публичный | NEXT_PUBLIC_ | Клиент |