Dynamic server usage, cookies, 정적 생성 | 빌드만 쿠키에서 멈추면?
Dynamic server usage는 쿠키 API가 고장난 게 아니라 정적 생성 중 cookies 호출이 요청 흐름 밖으로 새어 빌드가 멈춘 줄입니다. try로 감싼 쿠키, setTimeout, 루트 레이아웃을 나눕니다. Next.js, 한국 1인 개발자 기준. 2026년 9월 dynamic server error 문서.
빌드 로그의 Dynamic server usage는 쿠키 함수가 고장난 게 아니라, 정적 생성 도중에 cookies를 불렀는데 그 신호가 요청 흐름 밖으로 새어 빌드가 멈춘 줄입니다. 로컬 next dev는 매 요청이라 조용한데, next build의 정적 페이지 단계에서만 터지는 경우가 많습니다.
로그인 여부를 루트 레이아웃에서 읽고 try로 감싸면 이 줄이 나기 쉽습니다. 세션 쿠키 이름이 sid든 token이든, 읽는 위치가 레이아웃인지 페이지인지가 먼저예요.
넥스트는 페이지를 될 수 있으면 빌드 때 미리 그립니다. 그 과정에서 cookies()나 headers()를 만나면, 이 경로는 요청마다 달라진다는 신호로 DynamicServerError를 던졌다가 스스로 잡아 동적 렌더로 바꿉니다. 공식 문서는 이 신호가 같은 호출 스택에 묶여 있어야 한다고 적습니다. setTimeout 안이나, await하지 않은 비동기 뒤에서 쿠키를 읽으면 스택이 끊깁니다.
신호가 밖으로 새면 빌드가 이렇게 멈춥니다. Dynamic server usage: Route /dashboard couldn't be rendered statically because it used cookies. digest는 DYNAMIC_SERVER_USAGE인 경우가 많습니다. 개발 서버는 요청이 항상 살아 있어 같은 코드가 그냥 지나갑니다. 그래서 집에서는 되고 버셀 프리뷰 빌드에서만 실패하는 패턴이 나옵니다.
로그인 페이지가 아니라 블로그 글처럼 누구에게나 같은 화면인데도 이 줄이 뜨면, 그 글 파일이 쿠키를 읽을 필요가 없는데 레이아웃이 읽고 있는 경우가 많습니다. 국내 사이트에서 상단 로그인 이름을 루트 레이아웃이 매 페이지마다 쿠키로 확인하다 빌드가 여기서 멈추는 일이 흔합니다.
개발 서버는 지나가고, 정적 페이지를 만드는 빌드에서 쿠키 신호가 터진다. 자체 인포그래픽
신호가 새는 자리 세 곳
try, 타이머, 루트 레이아웃이 단골입니다.
공식 예제는 타임아웃 안에서 cookies()를 부르는 코드를 잘못된 예로 둡니다. 쿠키를 타임아웃 전에 읽고, 읽은 값만 나중에 쓰면 스택이 유지됩니다. 두 번째로 많이 삼켜지는 곳은 try/catch입니다. 넥스트가 동적 전환을 위해 던진 오류를 내 코드가 잡으면, 프레임워크는 그 신호를 못 보고 빌드 오류로 다시 올립니다. 쿠키 읽기는 try 밖에 두고, 그 다음 네트워크 호출만 try로 감싸세요.
코드 위치
빌드에서 생기는 일
수정
try 안의 cookies()
bailout 신호가 잡힘
쿠키 읽기를 try 밖으로
setTimeout 안의 cookies()
호출 스택이 끊김
먼저 읽고 값만 타이머로
await 빠진 프로미스 뒤
다른 실행 맥락
쿠키를 await 전에 읽기
루트 layout의 cookies()
모든 경로가 동적 후보
세션이 필요한 구간만
dynamic = force-static 과 쿠키
정적 강제와 충돌
강제 정적을 빼거나 쿠키를 제거
넥스트 15부터 cookies()는 비동기입니다. const cookieStore = await cookies()처럼 기다려야 하고, .get('sid')는 그 다음입니다. 예전처럼 동기 호출로 두면 타입이 어긋나거나 빌드가 다른 문장으로 실패합니다. 버튼 경계의 use client 오류와는 문장이 다릅니다. 훅이 막힌 화면은 use client 글에서 따로 봅니다.
대시보드처럼 사람마다 화면이 달라야 하면 쿠키를 읽는 것이 맞습니다. 그 경우 빌드가 그 경로를 정적 파일로 굳히지 않는 것이 정상입니다. 신호를 삼키지만 않으면 넥스트가 동적 렌더로 넘깁니다. 글 목록이나 공개 포스트는 쿠키 없이 정적 생성에 남겨 두세요. 상단에 로그인 이름이 필요하면 그 조각만 별도 서버 컴포넌트로 빼고, 본문 페이지가 직접 cookies()를 부르지 않게 합니다.
export const dynamic = 'force-static'을 쿠키를 읽는 페이지에 같이 두면 충돌합니다. 정적으로 굳히겠다는 선언과 요청마다 읽겠다는 호출이 한 파일에 있는 상태입니다. 세션이 필요하면 강제 정적 선언을 빼고, 공개 페이지면 쿠키 호출을 빼면 됩니다. 날짜나 난수처럼 쿠키는 아닌데 요청마다 달라져야 하는 값은 connection()을 먼저 기다린 뒤 계산합니다. Next.js 문서는 이 함수를 next/server에서 가져오며, 프리렌더를 요청 시각까지 미루는 용도라고 설명합니다.
[ ] 빌드 로그의 경로가 어느 페이지인지 적었다
[ ] 그 파일과 상위 layout에서 cookies 검색을 했다
[ ] cookies 호출이 try와 setTimeout 밖에 있다
[ ] await cookies()로 기다린다
[ ] 공개 페이지에는 쿠키 호출이 없다
공개 글은 정적으로 두고, 세션이 필요한 경로만 쿠키를 읽는다. 자체 인포그래픽
로컬은 되는데 빌드만 실패할 때
next dev가 아니라 next build로 재현하는 편이 맞습니다.
개발 모드는 페이지를 요청 때 그리므로 동적 전환 신호가 티가 안 납니다. 배포 전에 next build를 한 번 돌리고, Generating static pages 구간에서 어느 경로가 찍히는지 보세요. 경로가 /이면 루트 레이아웃이나 홈을, /blog/글슬러그이면 그 글과 공통 레이아웃을 의심하면 됩니다.
프리뷰 배포만 실패하고 로그에 같은 문장이 있으면 코드 문제가 맞습니다. 환경변수 키가 비어 클라이언트가 undefined인 증상과는 문장이 다릅니다. 그 경우는 NEXT_PUBLIC 글을 보면 됩니다. 서버 HTML과 브라우저 첫 화면의 글자가 다른 증상은 하이드레이션 글 쪽이고, 빌드가 쿠키 문장으로 멈추는 것과는 단계가 다릅니다.
한국 시간으로 배포를 저녁에 거는 1인 작업이라면, 푸시 전에 로컬 빌드를 한 번 보는 쪽이 프리뷰를 여러 번 돌리는 것보다 빠릅니다. 로그의 경로 한 줄이 고칠 파일을 거의 알려 줍니다.
로그에 경로가 여러 개면 공통 레이아웃을 먼저 여세요. 글마다 쿠키를 쓴 것이 아니라 상단 한 곳이 모든 페이지를 끌고 가는 경우가 많습니다. 그 한 곳을 헤더 조각으로 빼면 공개 글의 정적 생성은 돌아오고, 로그인 이름만 요청마다 그려집니다. 빌드를 다시 돌려 방금 그 경로가 목록에서 빠졌는지 확인하면 수정이 맞는지 바로 알 수 있습니다.
catch로 삼키지 말 것 | cookies()가 던지는 DynamicServerError는 실패가 아니라 동적 렌더로 가라는 신호인 경우가 있습니다. 그 호출을 try 안에 넣으면 신호가 사라지고 빌드 오류로 남습니다.
쿠키가 필요 없는데 요청 시각이 필요하면
세션이 없으면 cookies 대신 connection을 기다립니다.
화면에 지금 시각이나 난수가 필요하고 로그인 쿠키는 필요 없다면 cookies()를 부르지 마세요. await connection()이 프리렌더를 요청이 들어온 뒤로 미룹니다. 공식 설명은 2026년 6월 문서 기준으로, 쿠키나 헤더를 쓰지 않지만 요청마다 출력이 달라야 할 때 쓰는 함수입니다. 쿠키 값을 읽어야 하면 connection으로 대체되지 않습니다. 값이 들어 있는 쿠키를 읽어야 이름이 나오니까요.
반대로 공개 문서 페이지에 방문자 수를 매 요청마다 세고 싶지 않다면, 그 조회를 빌드 때 고정하거나 캐시하는 쪽이 이 오류를 피합니다. 동적 함수를 추가하는 것이 항상 해결은 아닙니다. 그 페이지가 정말 요청마다 달라야 하는지만 먼저 고르면 됩니다.
쿠키 없이 요청 시각만 필요할 때
import { connection } from 'next/server'
export default async function Page() {
await connection()
const stamped = new Date().toISOString()
return <p>{stamped}</p>
}
세션 값이 필요하면 cookies, 요청 시각만 필요하면 connection. 자체 인포그래픽
next dev는 페이지를 요청마다 그려서 정적 생성 단계가 없습니다. Dynamic server usage는 next build가 페이지를 미리 그리다가 쿠키를 만났을 때 드러납니다. 배포 전에 next build로 한 번 재현하는 편이 맞습니다.
로그인한 대시보드인데도 오류인가요?
쿠키를 읽는 것 자체가 오류는 아닙니다. 그 호출이 try나 setTimeout 안에 있어 신호가 삼켜지면 빌드가 실패로 바뀝니다. 쿠키를 먼저 await하고, 실패할 수 있는 네트워크 호출만 try에 넣으세요.
cookies()에 await를 안 붙이면요?
넥스트 15부터 cookies는 프로미스를 반환합니다. await 없이 get을 호출하면 쿠키 값이 아니라 프로미스 자체를 다루게 됩니다. const cookieStore = await cookies() 다음에 get을 부르면 됩니다.
루트 레이아웃에서 읽으면 안 되나요?
읽을 수는 있지만 그 아래 공개 페이지까지 동적 후보가 됩니다. 로그인 이름이 필요한 헤더 조각만 분리하고, 글 본문 페이지는 쿠키를 부르지 않게 두는 편이 정적 생성을 유지합니다.
force-static과 같이 쓰면요?
정적으로 굳히라는 설정과 요청 쿠키를 읽으라는 호출이 충돌합니다. 세션이 필요하면 force-static을 빼고, 공개 페이지면 cookies 호출을 빼면 됩니다. 둘을 한 파일에 두지 마세요.
connection으로 쿠키 검사를 대신할 수 있나요?
없습니다. connection은 요청이 들어올 때까지 그리기를 미룰 뿐 쿠키 값을 주지 않습니다. 세션 문자열이 필요하면 await cookies()로 읽어야 하고, 시각이나 난수뿐이면 connection 쪽이 맞습니다.
빌드가 쿠키에서 멈추면 어느 경로가 cookies를 읽고, 그 호출이 try 안에 있는지부터 보면 됩니다. 공개 페이지에서는 호출을 빼고, 세션 페이지에서는 await로 먼저 읽으면 동적 전환 신호가 빌드를 깨지 않습니다. 관련 글: use client, 하이드레이션, 환경변수.
Dynamic server usagecookies정적 생성next build레이아웃넥스트프론트엔드세션connection개발자