Next.js
Next.js 서버 환경변수와 브라우저 공개 값을 나눈 설정 체크리스트
배포 후 브라우저 번들에서 내부 주소가 보였던 일을 계기로 환경변수의 경계를 점검한 목록이다.
배포한 뒤에야 보인 값
로컬에서는 API_INTERNAL_URL 하나로 잘 동작했다. 그런데 배포한 페이지의 소스와 브라우저 번들을 확인하니 내부용 API 호스트가 문자열로 포함되어 있었다. 기능은 고장 나지 않았지만, 브라우저에서 쓸 필요가 없는 주소가 공개된 상태였다. 원인은 서버 컴포넌트에서 읽어야 할 값과 클라이언트 컴포넌트에서 읽을 값을 같은 이름으로 관리한 데 있었다.
Next.js에서 브라우저 번들로 전달할 값은 공개해도 되는 것만 NEXT_PUBLIC_ 접두사를 붙인다. 접두사가 없다고 해서 모든 상황에서 비밀이 되는 것은 아니지만, 클라이언트 코드가 그 값을 읽는 순간 보호 경계가 무너진다.
먼저 사용처를 두 종류로 표시하기
설정 파일을 다음처럼 나눴다.
# .env.example
API_INTERNAL_URL=http://api:3000
NEXT_PUBLIC_SITE_URL=https://example.com
NEXT_PUBLIC_ANALYTICS_ID=
API_INTERNAL_URL은 서버에서만 호출하고, 공개 사이트 주소와 분석 ID만 클라이언트가 읽는다. 특히 API 키, 데이터베이스 URL, 서명용 시크릿에는 NEXT_PUBLIC_를 붙이지 않는다.
// lib/config.ts
export const serverConfig = {
apiInternalUrl: process.env.API_INTERNAL_URL,
sessionSecret: process.env.SESSION_SECRET,
};
export const publicConfig = {
siteUrl: process.env.NEXT_PUBLIC_SITE_URL,
analyticsId: process.env.NEXT_PUBLIC_ANALYTICS_ID,
};
빌드 시점에 값이 박히는 항목도 있어서, 배포 후 환경변수만 바꾸면 즉시 바뀐다고 가정하지 않았다. 이미지 빌드 단계와 실행 단계 중 어디에서 주입되는지도 함께 기록했다.
확인 명령과 수정 기준
npm run build
rg -n 'API_INTERNAL_URL|SESSION_SECRET|sk-[A-Za-z0-9]+' .next/static
실제 값 대신 변수명이나 테스트용 문자열이 나오는지 확인하고, 민감한 값이 발견되면 로그를 공유하기 전에 즉시 폐기한다. 값이 노출되지 않았더라도 .env.example에는 이름만 남기고 실제 파일은 저장소에 넣지 않았다.
- 서버 전용 값이
use client파일에서 import되지 않는다. - 공개해도 되는 값만
NEXT_PUBLIC_로 시작한다. - CI 변수와 런타임 변수의 주입 시점이 문서에 적혀 있다.
- 빌드 산출물에서 실제 비밀값을 검색했다.
- 노출 가능성이 있던 키는 사용 이력이 없더라도 교체를 검토했다.
설정 이름의 접두사만 바꾸는 것으로 끝내지 않고, 번들 검색까지 해야 마음이 놓였다. 다음 프로젝트부터는 설정 객체를 처음부터 서버용과 공개용으로 분리해 잘못된 import를 눈에 띄게 만들 생각이다.