Dynamic server usage, cookies, 정적 생성 | 빌드만 쿠키에서 멈추면 뭐부터 볼까?
Dynamic server usage는 정적 생성 중 cookies 호출이 넥스트의 동적 전환 밖으로 나가 빌드가 멈출 때 나는 메시지입니다. setTimeout 안이나 await하지 않은 프로미스 뒤에서 읽지 말고, 같은 호출 스택에서 await cookies 하세요. next dev가 조용해도 next build는 멈춥니다. Next.js, 쿠키, 정적 생성, 한국 1인 개발. 2026년 10월 문서.
Dynamic server usage는 쿠키 값이 잘못된 게 아니라, 정적 페이지를 만드는 동안 쿠키 호출이 넥스트가 잡아야 할 동적 전환 신호 밖으로 나가 빌드가 멈춘 메시지입니다. 쿠키를 지우기 전에, 같은 호출 스택에서 기다린 뒤 읽는지부터 보세요.
프로덕션 빌드가 정적 페이지를 세다가 쿠키를 썼다는 이유로 멈추는 일이 있습니다. 로컬 개발 서버는 요청마다 그려서 같은 코드가 조용할 수 있습니다. 프리뷰에서만 빨간 로그가 남습니다.
문서는 타이머 안이나 기다리지 않은 약속 뒤에서 쿠키를 부르면 빌드가 실패한다고 적습니다. 근거는 동적 서버 오류 문서입니다. 리다이렉트가 던지는 예외와는 이름이 다릅니다.
이 한 줄은 정적 생성이 신호를 놓친 자리
정적 생성 중에 동적 함수를 만나면 넥스트는 DynamicServerError를 던지고, 그 예외를 잡아 그 페이지를 요청 때 그리기로 바꿉니다. 신호가 잡히면 빌드는 통과하고 그 경로는 동적입니다.
신호가 잡히지 않으면 빌드 로그에 Dynamic server usage가 남습니다. 공식 문서는 비동기 맥락(Async Context)에 묶인 cookies나 headers를, 그 맥락이 없는 호출 스택에서 읽었을 때 이 메시지가 난다고 설명합니다. 대표가 setTimeout과 setInterval입니다.
넥스트 15부터 cookies()는 프로미스입니다. await cookies()로 받은 뒤에 get을 부릅니다. 동기 호출로 쓰던 14 코드를 그대로 두면 타입과 런타임이 먼저 어긋납니다. 그 다음이 이 빌드 메시지입니다.
먼저 볼 로그 | 경로 이름과 cookies 또는 headers가 같이 있으면, 그 파일의 호출 위치를 엽니다. 쿠키 이름을 바꾸기 전에 호출이 렌더 함수의 await 줄에 있는지 확인하세요.
개발 서버는 조용해도 정적 페이지를 세는 빌드에서만 이 줄이 납니다
빌드가 멈추는 호출 세 가지
공식 문서가 적은 원인과, 그 예외를 먼저 삼키는 자리를 한 표로 나눕니다. 세 자리 모두 쿠키 이름과는 무관합니다.
자리
왜 빌드가 멈추나
먼저 고칠 것
setTimeout, setInterval
콜백은 렌더의 비동기 맥락 밖
cookies를 타이머 밖에서 await
await 없는 프로미스
끝난 뒤에 새 실행 맥락에서 읽음
동적 함수를 부르는 프로미스를 await
try가 예외를 삼킴
넥스트가 잡아야 할 신호를 내 코드가 먼저 잡음
cookies를 try 밖으로
세 번째 줄은 리다이렉트와 헷갈리기 쉽습니다. redirect()도 값을 돌려주지 않고 예외를 던지므로 try 안이면 이동이 삼켜집니다. 여기 메시지는 그 예외가 아닙니다. 정적 생성이 던진 DynamicServerError를 try가 먹은 자리입니다. 화면 이동이 안 되는 로그와 빌드가 쿠키에서 멈추는 로그는 고치는 칸이 다릅니다.
타이머 안에서 쿠키를 읽으면 빌드가 멈춘다
import { cookies } from 'next/headers'
async function getCookieData() {
return new Promise((resolve) =>
setTimeout(async () => {
const cookieStore = await cookies()
resolve(cookieStore.getAll())
}, 1000)
)
}
고치는 순서는 호출을 렌더로 올리는 것
쿠키는 타이머 밖에서 읽고, 타이머에는 이미 꺼낸 값만 넘깁니다. 공식 예제도 이 순서입니다.
try로 감싼 세션 조회라면 await cookies()를 try 위로 올립니다. 없는 쿠키는 get이 빈 값을 주니, 그 빈 값만 분기하면 됩니다. 동적 전환 신호까지 삼키는 catch는 빼는 편이 맞습니다.
export const dynamic = 'force-dynamic'은 그 경로를 항상 요청 때 그리게 합니다. 호출이 이미 올바른데 정적 생성을 끄고 싶을 때 씁니다. 타이머 안의 cookies를 올바른 스택으로 바꿔 주지는 않습니다. 루트 레이아웃에서 쿠키를 읽으면 그 레이아웃을 쓰는 경로가 함께 동적이 되어, 빌드 로그의 정적 페이지 수가 줄어듭니다. 세션이 필요한 자식 경로로 호출을 내리는 편이 정적 페이지를 남깁니다.
쿠키를 먼저 읽고 타이머에는 값만 넘긴다
import { cookies } from 'next/headers'
async function getCookieData() {
const cookieStore = await cookies()
const cookieData = cookieStore.getAll()
return new Promise((resolve) =>
setTimeout(() => {
resolve(cookieData)
}, 1000)
)
}
빌드가 잠기는 자리는 쿠키 이름이 아니라 호출이 선 스택입니다
정적에서 동적으로 바뀌었다는 다른 문서
넥스트에는 제목이 비슷한 문서가 하나 더 있습니다. app/ Static to Dynamic Error는 빌드 때 정적으로 만든 경로가, 런타임에 폴백이나 재검증 자리에서 cookies나 headers를 쓰려 할 때 납니다. 빌드가 정적 페이지를 세다 멈추는 Dynamic server usage와 문장이 다릅니다.
그 문서는 동적 값을 조건부로 쓰지 말 것과, dynamic 설정을 force-static 또는 force-dynamic으로 고정할 것을 적습니다. force-static은 동적 값이 있어도 정적 처리를 강제하므로, 세션 쿠키가 필요한 페이지에는 맞지 않습니다. 로그 문장을 먼저 가른 뒤에 설정을 고르세요.
하이드레이션 미스매치는 서버 HTML과 브라우저 첫 화면이 다를 때입니다. 엣지 런타임이 노드 모듈을 거절하는 줄도 아닙니다. 빌드 로그에 used cookies가 있으면 이 칸만 보면 됩니다.
로그
의미
손댈 곳
Dynamic server usage, used cookies
정적 생성 신호가 밖으로 나감
await 스택, try 밖
Static to Dynamic Error
정적 경로가 런타임에 동적 값을 읽음
조건부 호출, dynamic 설정
redirect가 try에 남음
이동 예외를 먹음
redirect는 try 밖
프리뷰 빌드 전에 볼 다섯 줄
로컬 next dev 초록만 보고 프리뷰에 올리면 이 줄이 늦게 보입니다. 한국에서 프리뷰 도메인으로 확인하는 1인 배포라면, 푸시 전에 next build를 한 번 돌리는 편이 로그를 빨리 만납니다.
한국에서 혼자 넥스트를 버셀 프리뷰에 올리면, 낮에 로컬은 조용하다가 저녁 배포의 정적 생성에서만 쿠키 때문에 빌드가 멈춥니다. 카카오 로그인 주소를 읽으려고 레이아웃에서 쿠키를 열면 그 아래 경로가 통째로 동적 페이지가 됩니다. 개발 서버 화면이 나와도 빌드 로그의 그 문장이 사라지기 전에는 끝난 것이 아닙니다. 타이머 안으로 쿠키 읽기를 옮긴 코드는 로컬에서 값이 보여도 프리뷰 생성 단계가 신호를 놓칩니다.
자주 묻는 질문
개발 서버는 되는데 빌드만 쿠키에서 멈추면 어디를 보나요?
next dev는 요청마다 그려서 정적 생성 신호를 검사하지 않습니다. next build가 그 페이지를 정적으로 세다가, cookies 호출이 setTimeout이나 await 없는 프로미스, 또는 try 안에 있으면 Dynamic server usage로 멈춥니다. 호출을 렌더의 await 줄로 올리세요.
force-dynamic을 붙이면 끝나나요?
그 경로를 항상 요청 때 그리게 할 뿐입니다. 타이머 안에서 cookies를 부르는 스택은 그대로입니다. 호출 위치를 먼저 고치고, 정적 생성을 끌 이유가 따로 있을 때만 설정을 추가하세요.
try 안에서 세션이 없으면 어떻게 나누나요?
await cookies는 try 밖에서 호출합니다. get이 돌려준 값이 비었는지만 분기하면 됩니다. catch가 DynamicServerError까지 먹으면 넥스트가 동적 렌더로 바꾸지 못하고 빌드가 멈춥니다.
루트 레이아웃에서 쿠키를 읽으면 왜 정적 페이지가 줄나요?
레이아웃의 동적 함수는 그 레이아웃을 쓰는 경로의 렌더에 포함됩니다. 세션이 필요한 자식 페이지로 호출을 내리면, 쿠키가 없는 경로는 정적 생성으로 남을 수 있습니다.
Static to Dynamic Error와 같은 문제인가요?
아닙니다. 그 문서는 이미 정적으로 만든 경로가 런타임에 동적 값을 읽는 경우입니다. Dynamic server usage는 정적 페이지를 만드는 도중에 신호가 밖으로 나간 빌드 로그입니다. 문장을 먼저 대조하세요.
넥스트 14처럼 cookies를 await 없이 써도 되나요?
15부터는 프로미스입니다. await cookies() 다음에 get을 부르세요. 14의 동기 호출을 그대로 두면 이 빌드 메시지를 보기 전에 타입이 먼저 어긋납니다.
Dynamic server usage는 정적 생성 중 cookies 호출이 넥스트의 전환 신호 밖에 있다는 뜻입니다. 타이머와 await 없는 프로미스, try 안을 먼저 빼고, 로그가 Static to Dynamic Error인지 한 번 더 읽으면 프리뷰 빌드가 그 줄에서 멈춘 이유를 가를 수 있습니다. 관련 글: 하이드레이션 미스매치, 엣지 런타임, CORS 프리플라이트.
Dynamic server usagecookies정적 생성Next.js빌드쿠키서버 컴포넌트프론트엔드