TechFeedTechFeed
Frontend

NEXT_PUBLIC, process.env, 환경변수 | 로컬은 되고 클라이언트만 비면?

클라이언트에서 NEXT_PUBLIC 값이 비면 런타임이 거절한 게 아니라 빌드 시점에 그 키가 번들에 안 박힌 줄입니다. 접두사, 통째 표기, 버셀 Preview 토글, 재배포. Next.js, 버셀, 한국 1인 개발자 기준. 2026년 9월 Next.js 환경변수 문서.

by

클라이언트에서 process.env.NEXT_PUBLIC_가 비면, 런타임이 읽기를 거절한 게 아니라 빌드 시점에 그 키가 번들에 안 박힌 줄입니다. 로컬 .env.local에만 있고 프리뷰 환경에 없으면 화면만 빈 문자열이 됩니다.


서버 로그에는 값이 보이는데 브라우저 콘솔만 undefined인 일, 넥스트 앱을 버셀에 올리면 한 번은 만납니다. 키 이름을 바꿨는데 재배포를 안 했거나, 접두사 없이 서버 전용 키를 클라이언트에서 읽은 경우가 대부분입니다.


도커 재시작보다 빌드 로그에 그 키가 있었는지부터 보세요. 근거는 Next.js 환경변수 가이드브라우저 번들 규칙에 있습니다.


클라이언트 값은 빌드에 박히나

접두사가 있는 키만 브라우저 코드로 들어갑니다. 넥스트는 NEXT_PUBLIC_로 시작하는 값을 빌드할 때 문자열로 바꿔 넣습니다. 서버 컴포넌트와 라우트 핸들러는 접두사 없는 키도 process.env로 읽습니다. 같은 이름을 클라이언트 파일에서 읽으면 번들에 없어서 undefined입니다.


그래서 로컬 dev는 되고 프리뷰만 비는 상황이 생깁니다. 개발 서버는 파일을 바꿀 때 값을 다시 읽지만, 프로덕션 번들은 빌드 당시 값입니다. 버셀 대시보드에서 키를 넣은 뒤 재배포를 안 하면 옛 번들이 빈 값을 들고 있습니다.


폴더마다 env 파일을 나누는 도구와는 칸이 다릅니다. direnv는 셸 환경을 바꾸고, 넥스트는 빌드가 어떤 파일을 읽었는지가 기준입니다. 셸 도구는 direnv 글을 보세요.


먼저 기억할 것 | 브라우저에 나갈 값은 이름 앞에 NEXT_PUBLIC_이 있어야 하고, 바꾼 뒤에는 다시 빌드해야 합니다. 서버 로그에 보인다고 클라이언트에도 있는 게 아닙니다.


NEXT_PUBLIC 환경변수가 빌드 시점에 클라이언트 번들에 들어가는 개념 이미지
접두사 없는 키는 서버에만 있고 브라우저 번들엔 없다

값이 비는 세 칸

접두사, 파일 위치, 배포 환경 세 칸이면 원인이 갈립니다.


증상먼저 할 일
접두사서버는 되고 브라우저만 undefinedNEXT_PUBLIC_ 여부, 클라이언트 파일인지
파일CI만 빔.env.local은 커밋 안 됨, .env 예시만 있는지
배포 환경프로덕션은 되고 프리뷰만 빔버셀 Production/Preview/Development 토글
재빌드대시보드에 넣었는데 화면은 옛값환경 변경 후 재배포
동적 키변수로 키를 조합하면 빔process.env.NEXT_PUBLIC_FOO 통째 표기

다섯 번째 칸이 잘 안 보입니다. 넥스트는 빌드가 소스에서 process.env.NEXT_PUBLIC_FOO 같은 완전한 식별자를 봐야 값을 넣습니다. process.env[name]처럼 변수로 열면 번들에 안 들어갑니다.


쿠키를 서버에서 읽는 줄과 섞지 마세요. 요청마다 달라지는 값은 환경변수가 아니라 쿠키와 헤더입니다. 그 칸은 cookies await 글입니다.


빌드가 인식하는 표기와 인식 못 하는 표기
// 번들에 들어감 const key = process.env.NEXT_PUBLIC_KAKAO_JS_KEY // 번들에 안 들어감 const name = 'NEXT_PUBLIC_KAKAO_JS_KEY' const missed = process.env[name] // 서버 전용. 클라이언트 파일에 두면 undefined const secret = process.env.KAKAO_CLIENT_SECRET

비밀키는 접두사를 붙이지 않기

브라우저에 나가는 값은 누구나 소스에서 볼 수 있습니다. 카카오 REST 시크릿, 디비 URL, 서비스 롤 키는 NEXT_PUBLIC_을 붙이지 않습니다. JS 키처럼 콘솔에 공개되는 값만 접두사를 씁니다.


클라이언트에서 비밀이 필요하면 라우트 핸들러나 서버 액션을 거쳐야 합니다. 화면 파일에 시크릿을 넣으면 값이 비는 게 오히려 다행이고, 접두사를 잘못 붙이면 번들에 그대로 박힙니다. 빌드 산출물에서 문자열 검색 한 번이면 유출 여부를 알 수 있습니다.


카카오 로그인 콜백처럼 서버만 쓰는 URL도 접두사 없이 둡니다. 브라우저가 써야 하는 리다이렉트 주소만 공개 키로 둡니다. CORS로 막히는 줄과 값이 비는 줄은 다릅니다. 헤더 문제는 CORS 글입니다.


  • [ ] 클라이언트에서 읽는 키에 NEXT_PUBLIC_이 있는지 봤다
  • [ ] process.env.키이름 통째 표기인지 확인했다
  • [ ] 시크릿에 접두사를 붙이지 않았는지 검색했다
  • [ ] 버셀 환경 토글에 Preview가 포함됐는지 봤다
  • [ ] 값을 바꾼 뒤 재배포했는지 확인했다

버셀 대시보드에서 Production과 Preview 환경변수가 갈리는 설정 화면 개념 이미지
프리뷰 토글을 끄면 대시보드에 키가 있어도 프리뷰 번들은 비어 있다

.env.local과 대시보드가 갈릴 때

넥스트가 읽는 파일은 .env, .env.local, .env.development, .env.production 순으로 겹칩니다. .env.local은 깃에 안 올리는 게 기본이라, 액션과 버셀은 그 파일을 모릅니다. 로컬만 되는 값의 거의 전부가 여기입니다.


버셀은 프로젝트 설정의 Environment Variables에 Production, Preview, Development를 따로 둡니다. Development는 vercel dev용이고, 깃 프리뷰 배포는 Preview입니다. 토글을 Production만 켜 두면 프리뷰 빌드에 키가 없습니다.


도커로 빌드하면 ARG와 ENV를 빌드 스테이지에 넘겨야 합니다. 런타임 ENV만 넣고 next build를 앞에서 해 두면 공개 키는 이미 빈 문자열로 박힙니다. 빌드 명령이 도는 스테이지에 값을 주세요.


클라이언트에서 값이 있는지 빌드 로그로 확인
// 공개 키만. 시크릿을 찍지 말 것 console.log('kakao js', process.env.NEXT_PUBLIC_KAKAO_JS_KEY ? 'set' : 'empty') // package.json // "build": "next build"

실전에서 좁히는 순서

브라우저 콘솔에서 값이 비면, 그 파일이 클라이언트 컴포넌트인지 보고 접두사를 확인합니다. 서버 로그에는 있으면 접두사 없는 키를 클라이언트에서 읽은 겁니다. 둘 다 없으면 파일과 대시보드를 봅니다.


키를 고쳤으면 반드시 재빌드합니다. 개발 서버는 재시작, 버셀은 재배포입니다. 이미지 호스트처럼 다른 설정 칸과 섞이면 remotePatterns 글을 따로 보세요. env가 비는 것과 호스트 허용은 다른 실패입니다.


콘솔에 시크릿을 찍지 않기 | 디버그할 때는 값이 있는지 없는지만 로그하세요. NEXT_PUBLIC 값은 번들에 남고, 서버 시크릿을 클라이언트 로그에 남기면 유출입니다.


.env.local은 깃에 없고 버셀 프리뷰는 대시보드 키만 읽는 개념 이미지
로컬 파일에만 있으면 프리뷰 빌드는 그 키를 모른다

참고 자료


내부 연계: 폴더별 환경 변수, cookies와 headers, 이미지 호스트, CORS


인용한 동작은 2026년 9월 공개 문서 기준입니다.


자주 묻는 질문

서버 로그에는 있는데 브라우저만 비어 있습니다.

접두사가 없거나, 클라이언트 파일이 서버 전용 키를 읽은 경우입니다. 브라우저에 나갈 값만 NEXT_PUBLIC_을 붙이고 재빌드하세요.


버셀에 키를 넣었는데 화면이 안 바뀝니다.

공개 키는 빌드 시점 값입니다. 대시보드 저장만으로는 옛 번들이 남습니다. Preview 토글을 켠 뒤 재배포하세요.


process.env[name]으로 읽으면 안 되나요?

클라이언트 번들에서는 안 들어갑니다. 빌드가 소스에서 키 이름을 통째로 봐야 값을 넣습니다. 서버 코드는 동적 접근이 되는 경우가 있어도, 공개 키는 완전한 식별자로 쓰세요.


시크릿에도 NEXT_PUBLIC_을 붙이면 편하지 않나요?

번들에 문자열이 남습니다. REST 시크릿과 디비 URL은 서버 전용으로 두고, 화면은 라우트 핸들러를 거치세요.


.env.example만 올리면 CI가 읽나요?

예시는 키 목록일 뿐 값이 없습니다. 액션 시크릿이나 버셀 대시보드에 실제 값을 넣어야 합니다. .env.local은 보통 커밋하지 않습니다.


개발 서버를 재시작해야 하나요?

.env.local을 바꾼 뒤에는 next dev를 다시 켜는 편이 맞습니다. 공개 키는 시작 시점에 읽히는 값이 많습니다. 배포는 재빌드가 필요합니다.


클라이언트 환경변수가 비면 빌드가 그 키를 번들에 안 넣었다는 뜻입니다. 접두사, 통째 표기, 프리뷰 토글, 재배포 네 칸을 순서대로 보세요. 관련 글: 폴더별 env, cookies await, 이미지 호스트.


NEXT_PUBLICprocess.env환경변수넥스트버셀클라이언트빌드프론트엔드.env.local개발자

함께 보면 좋은 문제 해결

EXPLORE / Frontend

이어서 읽어보기

전체 토픽 둘러보기