Next.js Draft Mode 가이드 정리
1. Draft Mode 개요
Draft Mode는 Headless CMS에서 아직 발행되지 않은 콘텐츠(초안)를 Next.js 애플리케이션에서 미리보기 할 수 있도록 도와주는 기능입니다.
- 평소에는 정적 렌더링(SSG)을 사용하더라도, 특정 요청에 한해서만 동적 렌더링으로 전환하여 초안 데이터를 가져올 수 있습니다.
- Draft Mode가 활성화되면 Next.js가 **특정 쿠키(
__prerender_bypass)**를 기반으로 해당 요청을 빌드 타임이 아닌 요청 시점에 렌더링합니다.
구성은 크게 세 단계입니다.
- Draft Mode를 활성화하는 Route Handler 만들기
- Headless CMS에서 이 Route Handler를 안전하게 호출하도록 설정하기
- 페이지에서
draftMode().isEnabled값을 확인해 초안/발행본을 나누어 가져오기
2. Step 1: Route Handler 만들기
먼저 Draft Mode를 켜는 API 엔드포인트를 생성합니다. 이름은 자유지만 예시로 app/api/draft/route.ts를 사용합니다.
// app/api/draft/route.ts
export async function GET(request: Request) {
return new Response('')
}
이제 Next.js에서 제공하는 draftMode 함수를 import하고, enable()을 호출합니다.
// app/api/draft/route.ts
import { draftMode } from 'next/headers'
export async function GET(request: Request) {
const draft = await draftMode()
draft.enable()
return new Response('Draft mode is enabled')
}
이 핸들러를 호출하면 Next.js가 응답에 쿠키를 추가해 Draft Mode를 활성화합니다.
- 브라우저에서
/api/draft로 직접 접속하고, 개발자 도구의 Network 탭에서 응답 헤더를 확인하면Set-Cookie헤더에__prerender_bypass쿠키가 설정되어 있는 것을 볼 수 있습니다.
- 이 쿠키가 포함된 이후 요청들은 Draft Mode가 활성화된 상태가 됩니다.
3. Step 2: Headless CMS에서 Route Handler 접근
대부분의 Headless CMS는 커스텀 미리보기 URL을 설정하는 기능을 제공합니다. 이를 활용하여 Draft Mode를 보안적으로 활성화합니다.
- 비밀 토큰(secret) 생성
- 임의의 토큰 문자열을 하나 만들고, Next.js 앱과 CMS만 알고 있도록 관리합니다.
- CMS에 미리보기 URL 등록 (예시)
https://<your-site>/api/draft?secret=<token>&slug=<path>
<your-site>: 배포된 사이트 도메인<token>: 방금 생성한 secret 토큰<path>: 미리 보고 싶은 페이지 경로- 예: 초안 페이지가
/posts/one이라면&slug=/posts/one - CMS에서 slug 변수를 활용해 동적으로 구성할 수도 있습니다.
- 예:
&slug=/posts/{entry.fields.slug}
- 예:
- 예: 초안 페이지가
- Route Handler 내부에서
secret과slug를 검증하고, 유효할 때만 Draft Mode를 활성화한 뒤 해당 페이지로 리다이렉트합니다.
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
export async function GET(request: Request) {
// 쿼리 파라미터 파싱
const { searchParams } = new URL(request.url)
const secret = searchParams.get('secret')
const slug = searchParams.get('slug')
// secret 및 slug 검증
// 이 secret은 이 Route Handler와 CMS만 알고 있어야 합니다.
if (secret !== 'MY_SECRET_TOKEN' || !slug) {
return new Response('Invalid token', { status: 401 })
}
// CMS에 실제로 해당 slug를 가진 콘텐츠가 존재하는지 확인
const post = await getPostBySlug(slug)
// 잘못된 slug라면 Draft Mode를 켜지 않음
if (!post) {
return new Response('Invalid slug', { status: 401 })
}
// Draft Mode 활성화 (쿠키 설정)
const draft = await draftMode()
draft.enable()
// 안전한 slug로 리다이렉트
// 사용자가 전달한 slug 문자열을 그대로 쓰지 않고,
// CMS에서 검증한 post.slug를 사용해 Open Redirect 취약성을 방지합니다.
redirect(post.slug)
}
위 코드가 성공적으로 실행되면:
- 브라우저는
post.slug경로로 리다이렉트됩니다. - 요청에는 Draft Mode 쿠키가 포함되어 있으므로, 해당 페이지는 초안 콘텐츠를 기준으로 렌더링됩니다.
4. Step 3: Draft 콘텐츠 미리보기
이제 페이지 코드에서 Draft Mode 활성 여부에 따라 다른 데이터 소스를 선택해야 합니다.
// app/page.tsx
// 데이터를 가져오는 페이지 예시
import { draftMode } from 'next/headers'
async function getData() {
const { isEnabled } = await draftMode()
const url = isEnabled
? 'https://draft.example.com' // 초안 API 엔드포인트
: 'https://production.example.com' // 발행본 API 엔드포인트
const res = await fetch(url)
return res.json()
}
export default async function Page() {
const { title, desc } = await getData()
return (
<main>
<h1>{title}</h1>
<p>{desc}</p>
</main>
)
}
동작 흐름:
- CMS에서 설정한 미리보기 버튼을 클릭하면
/api/draft?secret=...&slug=...가 호출됩니다. - Route Handler가 secret·slug를 검증하고, Draft Mode 쿠키를 설정합니다.
- 브라우저가
slug경로로 리다이렉트됩니다. - 해당 페이지는
draftMode().isEnabled값을 확인해, 초안용 API 엔드포인트에서 데이터를 가져옵니다. - CMS에서 초안을 수정·저장하면, 발행하지 않아도 새로고침만으로 변경된 초안 내용을 확인할 수 있습니다.
5. 정리 및 활용 팁
- Draft Mode는 전체 사이트를 동적으로 만드는 것이 아니라, 특정 요청에서만 SSG → 동적 렌더링으로 전환하는 기능입니다.
- 실제 서비스에서는 다음과 같이 활용할 수 있습니다.
- 운영 사이트는 그대로 SSG/ISR로 빠르게 서비스
- 마케터·에디터는 CMS에서 초안을 저장 후, 미리보기 URL로 실제 레이아웃에서 검수
- 초안이 완성되면 CMS에서 발행(Publish)하여 정식 반영
- 보안을 위해:
- secret 토큰은 환경 변수로 관리하는 것이 좋습니다.
- slug 검증 시 CMS에서 실제 존재 여부를 체크해야 합니다.
- 임의의 redirect 경로를 허용하지 않도록 반드시 검증된 slug만 사용합니다.