ReactNextCentral

드래프트 모드

Published on
Next.js에는 정적 및 동적 페이지 간에 전환할 수 있는 드래프트 모드가 있습니다. 여기에서 앱 라우터와 함께 작동하는 방식을 알아볼 수 있습니다.
Table of Contents

드래프트 모드

Heeadless CMS에서 데이터를 가져올 때 정적 렌더링은 유용합니다. 그러나 Heeadless CMS에서 드래프트를 작성하고 페이지에서 즉시 드래프트를 보고 싶을 때에는 이상적이지 않습니다. Next.js에 빌드 시간이 아닌 요청 시간에 이러한 페이지를 렌더링하도록 하고 게시된 콘텐츠 대신 드래프트 콘텐츠를 가져오도록 하고 싶습니다. 특정 경우에만 Next.js에 동적 렌더링으로 전환하도록 하고 싶습니다.

Next.js에는 이 문제를 해결하는 드래프트 모드라는 기능이 있습니다. 사용 방법에 대한 지침은 다음과 같습니다.


단계 1: 라우트 핸들러 생성 및 접근

먼저, 라우트 핸들러를 생성하세요. 임의의 이름을 가질 수 있습니다. 예: app/api/draft/route.ts

그런 다음, next/headers에서 draftMode를 가져와 enable() 메서드를 호출합니다.

app/api/draft/route.ts
// 드래프트 모드를 활성화하는 라우트 핸들러
import { draftMode } from 'next/headers'

export async function GET(request: Request) {
  draftMode().enable()
  return new Response('드래프트 모드가 활성화되었습니다')
}
JavaScript
app/api/draft/route.js
// 드래프트 모드를 활성화하는 라우트 핸들러
import { draftMode } from 'next/headers'

export async function GET(request) {
  draftMode().enable()
  return new Response('드래프트 모드가 활성화되었습니다')
}

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

이것을 수동으로 테스트하려면 /api/draft를 방문하고 브라우저의 개발자 도구를 확인하세요. Set-Cookie 응답 헤더에 __prerender_bypass라는 이름의 쿠키가 있음을 확인하세요.

Headless CMS에서 안전하게 접근하기

실제로, Headless CMS에서 이 라우트 핸들러를 안전하게 호출하고 싶을 것입니다. 특정 단계는 사용 중인 Headless CMS에 따라 다를 수 있지만, 다음은 취할 수 있는 일반적인 단계입니다.

이 단계들은 사용 중인 Headless CMS가 사용자 정의 드래프트 URL 설정을 지원한다고 가정합니다. 지원하지 않으면 이 방법을 사용하여 드래프트 URL을 보안할 수 있지만, 드래프트 URL을 수동으로 구성하고 접근해야 합니다.

  • 첫째, 선호하는 토큰 생성기를 사용하여 비밀 토큰 문자열을 생성해야 합니다. 이 비밀은 Next.js 앱과 Headless CMS만 알고 있어야 합니다. 이 비밀은 CMS에 액세스 권한이 없는 사람들이 드래프트 URL에 액세스하는 것을 방지합니다.
  • 둘째, Headless 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를 사용해야 합니다.

Headless CMS는 <path>를 CMS의 데이터를 기반으로 동적으로 설정할 수 있도록 드래프트 URL에 변수를 포함시킬 수도 있습니다. 예: &slug=/posts/{entry.fields.slug}

마지막으로, 라우트 핸들러에서 아래와 같이 동작합니다.

  • 비밀이 일치하고 slug 매개변수가 존재하는지 확인하세요(그렇지 않으면 요청이 실패해야 합니다).
  • 쿠키를 설정하기 위해 draftMode.enable()를 호출합니다.
  • 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('토큰이 유효하지 않습니다', { status: 401 })
  }

  // 제공된 `slug`가 존재하는지 확인하기 위해 Headless CMS를 가져옵니다
  // getPostBySlug는 Headless CMS로 필요한 가져오기 로직을 구현합니다
  const post = await getPostBySlug(slug)

  // 슬러그가 존재하지 않으면 드래프트 모드 활성화를 방지하세요
  if (!post) {
    return new Response('유효하지 않은 슬러그', { status: 401 })
  }

  // 쿠키를 설정하여 드래프트 모드 활성화
  draftMode().enable()

  // 가져온 포스트의 경로로 리다이렉트
  // open redirect 취약점으로 이어질 수 있기 때문에 
  // searchParams.slug로 리다이렉트하지 않습니다
  redirect(post.slug)
}

성공하면 브라우저는 드래프트 모드 쿠키와 함께 보려는 경로로 리다이렉트됩니다.


단계 2: 페이지 업데이트

다음 단계는 draftMode().isEnabled의 값을 확인하기 위해 페이지를 업데이트하는 것입니다.

쿠키가 설정된 페이지를 요청하면, 데이터는 요청 시간에 가져옵니다(빌드 시간 대신). 또한, isEnabled의 값은 true일 것입니다.

app/page.tsx
// 데이터를 가져오는 페이지
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>
  )
}
JavaScript
app/page.js
// 데이터를 가져오는 페이지
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>
  )
}

다 됐습니다! Headless CMS 또는 수동으로 드래프트 라우트 핸들러(secretslug 포함)에 접근하면 이제 드래프트 컨텐츠를 볼 수 있어야 합니다. 게시하지 않고 드래프트를 업데이트하면 드래프트를 볼 수 있어야 합니다.

이것을 Headless CMS에 드래프트 URL로 설정하거나 수동으로 접근하면 드래프트를 볼 수 있어야 합니다.

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

추가 정보

드래프트 모드 쿠키 삭제하기

기본적으로 브라우저가 닫힐 때 드래프트 모드 세션도 종료됩니다. 드래프트 모드 쿠키를 수동으로 지우려면 draftMode().disable()를 호출하는 Route Handler를 생성하세요.

app/api/disable-draft/route.ts
import { draftMode } from 'next/headers'

export async function GET(request: Request) {
  draftMode().disable()
  return new Response('Draft mode가 비활성화 되었습니다')
}
JavaScript
"app/api/disable-draft/route.js
import { draftMode } from 'next/headers'

export async function GET(request) {
  draftMode().disable()
  return new Response('Draft mode가 비활성화 되었습니다')
}

그런 다음, Route Handler를 호출하기 위해 /api/disable-draft로 요청을 보냅니다. 이 경로를 next/link를 사용하여 호출할 경우, prefetch로 쿠키를 실수로 삭제하지 않도록 prefetch={false}를 전달해야 합니다.

next build마다 고유함

next build를 실행할 때마다 새로운 bypass 쿠키 값이 생성됩니다. 이로 인해 bypass 쿠키가 추측될 수 없게 보장됩니다.

HTTP를 통해 로컬에서 드래프트 모드를 테스트하려면, 브라우저가 서드파티 쿠키와 로컬 스토리지 접근을 허용해야 합니다.