Develop

Next.js 환경변수 완벽 가이드 — .env.local부터 NEXT_PUBLIC 클라이언트 변수까지

Next.js에서 API 키는 서버에서만 써야 하므로 NEXT_PUBLIC 없이, 브라우저에서도 써야 하면 NEXT_PUBLIC를 붙여야 한다. .env 파일 종류, 서버/클라이언트 변수 구분, 환경별 설정까지 정리했다.

Next.js환경변수.envNEXT_PUBLIC보안
Next.js 프로젝트의 .env.local 파일에 환경변수를 정의하고 서버와 클라이언트 컴포넌트에서 각각 접근하는 코드 예시
  • ·NEXT_PUBLIC_ 접두사: 빌드 시 번들에 인라인으로 포함되어 브라우저에서 접근 가능
  • ·.env.local: Git에 커밋되지 않는 로컬 전용 환경변수 파일
  • ·Next.js 환경변수 우선순위: .env.local > .env.[NODE_ENV].local > .env.[NODE_ENV] > .env
  • ·서버 컴포넌트 환경변수: 런타임에 동적으로 읽히므로 재빌드 없이 값 변경 가능
처음에 클라이언트 컴포넌트 파일 안에서 무턱대고 `process.env.API_KEY`를 가져와 실행했다가 화면에 계속 `undefined` 빈 값이 뜨길래 한참을 헤맨 흑역사가 있습니다. 접두사 규칙을 모르고 겪은 삽질이었는데, 이를 깨달은 직후 접두사를 덜컥 붙였다가 깃허브 번들 스캔에서 시크릿 키가 인라인으로 노출될 뻔한 위험을 넘기고 등등의 과정을 통해 환경변수 보안 경계의 무서움을 뼈저리게 체감했습니다. 현재는 Zod를 엮어서 필수 변수 검증계를 구축해 둔 덕분에, 새로운 팀원이 합류해도 누락된 설정을 빌드 단에서 즉각 감지해 대응하고 있습니다.

1. 서버와 클라이언트의 경계와 보안

민감 정보 노출을 방지하는 NEXT_PUBLIC 접두사의 핵심 동작 원리

Next.js 개발을 할 때 가장 헷갈리면서도 삐끗하면 보안 대형 사고로 이어지는 영역이 바로 NEXT_PUBLIC_ 접두사의 용법입니다. 기본적으로 접두사 없이 선언된 DATABASE_URL이나 API_SECRET_KEY 같은 변수들은 철저하게 노드(Node.js) 서버 실행 컨텍스트 안에서만 격리되어 기동됩니다. 서버 컴포넌트, API 라우터 등 서버 단 코드에서 호출할 때는 정상적으로 값을 읽어올 수 있지만, 브라우저 번들로 넘어가는 클라이언트 컴포넌트 영역에서 호출하면 undefined가 리턴되어 유출을 차단하죠. 하지만 클라이언트 단에서 구글 애널리틱스 토큰 같은 비민감 공개값을 접근해야 할 때 변수명 앞에 NEXT_PUBLIC_을 추가하게 되는데, 이 접두사가 붙은 변수들은 빌드 시점에 자바스크립트 소스코드 파일 안에 원시 문자열로 박혀 인라인(Inline) 처리됩니다. 즉, 개발자 도구를 켜고 JS 번들 파일을 열어 검색만 하면 하드코딩된 값들이 적나라하게 노출된다는 뜻입니다. 여기에 결제 시크릿 키나 DB 패스워드를 담아두는 것은 해커에게 지갑을 열어주는 대참사이므로, 보안 경계를 명확히 구분하는 기본기가 필수적입니다.

2. 파일 구성 체계와 우선순위

Next.js 프로젝트에서 다루는 .env 파일 종류와 덮어쓰기 순서

프로젝트의 덩치가 커질수록 로컬 테스트용, CI/CD 테스트용, 스테이징 테스트용, 실 배포용 등 수많은 환경별 파일이 엉켜 복잡해집니다. Next.js는 유연한 환경을 위해 여러 겹의 .env 파일 스택을 지원하며, 정해진 우선순위 법칙에 따라 오버라이드(Override)를 수행합니다. 가장 밑단에는 전역 공통 환경변수를 담는 .env가 있고, 그 위로 development, production, test 상태를 판별해 매핑되는 .env.development.env.production 등이 올라가며, 최상단에는 로컬 개발자 개인의 비공개 자격증명을 정의하는 .env.local이 군림하여 모든 하위 설정을 덮어씁니다. 여기서 한 가지 삽질 주의점은, 유닛 테스트를 돌릴 때 사용되는 test 구동 모드 아래에서는 로컬 개발자 편의용 .env.local 파일 자체가 적용 대상에서 제외된다는 점입니다. 실무에서는 이러한 우선순위의 특징을 제대로 파악하지 못해 로컬 로직이 왜 테스트 환경에서 실패하는지 헤매는 경우가 잦으므로, 공통 뼈대는 저장소에 노출해도 되는 껍데기 .env.example로 형상 관리를 하고, 로컬 변수는 무조건 .gitignore 리스트에 포함된 .env.local 파일 하나로 관리하는 게 협업 표준입니다.

3. 환경변수 선언 및 실무 주입법

.env.local 작성 요령 및 서버 사이드 런타임 환경변수 접근법

로컬 개발 망을 세팅하려면 루트 디렉토리에 .env.local 파일을 개설하고 DATABASE_URL=주소와 같이 줄바꿈 형태로 매핑을 해두면 됩니다. 간혹 문자열 값을 쿼테이션으로 묶어야 하는지 헷갈릴 수 있는데, 공백이나 특수 문자가 없다면 그냥 따옴표 없이 적는 게 깔끔하고 혹시 모를 파싱 에러를 예방할 수 있죠. 서버 컴포넌트 내부에서 환경변수를 호출할 때는 비동기(async) 컴포넌트 라이프사이클 상단에서 process.env.변수명으로 바로 가져올 수 있는데, 이때 값이 누락되어 일어날 수 있는 널 포인터 버그를 방지하기 위해 기본 백업값(process.env.PORT ?? 3000)을 달아주는 것이 방어적 프로그래밍 관점에서 유용합니다. 서버 전용 변수를 클라이언트 컴포넌트 단에서 부주의하게 찔러 쓰려고 시도하면 Next.js 빌드 시점에 가차 없이 엄격한 경고 로그를 뿌려서 개발자가 보안 구멍을 빨리 메울 수 있게 도와주므로, 경고 로그를 무시하지 말고 체크하는 습관이 중요합니다.

# .env.local
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
API_SECRET_KEY=your-secret-key
NEXT_PUBLIC_GA_ID=G-XXXXXXXXXX
NEXT_PUBLIC_API_URL=https://api.example.com

# 서버 컴포넌트에서 사용
// app/page.tsx (서버 컴포넌트)
export default async function Page() {
  const dbUrl = process.env.DATABASE_URL  // 서버에서만 접근 가능
  const gaId = process.env.NEXT_PUBLIC_GA_ID  // 클라이언트에서도 접근 가능
  return <div />
}

zod 스키마 스펙을 활용한 안전한 환경변수 타입 검증 시스템 구축하기

프로젝트 규모가 커지고 다양한 시크릿 키가 얽히면, 어떤 변수가 빠졌는지도 모른 채 서버를 올렸다가 런타임 중에 특정 API 호출 단에서야 비로소 undefined 크래시를 뿜어 장애를 일으키는 피곤한 상황이 벌어집니다. 이를 방지하는 매우 스마트한 기법이 바로 Zod 스키마 유틸리티를 활용한 빌드 타임 사전 검증 환경 설계입니다. zod를 이용해 애플리케이션 시작점에 필요한 모든 서버용/클라이언트용 환경변수의 스키마(z.string().url(), z.string().min(5))를 선언해 두고, 서버 부팅 시점에 즉시 파싱 검증을 수행하도록 구조화하는 방식이죠. 이렇게 세팅해 두면 필요한 환경변수 중 단 하나라도 공란이거나 포맷이 어긋났을 때 서버 기동 첫 단계에서 즉각적인 경보와 함께 배포 빌드를 중단시켜 주므로, 런타임 버그의 싹을 완전히 잘라내고 타입 에러의 고통 없이 안전한 데이터 소싱을 달성할 수 있어 모던 프론트엔드 실무 아키텍처 구성 시 필수 코스로 여겨집니다.

자주 묻는 질문

로컬에서 .env.local 설정을 변경하고 새로고침을 했는데도 코드상에 적용이 안 됩니다.+

환경변수는 Next.js 서버 구동 최초 시점에 한 번만 프로세스 메모리에 로드됩니다. 코드 HMR(Hot Module Replacement) 기능으로는 환경변수 변경사항이 적용되지 않으므로, 변경 즉시 로컬 터미널의 개발 서버를 종료하고 npm run dev를 다시 실행하셔야 정상 반영됩니다.

NEXT_PUBLIC_ 변수를 클라이언트 단에 넘겨주고 싶은데 보안 유출은 싫습니다. 해결책이 있나요?+

변수 자체를 클라이언트에 직접 노출하는 대신, 안전한 서버 컴포넌트(Server Component) 영역에서 환경변수를 호출한 뒤 클라이언트 자식 컴포넌트의 Props 매개변수 데이터로 필요한 핵심 알맹이만 직접 발라 넘겨주는 디자인 패턴을 적용하면 보안 유출 없이 가볍게 제어할 수 있습니다.

관련 글