Draft Mode
정적 렌더링은 페이지가 헤드리스 CMS에서 데이터를 가져올 때 유용합니다. 그러나 헤드리스 CMS에서 초안을 작성하고 페이지에서 즉시 초안을 보고 싶을 때는 이상적이지 않습니다. 이러한 경우 Next.js가 이 페이지들을 빌드 타임이 아닌 요청 타임에 렌더링하고, 게시된 콘텐츠 대신 초안 콘텐츠를 가져오도록 하고 싶을 것입니다. Next.js가 이 특정 경우에만 동적 렌더링으로 전환되도록 하고 싶을 것입니다.
Next.js는 이 문제를 해결하는 Draft Mode라는 기능을 제공합니다. 사용 방법은 다음과 같습니다.
Step 1: Create and access the Route Handler
먼저, Route Handler를 생성합니다. 이름은 app/api/draft/route.ts
와 같이 임의로 지정할 수 있습니다.
그런 다음, next/headers
에서 draftMode
를 가져와 enable()
메서드를 호출합니다.
// Draft 모드를 활성화하는 라우트 핸들러
import { draftMode } from 'next/headers'
export async function GET(request: Request) {
draftMode().enable()
return new Response('Draft mode is enabled')
}
// Draft 모드를 활성화하는 라우트 핸들러
import { draftMode } from 'next/headers'
export async function GET(request) {
draftMode().enable()
return new Response('Draft mode is enabled')
}
이렇게 하면 Draft 모드를 활성화하는 쿠키가 설정됩니다. 이 쿠키를 포함하는 이후의 요청은 Draft Mode를 트리거하여 정적으로 생성된 페이지의 동작을 변경합니다(이후에 더 설명함).
브라우저의 개발자 도구에서 Set-Cookie
응답 헤더와 __prerender_bypass
라는 쿠키를 확인하여 /api/draft
를 방문하여 수동으로 테스트할 수 있습니다.
Securely accessing it from your Headless CMS
실제로는 헤드리스 CMS에서 이 라우트 핸들러를 보안 방식으로 호출하고 싶을 것입니다. 사용 중인 헤드리스 CMS에 따라 구체적인 단계는 다를 수 있지만, 다음과 같은 일반적인 단계를 따를 수 있습니다.
이 단계는 사용 중인 헤드리스 CMS가 사용자 정의 초안 URL을 설정할 수 있는 기능을 지원한다고 가정합니다. 그렇지 않다면 이 방법을 사용하여 초안 URL을 보호할 수 있지만, 초안 URL을 수동으로 구성하고 접근해야 합니다.
먼저, 원하는 토큰 생성기를 사용하여 시크릿 토큰 문자열을 생성해야 합니다. 이 시크릿은 Next.js 앱과 헤드리스 CMS만 알고 있어야 합니다. 이 시크릿은 CMS에 액세스할 수 없는 사람들이 초안 URL에 접근하는 것을 방지합니다.
두 번째로, 헤드리스 CMS가 사용자 정의 초안 URL을 설정하는 기능을 지원하는 경우, 다음을 초안 URL로 지정합니다. 이는 라우트 핸들러가 app/api/draft/route.ts
에 위치한다고 가정합니다.
https://<your-site>/api/draft?secret=<token>&slug=<path>
<your-site>
는 배포된 도메인이어야 합니다.<token>
은 생성한 시크릿 토큰으로 교체해야 합니다.<path>
는 보고자 하는 페이지의 경로여야 합니다./posts/foo
를 보고자 한다면&slug=/posts/foo
를 사용해야 합니다.
사용 중인 헤드리스 CMS는 변수 삽입을 지원할 수 있으며, 따라서 <path>
를 CMS의 데이터에 따라 동적으로 설정할 수 있습니다: &slug=/posts/{entry.fields.slug}
마지막으로, 라우트 핸들러에서:
- 시크릿이 일치하고
slug
매개변수가 존재하는지 확인합니다(없으면 요청이 실패해야 함). draftMode.enable()
을 호출하여 쿠키를 설정합니다.- 그런 다음 브라우저를
slug
로 지정된 경로로 리디렉션합니다.
// 시크릿과 slug를 사용하는 라우트 핸들러
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')
// 시크릿과 다음 매개변수 확인
// 이 시크릿은 이 라우트 핸들러와 CMS만 알고 있어야 합니다
if (secret !== 'MY_SECRET_TOKEN' || !slug) {
return new Response('Invalid token', { status: 401 })
}
// 제공된 `slug`가 존재하는지 확인하기 위해 헤드리스 CMS를 가져옵니다
// getPostBySlug는 헤드리스 CMS에 필요한 가져오기 로직을 구현합니다
const post = await getPostBySlug(slug)
// `slug`가 존재하지 않으면 Draft 모드가 활성화되지 않도록 합니다
if (!post) {
return new Response('Invalid slug', { status: 401 })
}
// Draft Mode를 활성화하여 쿠키 설정
draftMode().enable()
// 가져온 게시물의 경로로 리디렉션
// 검색 매개변수 slug로 리디렉션하지 않습니다. 이는 열린 리디렉션 취약성으로 이어질 수 있습니다
redirect(post.slug)
}
// 시크릿과 slug를 사용하는 라우트 핸들러
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
export async function GET(request) {
// 쿼리 문자열 매개변수 파싱
const { searchParams } = new URL(request.url)
const secret = searchParams.get('secret')
const slug = searchParams.get('slug')
// 시크릿과 다음 매개변수 확인
// 이 시크릿은 이 라우트 핸들러와 CMS만 알고 있어야 합니다
if (secret !== 'MY_SECRET_TOKEN' || !slug) {
return new Response('Invalid token', { status: 401 })
}
// 제공된 `slug`가 존재하는지 확인하기 위해 헤드리스 CMS를 가져옵니다
// getPostBySlug는 헤드리스 CMS에 필요한 가져오기 로직을 구현합니다
const post = await getPostBySlug(slug)
// `slug`가 존재하지 않으면 Draft 모드가 활성화되지 않도록 합니다
if (!post) {
return new Response('Invalid slug', { status: 401 })
}
// Draft Mode를 활성화하여 쿠키 설정
draftMode().enable()
// 가져온 게시물의 경로로 리디렉션
// 검색 매개변수 slug로 리디렉션하지 않습니다. 이는 열린 리디렉션 취약성으로 이어질 수 있습니다
redirect(post.slug)
}
성공하면 브라우저는 보고자 하는 경로로 리디렉션됩니다.
Step 2: Update page
다음 단계는 페이지에서 draftMode().isEnabled
값을 확인하도록 업데이트하는 것입니다.
쿠키가 설정된 페이지를 요청하면 빌드 타임이 아닌 요청 타임에 데이터가 가져와집니다.
또한, isEnabled
값은 true
가 됩니다.
// 데이터를 가져오는 페이지
import { draftMode } from 'next/headers'
async function getData() {
const { isEnabled } = draftMode()
const url = isEnabled
? 'https://draft.example.com'
: 'https://production.example.com'
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>
)
}
// 데이터를 가져오는 페이지
import { draftMode } from 'next/headers'
async function getData() {
const { isEnabled } = draftMode()
const url = isEnabled
? 'https://draft.example.com'
: 'https://production.example.com'
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 또는 수동으로 초안 라우트 핸들러(시크릿과 slug 포함)에 접근하면 초안 콘텐츠를 볼 수 있습니다. 초안을 게시하지 않고 업데이트하더라도 초안을 볼 수 있어야 합니다.
헤드리스 CMS에 이를 초안 URL로 설정하거나 수동으로 접근하면 초안을 볼 수 있습니다.
https://<your-site>/api/draft?secret=<token>&slug=<path>
More Details
Clear the Draft Mode cookie
기본적으로 Draft Mode 세션은 브라우저가 닫힐 때 종료됩니다.
Draft Mode 쿠키를 수동으로 지우려면 draftMode().disable()
을 호출하는 라우트 핸들러를 생성합니다:
import { draftMode } from 'next/headers'
export async function GET(request: Request) {
draftMode().disable()
return new Response('Draft mode is disabled')
}
import { draftMode } from 'next/headers'
export async function GET(request) {
draftMode().disable()
return new Response('Draft mode is disabled')
}
그런 다음 /api/disable-draft
에 요청을 보내 라우트 핸들러를 호출합니다. next/link
를 사용하여 이 경로를 호출하는 경우, 쿠키가 사전 로드 시 실수로 삭제되지 않도록 prefetch={false}
를 전달해야 합니다.
Unique per next build
매번 next build
를 실행할 때마다 새로운 우회 쿠키 값이 생성됩니다.
이는 우회 쿠키를 추측할 수 없도록 보장합니다.
참고: 로컬에서 HTTP를 통해 Draft Mode를 테스트하려면 브라우저에서 타사 쿠키 및 로컬 스토리지 액세스를 허용해야 합니다.