얼마 전 Vercel 보안 사고 공지를 보고 Next.js 버전을 올렸다. 계기는 보안 이슈 대응이었고, 작업 자체는 비교적 단순할 거라고 생각했다. 그런데 업그레이드 직후 예상하지 못한 문제가 하나 생겼다.
pnpm dev로 개발 서버를 띄운 뒤 http://localhost:3000으로 접속하면 정상인데, 같은 맥북의 로컬 네트워크 주소인 http://192.168.x.x:3000으로 접속하면 첫 화면에서 계속 로딩 화면만 보였다. 처음에는 API 문제나 CORS, 혹은 네트워크 문제라고 생각했다. 하지만 실제 원인은 전혀 다른 곳에 있었다.
증상
내 환경은 대략 이랬다.
- macOS
pnpm dev- Next.js 업그레이드 이후부터 문제 발생
localhost:3000은 정상192.168.x.x:3000은 초기 스켈레톤 화면에서 멈춘 것처럼 보임
겉으로 보면 "페이지는 열리는데 앱이 끝까지 로드되지 않는" 상태였다. 특히 홈 화면이 dynamic(..., { ssr: false }) 로딩 UI를 쓰고 있어서, 더더욱 "무한 로딩"처럼 보였다.
처음에 의심했던 것들
처음엔 아래 같은 후보들을 생각했다.
NEXT_PUBLIC_API_BASE_URL설정이 잘못된 건 아닌가- 같은 와이파이인데도 네트워크 접근에 문제가 있나
- 브라우저 CORS 정책에 걸린 건 아닐까
- 클라이언트 코드에서
localhost를 하드코딩한 부분이 있나
실제로 .env.local에는 이런 환경변수가 있었다.
이건 "다른 기기에서 프론트를 열었을 때"는 분명히 문제가 된다. 아이폰이나 다른 노트북에서 프론트를 열면, 그 기기 기준의 localhost:8152를 바라보게 되기 때문이다.
다만 이건 이번 현상의 핵심 원인은 아니었다. 왜냐하면 홈 첫 화면은 API를 치기 전부터 이미 스켈레톤에서 멈추고 있었기 때문이다. 그래서 원인을 다시 찾아야 했다.
실제 원인
원인은 Next.js 개발 서버의 보안 강화였다.
업그레이드한 버전에서는 개발 모드에서 localhost 외의 origin에서 들어오는 dev resource 요청을 기본적으로 제한하고 있었다. 페이지 HTML 자체는 열릴 수 있어도, 내부 스크립트나 청크, 개발용 endpoint 요청이 차단되면 실제 앱은 끝까지 hydration되지 못한다. 그 결과 사용자는 "무한 로딩"처럼 보이는 화면만 보게 된다.
핵심은 이거였다.
문제는 앱 로직이 아니라, 개발 모드에서 내부 dev resource 요청이 cross-origin으로 판단되어 차단된 것이었다.
즉, Vercel 보안 사고가 직접 원인은 아니었다. 정확히는 그 공지를 계기로 Next.js를 업그레이드했고, 그 과정에서 강화된 dev server 보안 정책을 밟은 셈이다.
처음 시도한 잘못된 해결
처음엔 단순하게 allowedDevOrigins에 현재 IP를 넣는 방식도 생각했다.
예를 들면 이런 식이다.
하지만 이 방식은 DHCP 환경에서 전혀 지속 가능하지 않다. 집이나 회사 와이파이에서 IP는 얼마든지 바뀔 수 있고, 그때마다 설정을 수정하는 건 말이 안 된다.
그래서 방향을 바꿨다.
- IP를 허용하는 게 아니라
- 안정적인 로컬 호스트명을 허용하는 방식으로
macOS에서는 Bonjour/mDNS 덕분에 LocalHostName.local 형식의 주소를 쓸 수 있다. 내 맥북의 경우 habyeongloui-MacBookPro.local이었다. 이 이름은 DHCP로 IP가 바뀌더라도 비교적 안정적으로 유지된다.
최종 해결
최종적으로는 세 가지를 정리했다.
1. 개발 서버를 LAN에 바인딩
package.json에서 dev 서버를 0.0.0.0으로 열었다.
여기서 중요한 점은 0.0.0.0은 "접속용 주소"가 아니라 "서버 바인딩 주소"라는 것이다. 브라우저에서 http://0.0.0.0:3000으로 접속하는 건 맞지 않다. 실제 접속은 localhost, .local, 혹은 실제 LAN IP로 해야 한다.
2. allowedDevOrigins를 .local 기준으로 설정
next.config.ts에는 이렇게 넣었다.
그리고 .env.local에는 다음 값을 추가했다.
이제 로컬 IP를 하드코딩하지 않고도, .local 호스트를 통해 개발 서버에 접근할 수 있게 됐다.
3. API base URL도 localhost에서 .local로 변경
프론트만 열리는 것으로 끝이 아니었다. 다른 기기에서 실제 API 호출까지 정상 동작시키려면, API 주소도 더 이상 localhost여서는 안 된다.
최종적으로 .env.local은 이렇게 정리했다.
이제 아이폰이나 다른 노트북에서 프론트를 열더라도, API 요청이 자기 자신의 localhost로 향하지 않고 실제 맥북의 백엔드로 향하게 된다.
여기서 끝이 아니었다: 실기기 검증에서 나온 추가 이슈
LAN 접속 문제를 해결한 뒤, 아이폰에서 실제로 열어봤다.
흥미로웠던 건 Safari에서는 문제가 없었는데, 카카오톡에서 링크를 눌러 열린 인앱 브라우저에서는 별도의 hydration warning이 보였다는 점이다.
처음엔 iPhone Safari 문제라고 생각했지만, Safari에서 직접 열어보니 재현되지 않았다. 결국 이건 "모바일 브라우저 전반의 문제"가 아니라 "카카오톡 인앱 브라우저 특이 케이스"로 보는 게 맞았다.
로그에는 이런 식의 hydration mismatch 경고가 찍혔다.
- 서버에서 렌더된 HTML과 클라이언트 초기 DOM이 다름
body에-webkit-text-size-adjust: 100%같은 스타일 차이가 생김
인앱 브라우저는 순정 Safari와 완전히 같지 않다. WebKit 기반이더라도 자체적으로 뷰포트나 텍스트 스케일 관련 속성을 덧붙이는 경우가 있어서, SSR HTML과 클라이언트 DOM이 어긋나는 일이 생길 수 있다.
이 부분은 아래처럼 완화했다.
추가로 모바일 환경에서 crypto.randomUUID() 관련 호환성 문제도 보여서, 브라우저가 지원하지 않으면 fallback ID를 쓰도록 정리했다.
이 후속 이슈는 메인 문제와는 다른 층위였다.
- 메인 문제: Next.js dev server의 origin 제한
- 후속 문제: 특정 인앱 브라우저 환경에서의 hydration mismatch
둘을 분리해서 봐야 문제를 정확히 이해할 수 있었다.
정리
이번 트러블슈팅에서 얻은 결론은 세 가지다.
첫째, 보안 공지를 계기로 프레임워크를 업그레이드하는 건 맞는 대응이다. 다만 업그레이드 후 문제가 생겼을 때 "버전 올리니까 이상해졌다" 정도로 뭉뚱그리면 원인을 놓치기 쉽다. 어떤 보안 모델이 바뀌었는지를 먼저 봐야 한다.
둘째, localhost 중심으로만 개발 환경을 짜두면 실기기 테스트에서 금방 한계가 온다. 특히 프론트와 API를 다른 기기에서도 열어야 한다면, .local 같은 안정적인 호스트명을 사용하는 편이 DHCP 환경에서 훨씬 낫다.
셋째, 실기기 검증은 "iPhone에서 열어봤다"로 끝나지 않는다. Safari와 카카오톡 인앱 브라우저는 다르고, 인앱 브라우저는 또 다른 종류의 문제를 드러낼 수 있다.
결국 이번 이슈는 "보안 사고 때문에 망가졌다"가 아니라, 보안 사고를 계기로 업그레이드하면서 드러난 개발환경 의존성과 브라우저 환경 차이의 문제였다.
참고
- Vercel 2026년 4월 보안 사고 공지 https://vercel.com/kb/bulletin/vercel-april-2026-security-incident
- Next.js
allowedDevOrigins문서 https://nextjs.org/docs/pages/api-reference/config/next-config-js/allowedDevOrigins