프론트엔드 환경 변수는 "빌드 시점에 코드에 주입되는 값"이라는 점을 이해하는 것이 핵심입니다. 클라이언트 번들에 포함되는 변수는 결국 사용자에게 공개되므로, 절대 비밀 값을 넣지 않습니다. 아래에서는 React 애플리케이션에서 널리 쓰이는 도구별 환경 변수 관리 방법과 실무 팁을 정리합니다.
1. 빌드 타임 vs 런타임 개념 이해
대부분의 React 빌드 도구는 소스 코드에 있는 참조를 빌드 타임에 문자열로 대체합니다. 예를 들어 Vite의 import.meta.env, CRA의 process.env.REACT_APP_*, Next.js의 process.env.NEXT_PUBLIC_* 등이 빌드 시점에 번들에 인라인됩니다. 따라서 .env를 바꿨다면 개발 서버 재시작이나 다시 빌드가 필요합니다. 런타임에 값을 바꾸고 싶다면 별도 주입 패턴(window.__ENV__)을 써야 합니다.
2. CRA(Create React App)에서의 사용법
CRA는 클라이언트에서 접근 가능한 환경 변수에 REACT_APP_ 접두사를 요구합니다. .env 파일은 프로젝트 루트에 두며, .env.development, .env.production 등의 파일로 환경별 값을 관리합니다.
// .env
REACT_APP_API_URL=https://api.example.com
REACT_APP_FEATURE_X=on
// src/config.ts
export const API_URL = process.env.REACT_APP_API_URL ?? 'http://localhost:4000';
export const FEATURE_X_ENABLED = (process.env.REACT_APP_FEATURE_X === 'on');
주의: REACT_APP_ 접두사 없이 선언하면 브라우저 코드에서 접근되지 않습니다. .env 수정 후 개발 서버를 재시작합니다.
3. Vite에서의 사용법(import.meta.env)
Vite는 클라이언트에서 접근 가능한 변수에 VITE_ 접두사를 요구합니다. import.meta.env로 접근하며 문자열로 주입되므로 필요한 경우 파싱합니다.
// .env.development
VITE_API_URL=http://localhost:4000
VITE_ENABLE_BETA=true
// src/config.ts
const apiUrl = import.meta.env.VITE_API_URL || 'http://localhost:4000';
const enableBeta = import.meta.env.VITE_ENABLE_BETA === 'true';
export { apiUrl, enableBeta };
// src/env.d.ts (TypeScript 안전성 확보)
interface ImportMetaEnv {
readonly VITE_API_URL: string;
readonly VITE_ENABLE_BETA?: string; // 'true' | 'false'
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
변경 반영을 위해 개발 서버를 재시작합니다. import.meta.env을 콘솔에 출력하면 노출되므로 로그에 민감한 값은 남기지 않습니다.
4. Next.js에서의 사용법(NEXT_PUBLIC_)
Next.js는 서버와 클라이언트를 구분합니다. 클라이언트에서 참조할 변수는 NEXT_PUBLIC_ 접두사가 필요합니다. 서버 전용 변수는 접두사 없이 process.env로 접근하며 브라우저에 노출되지 않습니다.
// .env.local (커밋 금지)
NEXT_PUBLIC_API_URL=https://api.example.com
SECRET_TOKEN=super-secret
// 클라이언트 컴포넌트
export default function Page() {
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
if (!apiUrl) throw new Error('NEXT_PUBLIC_API_URL 누락');
return <div>API: {apiUrl}</div>;
}
// 서버 코드 (예: app/api/route.ts)
export async function GET() {
const token = process.env.SECRET_TOKEN; // 클라이언트에 노출되지 않음
// 서버에서 외부 API 호출 등
}
Next.js는 서버에서의 process.env 접근이 런타임에 가능하지만, 클라이언트로 보내는 값은 빌드에 인라인되므로 NEXT_PUBLIC_만 노출됩니다.
5. 환경별 파일과 우선순위
일반적으로 .env → .env.local → .env.[mode] → .env.[mode].local 순으로 덮어씁니다(mode는 development/production/test). 팀에는 .env.example을 커밋하고 실제 값은 .env.local로 개발자별 관리합니다.
// .env.example (템플릿)
REACT_APP_API_URL=
VITE_API_URL=
NEXT_PUBLIC_API_URL=
6. CI/CD에서 변수 주입
빌드 단계에서 CI의 환경 변수를 전달합니다. cross-env나 빌드 도구 옵션으로 주입할 수 있습니다.
// package.json 스크립트 예시 (Vite)
{
"scripts": {
"build": "cross-env VITE_API_URL=$API_URL vite build"
}
}
Next.js/CRA도 동일하게 환경 변수를 설정한 상태로 빌드하면 번들에 주입됩니다. 값 변경 시 재빌드가 필요합니다.
7. 런타임 주입(window.__ENV__) 패턴
SPA를 여러 환경에 동일 빌드로 배포하고 싶다면, 빌드 외부의 스크립트로 런타임에 변수를 주입합니다.
// public/env.js (배포 시 생성)
window.__ENV__ = {
API_URL: 'https://api.example.com',
ENABLE_BETA: 'false'
};
// index.html에서 번들보다 먼저 로드
// <script src="/env.js"></script>
// src/config.ts
declare global {
interface Window { __ENV__?: { API_URL?: string; ENABLE_BETA?: string } }
}
export const API_URL = window.__ENV__?.API_URL ?? 'http://localhost:4000';
export const ENABLE_BETA = window.__ENV__?.ENABLE_BETA === 'true';
이 방식은 환경별 재빌드 없이 값을 바꿀 수 있지만, 여전히 클라이언트에 공개되므로 비밀 값은 금지입니다.
8. 안전 수칙과 실패 방지
비밀 값은 서버에만 두고, 클라이언트에는 공개 가능한 값만 넣습니다. 애플리케이션 시작 시 필수 변수 존재 여부를 검사하고 없으면 즉시 에러를 던져 빠르게 실패합니다. 또한 숫자/불리언은 문자열로 주입되므로 파싱을 명확히 합니다.
// 필수 값 체크
function requireEnv(name, value) {
if (!value) throw new Error(`환경 변수 누락: ${name}`);
return value;
}
export const API_URL = requireEnv('API_URL', import.meta.env.VITE_API_URL);
9. 자주 겪는 오류와 디버깅 팁
접두사 누락(REACT_APP_/VITE_/NEXT_PUBLIC_)으로 값이 undefined가 됩니다. .env를 바꾼 후 개발 서버 재시작을 잊지 마세요. 빌드 캐시로 오래된 값이 남을 수 있으니 CI에서 캐시 키에 .env 해시를 포함하거나 캐시를 무효화합니다. 불리언/숫자 처리 시 문자열 비교를 명확히 하고, 번들 결과물에 민감한 값이 포함되지 않았는지 검사합니다.
10. 정리
React에서 환경 변수는 도구마다 접근 방식과 접두사가 다르지만 공통적으로 빌드 타임 주입이며 클라이언트에 공개됩니다. 도구별 규칙(CRA: REACT_APP_, Vite: VITE_, Next: NEXT_PUBLIC_)을 지키고, 환경별 파일과 CI 주입을 체계화하세요. 런타임 변경이 필요하면 window.__ENV__ 패턴을 사용하고, 비밀은 항상 서버에만 두는 것이 안전합니다.
'React' 카테고리의 다른 글
| React에서 파일 업로드 기능 구현하기 (0) | 2026.04.22 |
|---|---|
| React와 GraphQL Apollo Client 연동하기 (1) | 2026.04.22 |
| React Portal로 모달 컴포넌트 구현하기 (0) | 2026.04.21 |
| React 앱에서 다국어(i18n) 지원하기 (2) | 2026.04.21 |
| React에서 Service Worker로 PWA 기능 추가하기 (0) | 2026.04.21 |