Next.js Route Handler GET 캐시가 적용되지 않을 때 force-static 점검법 점검이 필요한 이유
현재 Next.js App Router의 Route Handler는 기본적으로 캐시되지 않습니다. GET 응답을 정적으로 캐시하려면 export const dynamic = 'force-static'처럼 명시적으로 선택해야 합니다. 이때 export const dynamic = 'force-static'을 설정해도 캐시가 작동하지 않는다면 코드 내부에 동적 요소가 남아 있거나 설정이 충돌하는 상황입니다. 순서대로 점검하지 않으면 원인을 찾기 어렵고, 불필요한 서버 부하와 응답 지연이 반복됩니다.
Next.js Route Handler GET 캐시가 적용되지 않을 때 force-static 점검법 핵심 항목
1. dynamic 설정 위치와 철자
- 확인 대상: route.ts(또는 route.js) 파일 최상단에
export const dynamic = 'force-static'구문이 정확히 존재하는지 확인합니다. - 확인 방법: 먼저 파일 상단에서
export const dynamic을 검색한 뒤, 따옴표 안 값이'force-static'인지 대소문자와 하이픈을 포함해 일치하는지 대조합니다. - 실패 신호:
forceStatic,force_static,FORCE-STATIC같은 변형이나export default로 선언했다면 Next.js가 인식하지 못합니다. - 조치: 정확히
export const dynamic = 'force-static'으로 수정하고, 파일 최상단(import 구문 아래, 함수 정의 위)에 배치합니다.
2. 동적 함수 사용 여부
- 확인 대상: GET 핸들러 내부에서
cookies(),headers()같은 런타임 API나 요청별 정보를 읽는지 확인합니다. - 확인 방법: 핸들러 본문에서
cookies,headers와request.url,request.headers,request.cookies접근을 검색합니다. 요청마다 달라지는 값이 필요하다면 정적 캐시보다 동적 응답이 맞는지 먼저 판단합니다. - 실패 신호: 빌드 로그에 "Dynamic server usage" 경고가 표시되거나, 개발 서버에서 매 요청마다 핸들러가 재실행되면 동적 함수가 원인일 가능성이 높습니다.
- 조치: 불필요한 동적 함수 호출을 제거하거나, 정적으로 결정 가능한 값으로 대체합니다. 반드시 필요하다면
force-static설정을 제거하고 동적 렌더링을 허용합니다.
3. Request 객체 접근
- 확인 대상: GET 핸들러 매개변수로 받은
request: Request객체의 속성을 읽는지 확인합니다. - 확인 방법: 먼저 핸들러 함수 시그니처에서
request매개변수를 찾은 뒤, 함수 본문에서request.url,request.headers같은 속성 접근을 검색합니다. - 실패 신호:
force-static을 설정했는데도 빌드 시 "Route ... is using runtime information"라는 경고가 나타나면 Request 객체 접근이 원인입니다. - 조치: Request 객체 속성 읽기를 제거하거나, 필요한 값을 환경 변수나 설정 파일로 옮깁니다. URL 파라미터가 필요하다면 동적 세그먼트(
[id])를 사용하고params로 받습니다.
4. fetch 캐시 옵션
- 확인 대상: 핸들러 내부에서 호출하는
fetch()요청에cache: 'no-store'또는next: { revalidate: 0 }옵션이 설정되어 있는지 확인합니다. - 확인 방법: 먼저 핸들러 함수 안에서
fetch(키워드를 검색한 뒤, 두 번째 인자 객체의cache와next속성을 확인합니다. - 실패 신호: fetch 옵션에
cache: 'no-store'가 있거나revalidate: 0이 설정되어 있으면 해당 요청이 매번 실행되어 전체 핸들러가 동적으로 전환됩니다. - 조치: 캐시해도 되는 데이터만
cache: 'force-cache'로 명시합니다. 주기적 갱신이 필요하면 검증한 갱신 주기를next.revalidate에 지정하고, 최신성이 필수인 데이터라면 Route Handler 자체를 동적으로 유지합니다.
5. 빌드·실행 환경 구분
- 확인 대상: 개발 서버가 아니라 실제 프로덕션 빌드에서 캐시 동작을 확인했는지 점검합니다.
- 확인 방법:
next build후next start로 실행하고 같은 GET 요청을 반복해 서버 로그와 응답을 비교합니다. 개발 모드 결과만으로 정적 캐시 여부를 결론 내리지 않습니다. - 실패 신호: 개발 서버에서 관찰한 실행 횟수만 근거로 캐시가 실패했다고 판단하거나, 빌드 결과에서 해당 Route Handler가 정적 대상으로 처리되지 않습니다.
- 조치: 프로덕션 빌드 로그와 실행 로그를 기준으로 다시 판단합니다. 환경 변수 접근 자체를 캐시 실패 원인으로 단정하지 말고 요청별 API 사용 여부를 별도로 확인합니다.
6. 응답 헤더 설정
- 확인 대상:
Response객체를 생성할 때Cache-Control헤더를 명시적으로no-cache또는no-store로 설정했는지 확인합니다. - 확인 방법: 먼저 핸들러 return 문에서
new Response()또는NextResponse.json()호출을 찾은 뒤, 두 번째 인자의headers객체에서Cache-Control키를 검색합니다. - 실패 신호: Next.js의 서버 측 정적 처리 여부와 브라우저·CDN의
Cache-Control결과를 같은 캐시로 간주해 원인을 잘못 판단합니다. - 조치: 먼저 Route Handler가 정적으로 생성됐는지 확인한 뒤, 별도로 브라우저·CDN 캐시 정책을 설계합니다. 검증하지 않은
max-age값을 임의로 추가하지 않습니다.
7. Next.js 버전 확인
- 확인 대상: 프로젝트의 Next.js 버전이 15 이상인지, 그리고 Route Handler 캐시 동작 변경 사항을 반영했는지 확인합니다.
- 확인 방법: 먼저
package.json에서"next"의존성 버전을 확인한 뒤, Next.js 15 업그레이드 가이드에서 Route Handler 기본 동작 변경 내용을 대조합니다. - 실패 신호: Next.js 15부터 GET/HEAD Route Handler가 기본적으로 캐시되지 않도록 변경되었으므로, 명시적으로
force-static을 설정하지 않으면 캐시가 작동하지 않습니다. - 조치: 캐시가 필요한 GET 핸들러에만
force-static을 적용합니다. POST·PUT·PATCH·DELETE 같은 다른 메서드는 캐시되지 않으며, 요청별 데이터가 필요한 GET은 동적으로 유지합니다.
점검 순서
- dynamic 설정 철자 확인: 가장 먼저
export const dynamic = 'force-static'구문의 위치와 철자를 점검합니다. 오타나 위치 오류는 즉시 수정 가능하며, 이후 단계에서 발생하는 혼란을 방지합니다. - 동적 함수 제거:
cookies(),headers()와 요청 객체 접근 호출을 검색하고 제거합니다. 이 함수들은 Next.js가 자동으로 동적 렌더링을 트리거하는 주요 원인입니다. - Request 객체 접근 제거: 핸들러 매개변수로 받은
request객체 속성을 읽는 코드를 찾아 제거하거나 대체합니다. - fetch 캐시 옵션 조정: 내부 fetch 호출의
cache옵션을force-cache로 변경하거나revalidate값을 설정합니다. - 환경 변수 빌드 시점 확정: 런타임 환경 변수를 빌드 시점 변수로 전환하거나 재빌드 워크플로를 구성합니다.
- 응답 헤더 점검:
Cache-Control헤더를 제거하거나 캐시 허용 값으로 변경합니다. - Next.js 버전 및 설정 확인: 마지막으로 버전별 기본 동작 변경 사항을 확인하고 필요한 마이그레이션을 수행합니다.
자주 놓치는 포인트
조건부 동적 함수 호출
if 문 안에서만 cookies()를 호출해도 Next.js는 파일 전체를 동적으로 간주합니다. 조건과 무관하게 함수 존재 자체가 동적 전환을 유발하므로, 사용하지 않는 경로에서도 완전히 제거해야 합니다.
외부 라이브러리의 숨겨진 Request 접근
ORM이나 인증 라이브러리가 내부적으로 Request 객체나 쿠키를 읽을 수 있습니다. 빌드 로그에서 "Dynamic server usage" 경고가 나타나는데 코드에서 직접 호출이 보이지 않는다면, 라이브러리 초기화 코드를 점검하거나 해당 라이브러리를 정적 설정으로 대체합니다.
개발 환경과 프로덕션 빌드 차이
next dev 개발 서버는 캐시 동작을 완전히 재현하지 않습니다. force-static 설정이 올바르게 작동하는지 확인하려면 next build && next start로 프로덕션 빌드를 생성한 뒤 테스트해야 합니다.
revalidate와 force-static 충돌
export const revalidate = 60과 export const dynamic = 'force-static'을 함께 사용하면 Next.js가 정적 생성 후 주기적으로 재검증합니다. 하지만 핸들러 내부에 동적 함수가 남아 있으면 revalidate 설정이 무시되므로, 먼저 모든 동적 요소를 제거한 뒤 revalidate를 설정해야 합니다.
재사용 체크리스트
아래 체크리스트를 복사하여 각 Route Handler 파일마다 점검에 활용할 수 있습니다.
- [ ]
export const dynamic = 'force-static'구문이 파일 최상단에 정확히 존재하는가? - [ ] 핸들러 내부에서
cookies(),headers()와 요청 객체 접근를 호출하지 않는가? - [ ]
request매개변수의 속성(request.url,request.headers등)을 읽지 않는가? - [ ] 모든
fetch()호출에cache: 'force-cache'또는next: { revalidate: [초] }가 설정되어 있는가? - [ ] 프로덕션 빌드와 실행 환경에서 반복 요청으로 확인했는가?
- [ ] 응답 헤더에
Cache-Control: no-store같은 캐시 무효화 값이 없는가? - [ ] Next.js 버전이 15 이상이라면 업그레이드 가이드의 Route Handler 변경 사항을 반영했는가?
- [ ]
next build && next start로 프로덕션 빌드를 생성하고 실제 캐시 동작을 확인했는가?
각 항목을 순서대로 확인하고, 실패 신호가 나타나면 즉시 조치합니다. 모든 항목을 통과해도 캐시가 작동하지 않는다면 빌드 로그와 런타임 로그를 함께 검토하여 외부 요인을 찾아야 합니다.
