Draft Mode

Pages 문서Data Fetching 문서에서 getStaticPropsgetStaticPaths를 사용하여 빌드 타임에 페이지를 사전 렌더링(정적 생성)하는 방법에 대해 이야기했습니다.

정적 생성은 페이지가 헤드리스 CMS에서 데이터를 가져올 때 유용합니다. 하지만 헤드리스 CMS에서 초안을 작성하고 페이지에서 바로 보는 것을 원할 때는 이상적이지 않습니다. 이러한 경우 Next.js가 이 페이지들을 빌드 타임이 아닌 요청 타임에 렌더링하고 게시된 컨텐츠 대신 초안 컨텐츠를 가져오도록 하고 싶을 것입니다. 이와 같이 Next.js가 특정 경우에 정적 생성을 우회하기를 원할 수 있습니다.

Next.js에는 이러한 문제를 해결하는 Draft Mode라는 기능을 제공합니다. 사용 방법은 다음과 같습니다.

Step 1: Create and access the API route

Next.js API Routes를 잘 모르는 경우 API Routes 문서을 먼저 참조하십시오.

먼저 초안 API route를 만듭니다. 이름은 자유롭게 설정할 수 있습니다 - 예: pages/api/draft.js

이 API route에서 응답 객체에 setDraftMode를 호출해야 합니다.

export default function handler(req, res) {
  // ...
  res.setDraftMode({ enable: true })
  // ...
}

이렇게 하면 draft 모드를 활성화하는 쿠키가 설정됩니다. 이러한 쿠키를 포함한 후속 요청은 Draft Mode를 트리거하여 정적으로 생성된 페이지의 동작을 변경합니다(이후에 더 설명함).

브라우저에서 직접 액세스하여 수동으로 테스트할 수 있도록 다음과 같은 API route를 만들고 액세스해보세요:

pages/api/draft.ts
// 브라우저에서 수동으로 테스트하기 위한 간단한 예제입니다.
export default function handler(req, res) {
  res.setDraftMode({ enable: true })
  res.end('Draft mode is enabled')
}

브라우저의 개발자 도구를 열고 /api/draft를 확인하면 __prerender_bypass라는 이름의 쿠키가 포함된 Set-Cookie 응답 헤더를 확인할 수 있습니다.

Securely accessing it from your Headless CMS

실제로는 헤드리스 CMS에서 이 라우트 핸들러를 보안 방식으로 호출하고 싶을 것입니다. 사용 중인 헤드리스 CMS에 따라 구체적인 단계는 다를 수 있지만, 다음과 같은 일반적인 단계를 따를 수 있습니다.

이 단계는 사용 중인 헤드리스 CMS가 사용자 정의 초안 URL 설정을 지원한다고 가정합니다. 지원하지 않는 경우에도 이 방법을 사용하여 초안 URL을 보호할 수 있지만, 초안 URL을 수동으로 구성하고 접근해야 합니다.

먼저, 원하는 토큰 생성기를 사용하여 시크릿 토큰 문자열을 생성해야 합니다. 이 시크릿은 Next.js 앱과 헤드리스 CMS만 알고 있어야 합니다. 이 시크릿은 CMS에 액세스할 수 없는 사람들이 초안 URL에 접근하는 것을 방지합니다.

두 번째, 헤드리스 CMS가 사용자 정의 초안 URL 설정을 지원하는 경우, 다음을 초안 URL로 지정합니다. 이는 초안 API route가 pages/api/draft.js에 위치한다고 가정합니다.

Terminal
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}

마지막으로, 초안 API route에서:

  • 시크릿이 일치하고 slug 매개변수가 존재하는지 확인합니다(존재하지 않으면 요청이 실패해야 합니다).
  • res.setDraftMode를 호출합니다.
  • 그런 다음 브라우저를 slug로 지정된 경로로 리디렉션합니다(다음 예제에서는 307 redirect (opens in a new tab)을 사용합니다).
export default async (req, res) => {
  // 시크릿과 다음 매개변수를 확인합니다.
  // 이 시크릿은 이 API route와 CMS만 알고 있어야 합니다.
  if (req.query.secret !== 'MY_SECRET_TOKEN' || !req.query.slug) {
    return res.status(401).json({ message: 'Invalid token' })
  }
 
  // 제공된 `slug`가 존재하는지 확인하기 위해 헤드리스 CMS를 가져옵니다.
  // getPostBySlug는 헤드리스 CMS에 필요한 fetching 로직을 구현합니다.
  const post = await getPostBySlug(req.query.slug)
 
  // slug가 존재하지 않으면 Draft Mode가 활성화되지 않도록 합니다.
  if (!post) {
    return res.status(401).json({ message: 'Invalid slug' })
  }
 
  // 쿠키를 설정하여 Draft Mode를 활성화합니다.
  res.setDraftMode({ enable: true })
 
  // 가져온 게시물의 경로로 리디렉션합니다.
  // req.query.slug로 리디렉션하지 않는 이유는 open redirect 취약점이 발생할 수 있기 때문입니다.
  res.redirect(post.slug)
}

성공하면 브라우저는 설정된 draft mode 쿠키와 함께 보고자 하는 경로로 리디렉션됩니다.

Step 2: Update getStaticProps

다음 단계는 getStaticProps를 업데이트하여 draft mode를 지원하는 것입니다.

res.setDraftMode를 통해 쿠키가 설정된 페이지를 요청하면 getStaticProps빌드 타임이 아닌 요청 타임에 호출됩니다.

또한, context.draftModetruecontext 객체와 함께 호출됩니다.

export async function getStaticProps(context) {
  if (context.draftMode) {
    // 동적 데이터
  }
}

우리는 초안 API route에서 res.setDraftMode를 사용했으므로 context.draftModetrue가 됩니다.

getStaticPaths를 사용하는 경우, context.params도 사용할 수 있습니다.

Fetch draft data

getStaticProps를 업데이트하여 context.draftMode에 따라 다른 데이터를 가져올 수 있습니다.

예를 들어, 헤드리스 CMS가 초안 게시물에 대해 다른 API 엔드포인트를 가지고 있는 경우, 아래와 같이 API 엔드포인트 URL을 수정할 수 있습니다:

export async function getStaticProps(context) {
  const url = context.draftMode
    ? 'https://draft.example.com'
    : 'https://production.example.com'
  const res = await fetch(url)
  // ...
}

이제 초안 API route(시크릿slug 포함)를 헤드리스 CMS 또는 수동으로 접근하면 초안 컨텐츠를 볼 수 있어야 합니다. 초안을 게시하지 않고 업데이트하더라도 초안을 볼 수 있어야 합니다.

헤드리스 CMS에 이를 초안 URL로 설정하거나 수동으로 접근하면 초안을 볼 수 있습니다.

Terminal
https://<your-site>/api/draft?secret=<token>&slug=<path>

More Details

Clear the Draft Mode cookie

기본적으로 초안 모드 세션은 브라우저를 닫으면 종료됩니다.

초안 모드 쿠키를 수동으로 지우려면 setDraftMode({ enable: false })를 호출하는 API route를 만듭니다:

pages/api/disable-draft.ts
export default function handler(req, res) {
  res.setDraftMode({ enable: false })
}

그런 다음 /api/disable-draft에 요청을 보내 API Route를 호출합니다. next/link를 사용하여 이 라우트를 호출하는 경우, 쿠키가 prefech 중에 실수로 삭제되지 않도록 prefetch={false}를 전달해야 합니다.

Works with getServerSideProps

Draft mode는 getServerSideProps와 함께 작동하며, context 객체에 draftMode 키로 사용할 수 있습니다.

참고: Draft Mode를 사용할 때 Cache-Control 헤더를 설정하면 우회할 수 없으므로 설정하지 않아야 합니다. 대신, ISR을 사용하는 것을 추천합니다.

Works with API Routes

API Routes는 요청 객체에서 draftMode에 접근할 수 있습니다. 예를 들어:

export default function myApiRoute(req, res) {
  if (req.draftMode) {
    // 초안 데이터를 가져옵니다.
  }
}

Unique per next build

next build를 실행할 때마다 새로운 우회 쿠키 값이 생성됩니다. 이를 통해서 우회 쿠키를 추측할 수 없도록 합니다.

참고: 로컬에서 HTTP를 통해 Draft Mode를 테스트하려면 브라우저에서 서드 파티 쿠키와 로컬 스토리지 액세스를 허용해야 합니다.