1. 개요
2026년 8월 24일 아침에 확인하니 회사소개와 전 제품 페이지가 500 이었다. 홈과 /contact 만 200. 로그를 따라가니 14시간 전부터였다. 아무도 몰랐다. 헬스 프로브는 초록이었고, 홈페이지는 열렸다. 이 글은 원인 세 겹과 “왜 14시간 동안 몰랐나” 를 다룬다.
2. 핵심 내용
2-1. 첫 겹: 프리렌더와 동적 API 의 공존
콘텐츠 페이지는 [...slug]/page.tsx catch-all 이다. 이 파일에 두 가지가 함께 있었다.
generateStaticParams: 빌드 시점에 slug 목록을 받아 정적 프리렌더generateMetadata안의headers(): 요청의 Host 를 읽어 어느 사이트인지 판정하는 동적 API
Next.js 는 이 조합을 DYNAMIC_SERVER_USAGE 로 거부한다. 정적으로 그리겠다고 해 놓고 요청 헤더를 읽으니 모순이다. 빌드는 통과했고, 런타임에 그 페이지를 요청하면 500.
생각해 보면 당연했다. Host 로 테넌트를 정하는 라우트는 본질적으로 요청마다 다르다. 같은 /about 이 철강 사이트와 수산 사이트에서 다른 내용이다. 프리렌더 시도 자체가 설계 오류였다.
2-2. 둘째 겹: 왜 이제 터졌나
generateStaticParams 는 오래 전부터 있었다. 그런데 왜 이제 문제가 됐나. 프리렌더 목록 API 가 항상 빈 배열 을 돌려주고 있었기 때문이다. 목록이 비면 프리렌더할 페이지가 없고, 모든 요청이 동적으로 처리되니 모순이 드러나지 않았다.
빈 배열이었던 이유가 또 두 겹이다.
- 목록 API 의 도메인 폴백이
.com이었다. 운영은.co. 빌드 컨테이너에서 환경변수가 안 잡히면 없는 도메인을 불렀다. - 응답 봉투
{status, data}를 벗기지 않았다. 배열이 아니라 객체가 오니Array.isArray가 항상 false.try/catch가 삼키고 빈 배열 반환.
어느 날 환경변수가 제대로 잡히고 봉투가 벗겨지자 목록이 채워졌고, 프리렌더가 실제로 시도되면서 모순이 터졌다. 잘못된 코드가 다른 잘못된 코드에 가려져 있었다. 하나를 고치자 다른 하나가 드러난 것이다.
2-3. 셋째 겹: 아무도 몰랐던 이유
14시간이다. 그 사이 로그는 있었다. 파드 stdout 에 스택트레이스가 찍혔다. 그런데 클러스터의 로그 수집 설정이 다른 프로젝트 네임스페이스만 keep 하고 있었다. 이 프로젝트의 로그는 클러스터를 나가지 않았다. 알림도 없었다.
헬스 프로브는 /api/health 를 본다. 200 이었다. 프로브는 원리상 “홈은 200, /about 은 500” 유형을 잡을 수 없다.
관측성이 없으면 장애는 존재하지 않는 것과 같다. 고객이 신고하지 않으면 영원히 모른다. B2B 사이트의 바이어는 신고하지 않는다. 다른 공급사를 찾는다.
2-4. 수정
generateStaticParams제거,export const dynamic = "force-dynamic".- 목록 API 호출부에서 봉투를 벗긴다. Server Action 과 서버 컴포넌트의 raw
fetch는 axios 인터셉터를 안 탄다는 것을 규칙으로 적었다. - 라우트 불변식 테스트:
src/app전체를 스캔해generateStaticParams와 동적 API(headers(),cookies()) 가 같은 파일에 있으면 실패한다. 문제 조합을 되돌려 테스트가 빨간불인지 확인한 뒤 머지했다. - 로그 수집 설정에 이 네임스페이스를 추가했다.
2-5. 배운 것
- 프리렌더할 수 있는 페이지와 없는 페이지를 테넌트 판정 방식 으로 가른다. Host 를 읽으면 프리렌더 불가.
- 응답 형태(봉투)를 바꾸는 계약은 모든 소비 경로를 찾아야 한다. 인터셉터는 한 경로만 지킨다.
try/catch로 빈 배열을 돌려주는 코드는 장애를 “빈 화면” 으로 번역한다. 목록이 비었을 때와 로드에 실패했을 때를 구분해야 한다.- 헬스 프로브는 프로세스 생사만 말한다. 페이지 단위 정상은 외부 감시나 로그 알림이 봐야 한다.
3. 마무리
요약
- Host 로 테넌트를 정하는 라우트는 프리렌더할 수 없다. 시도 자체가 오류.
- 잘못된 코드가 다른 잘못된 코드를 가린다. 하나를 고치면 다른 하나가 터진다.
- 관측성 없이는 장애가 존재하지 않는 것과 같다. 14시간.
- 불변식은 테스트로 잠그고, 잠글 때 빨간불을 먼저 본다.
마지막 편은 배포 자동화를 되돌린 이유다.