revalidate를 지웠는데 페이지가 빌드 시점 데이터로 굳어 있던 이유
문제 발생
DB에서 목록을 읽는 sitemap.ts가 새 글을 반영하지 않았습니다. 배포할 때마다 그 시점의 목록으로 고정됐습니다.
revalidate 설정을 지웠는데도 그대로였고, 결국 DB에 접속할 수 없는 Docker 빌드 환경에서 빌드가 실패하면서야 원인이 드러났습니다. 빌드 중에 DB를 읽고 있었던 것입니다.
원인 분석
Next.js는 기본값(dynamic = 'auto')에서 가능한 한 많이 캐시합니다. 공식 문서의 표현대로 "어떤 컴포넌트도 동적 동작을 선택하지 못하게 막지 않으면서 최대한 캐시하는" 것이 기본 옵션입니다.
여기서 중요한 것은 무엇이 라우트를 동적으로 만드는가입니다. cookies(), headers(), searchParams 같은 요청 시점(Request-time) API를 쓰거나 캐시되지 않은 데이터를 가져오면 동적이 됩니다. 그런 신호가 하나도 없으면 Next.js는 그 라우트를 정적으로 판단하고 빌드 시점에 한 번 실행해 결과를 굳힙니다.
sitemap.ts 안에는 그런 API 호출이 전혀 없었습니다. DB 조회는 fetch가 아니라 드라이버 호출이라 Next.js가 "동적"이라고 볼 근거가 없습니다. 그래서 revalidate를 지우는 것만으로는 동적이 되지 않고, 오히려 정적으로 되돌아갔습니다.
로컬에서는 .env.local의 DB가 항상 연결되므로 빌드가 조용히 성공해서 이 차이가 드러나지 않았습니다.
해결 방안
- 매 요청 실행이 필요하면 명시합니다. 추측에 맡기지 않습니다.
export const dynamic = "force-dynamic";문서 기준 이 값은 요청 시점마다 사용자별로 라우트를 렌더하도록 강제하며, 레이아웃/페이지의 모든 fetch에 { cache: 'no-store', next: { revalidate: 0 } }를 설정하고 fetchCache = 'force-no-store'를 설정한 것과 같습니다.
- 네 값의 뜻을 구분합니다.
| 값 | 동작 |
|---|---|
'auto' (기본) | 동적 선택을 막지 않는 선에서 최대한 캐시 |
'force-dynamic' | 요청 시점마다 렌더 |
'error' | 정적 렌더를 강제하고, 요청 시점 API나 캐시되지 않은 데이터를 쓰면 오류 |
'force-static' | 정적 렌더를 강제하고 cookies()·headers()·useSearchParams()가 빈 값을 반환하게 함 |
-
'force-static'의 부작용을 압니다. 오류가 아니라 빈 값이 돌아옵니다. 쿠키를 읽어 분기하는 코드가 조용히 "로그아웃 상태"로 동작합니다. -
정적이어야 하는 페이지에는
'error'가 좋은 안전장치입니다. 실수로 요청 시점 API가 들어오면 빌드에서 막힙니다. -
revalidate는 다른 축입니다.false(기본)는 사실상 무기한 캐시,0은 요청 시점 API나 캐시되지 않은 fetch가 없어도 항상 동적으로 렌더하게 만들고, 숫자는 그 초만큼의 재검증 주기입니다. 값은 정적으로 분석 가능해야 해서revalidate = 600은 되지만revalidate = 60 * 10은 안 됩니다. -
next build출력으로 실제 결과를 확인합니다. 의도와 실제가 다른지는 여기서만 드러납니다.
○ (Static) prerendered as static content
ƒ (Dynamic) server-rendered on demand-
개발 서버로는 확인되지 않습니다. 문서도 명시하듯 개발 환경에서 페이지는 항상 요청 시점에 렌더되고 캐시되지 않습니다. 정적/동적 판정은
next build로만 보입니다. -
버전 조건을 확인합니다. Next.js 16에서
cacheComponents플래그를 켜면dynamic·dynamicParams·revalidate·fetchCache라우트 세그먼트 설정이 제거됩니다. 이 글은 그 플래그를 쓰지 않는 프로젝트 기준입니다.
댓글0
댓글을 남기려면 로그인이 필요해요. 로그인
아직 댓글이 없어요. 첫 의견을 편하게 남겨 보세요.