Как типизировать переменные окружения в TypeScript
Вы написали process.env.API_KEY в сотый раз, и TypeScript по-прежнему определяет её тип как string | undefined. Ваша IDE не предлагает автодополнение. Хуже того, вы развернули приложение в production, только чтобы обнаружить, что отсутствующая переменная привела к краху приложения. Переменные окружения в TypeScript заслуживают более качественной обработки.
Это руководство показывает, как добавить типобезопасность к переменным окружения в современных frontend и full-stack проектах — охватывая как import.meta.env для проектов на базе Vite, так и process.env для Node.js контекстов.
Ключевые выводы
- Браузерные приложения используют инъекцию на этапе сборки (переменные встраиваются в бандлы), в то время как Node.js читает переменные окружения во время выполнения — это различие влияет как на стратегии безопасности, так и на типизацию.
- Используйте объявления
ImportMetaEnvдля проектов на Vite и расширениеNodeJS.ProcessEnvдля Node.js контекстов, чтобы получить автодополнение в IDE и проверку типов. - Только типов TypeScript недостаточно для предотвращения сбоев во время выполнения — всегда проверяйте обязательные переменные окружения при запуске, используя простые проверки или библиотеки схем, такие как Zod.
- Никогда не размещайте секреты в переменных с префиксами (
VITE_,NEXT_PUBLIC_), так как они становятся видимыми в клиентских бандлах.
Понимание переменных окружения времени сборки и времени выполнения
Прежде чем что-либо типизировать, важно понять критическое различие: браузерные приложения и серверы обрабатывают переменные окружения по-разному.
Инъекция во время сборки (браузерные приложения): Инструменты вроде Vite заменяют ссылки на переменные окружения фактическими значениями во время сборки. Переменные не существуют во время выполнения — они встроены в ваш JavaScript-бандл.
Переменные окружения времени выполнения (серверная сторона): Node.js читает process.env из реального системного окружения при выполнении вашего кода. Значения могут изменяться между развертываниями без пересборки.
Это различие важно для безопасности. Любая переменная, внедренная во время сборки, становится видимой в вашем клиентском коде. Поэтому фреймворки используют правила экспозиции на основе префиксов — Vite раскрывает только переменные, начинающиеся с VITE_, а Next.js раскрывает клиентские переменные с префиксом NEXT_PUBLIC_. Приватные ключи без этих префиксов остаются только на серверной стороне.
Типизация import.meta.env в проектах Vite
Vite использует import.meta.env вместо process.env. Чтобы добавить типобезопасные переменные окружения в TypeScript, создайте файл объявлений:
// env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL: string
readonly VITE_APP_TITLE: string
// добавьте больше переменных здесь
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
Теперь TypeScript предоставляет автодополнение и обрабатывает эти переменные как string, а не string | undefined. Разместите этот файл в директории src и убедитесь, что ваш tsconfig.json включает его.
Типизация ProcessEnv для Node.js контекстов
Для серверного кода или инструментов, использующих process.env, расширьте интерфейс NodeJS.ProcessEnv:
// globals.d.ts
declare namespace NodeJS {
interface ProcessEnv {
DATABASE_URL: string
API_SECRET: string
NODE_ENV: 'development' | 'production' | 'test'
}
}
Этот подход дает вам автодополнение во всей кодовой базе. Пример с NODE_ENV показывает, что вы можете использовать union-типы для переменных с известными возможными значениями.
Discover how at OpenReplay.com.
Почему одних типов недостаточно
Вот в чём загвоздка: объединение объявлений сообщает TypeScript, что должно существовать, а не что действительно существует. Вы типизировали DATABASE_URL как string, но если кто-то забудет её установить, ваше приложение упадёт во время выполнения.
Типы TypeScript стираются во время компиляции. Они не могут проверить, что переменные окружения действительно существуют, когда выполняется ваш код.
Валидация переменных окружения при запуске
Для валидации переменных окружения в TypeScript проверяйте их рано — до того, как ваше приложение сделает что-то важное. Простой подход:
// config.ts
function getEnvVar(key: string): string {
const value = process.env[key]
if (!value) {
throw new Error(`Missing required environment variable: ${key}`)
}
return value
}
export const config = {
databaseUrl: getEnvVar('DATABASE_URL'),
apiSecret: getEnvVar('API_SECRET'),
} as const
Импортируйте этот модуль конфигурации в точке входа вашего приложения. Если какая-либо переменная отсутствует, вы узнаете об этом немедленно, а не обнаружите это в середине обработки запроса.
Для более надёжной валидации библиотеки вроде Zod позволяют определять схемы, которые проверяют типы, форматы и ограничения:
import { z } from 'zod'
const envSchema = z.object({
DATABASE_URL: z.string().url(),
PORT: z.string().transform(Number).pipe(z.number().min(1)),
})
export const env = envSchema.parse(process.env)
Это приведёт к быстрому отказу с понятными сообщениями об ошибках, если DATABASE_URL не является валидным URL или PORT не является числом.
Обеспечение безопасности ваших переменных
Помните о правилах префиксов. В проектах Vite только переменные с префиксом VITE_ попадают в браузер. Всё остальное доступно только процессу сборки (серверная сторона) и не отправляется в браузер. Никогда не размещайте секреты в переменных с префиксами — они будут видны в вашем production-бандле.
Для серверных секретов полагайтесь на конфигурацию окружения вашей хостинг-платформы, а не на .env файлы в production. Немедленно добавьте .env в ваш .gitignore.
Заключение
Типобезопасные переменные окружения в TypeScript требуют двух уровней: объединения объявлений для автодополнения на этапе компиляции и валидации во время выполнения для безопасности в production. Используйте ImportMetaEnv для проектов Vite, NodeJS.ProcessEnv для Node.js контекстов и всегда проверяйте обязательные переменные при запуске. Ваше будущее «я» — отлаживающее инцидент в production в 3 часа ночи — скажет вам спасибо.
Часто задаваемые вопросы
Да, но держите объявления раздельными. Создайте файл env.d.ts с ImportMetaEnv для ваших frontend-пакетов на Vite и globals.d.ts с расширением NodeJS.ProcessEnv для backend-пакетов. tsconfig.json каждого пакета должен включать только соответствующий файл объявлений, чтобы избежать конфликтов типов.
Ваш tsconfig.json, вероятно, не включает файл объявлений. Проверьте, что ваш массив include охватывает расположение файла, или добавьте путь к файлу явно. Также убедитесь, что вы случайно не затеняете интерфейс в другом файле объявлений. Перезапустите сервер TypeScript после внесения изменений.
Для frontend-приложений валидация во время сборки более полезна, так как переменные встраиваются в процессе сборки. Добавьте prebuild-скрипт, который проверяет наличие необходимых VITE_-переменных до запуска Vite. Валидация во время выполнения в браузере всё равно не может восстановиться после отсутствующих переменных.
Отметьте опциональные переменные знаком вопроса в объявлении интерфейса, например OPTIONAL_VAR?: string. Для валидации используйте метод optional() в Zod или предоставьте значения по умолчанию с помощью default(). Ваш объект конфигурации должен отражать, какие переменные действительно обязательны, а какие желательны.
Complete picture for complete understanding
Capture every clue your frontend is leaving so you can instantly get to the root cause of any issue with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data.
Check our GitHub repo and join the thousands of developers in our community.