Lang x Lang

Draft Mode

静的レンダリングは、ページがヘッドレス CMS から fetch するときに便利です。しかし、ヘッドレス CMS でドラフトを書いて、すぐにページでドラフトを表示したい場合には理想的ではありません。あなたは、 Next.js にこれらのページを build 時ではなく** request 時**に render し、 fetch を公開した内容ではなくドラフト内容で行ってほしいと思うでしょう。 Next.js が特定のケースだけでdynamic レンダリングに切り替えることを望むでしょう。

Next.js はこの問題を解決するDraft Modeという機能を持っています。その使用方法について説明します。

Step 1: Create and access the Route Handler

まず、Route Handlerを作成します。名前は何でも構いません - 例えば、app/api/draft/route.ts などとします。

それから、next/headersから import draftModeをして、enable() method を呼び出します。

app/api/draft/route.ts
// route handler enabling draft mode
import { draftMode } from 'next/headers'

export async function GET(request: Request) {
  draftMode().enable()
  return new Response('Draft mode is enabled')
}
app/api/draft/route.js
// route handler enabling draft mode
import { draftMode } from 'next/headers'

export async function GET(request) {
  draftMode().enable()
  return new Response('Draft mode is enabled')
}

これにより、ドラフトモードを有効にするためのcookieが設定されます。この cookie を含む後続の Request は、静的に生成されたページの動作を変更するDraft Modeをトリガーします(この詳細については後述します)。

この操作は /api/draft を訪れて、自身のブラウザの開発者ツールを見ることで手動で test することができます。Set-Cookie response header で名前が__prerender_bypassである cookie に注目してください。

ヘッドレス CMS からそれに安全にアクセスする

実際には、この Route Handler を安全にヘッドレス CMS から呼び出したいでしょう。具体的な手順は使用しているヘッドレス CMS により異なりますが、ここでは可能な一般的な手順をいくつか紹介します。

これらの手順は、ご使用のヘッドレス CMS がカスタムドラフト URLの設定をサポートしていることを前提としています。サポートしていない場合でも、この method を使用してドラフト URL を保護することができますが、ドラフトの URL を手動で構築し、アクセスする必要があります。

まず最初に、お選びのトークンジェネレーターを使用して、secret トークン stringを作成する必要があります。この secret は、あなたの Next.js app とあなたのヘッドレス CMS だけが知っています。この secret は、CMS へのアクセス権がない人々がドラフト URL にアクセスするのを防ぎます。

Second、あなたのヘッドレス CMS がカスタムドラフト URL の設定をサポートしている場合、以下をドラフト URL に指定してください。これは、あなたの Route Handler が app/api/draft/route.ts にあることを前提としています。

Terminal
https://<your-site>/api/draft?secret=<token>&slug=<path>
  • <your-site>はあなたのデプロイメントドメインであるべきです。
  • <token>は生成した secret トークンに置き換えるべきです。
  • <path>は表示したいページの path でなければなりません。/posts/fooを表示したい場合は、&slug=/posts/fooを使用すべきです。

あなたのヘッドレス CMS では、URL のドラフトに variable を含めることができ、それにより<path>が次のように CMS のデータに基づいて動的に設定できるかもしれません:&slug=/posts/{entry.fields.slug}

最後に、 Route Handler で:

  • secret が一致し、slugパラメータが存在するか確認してください(存在しない場合、request は失敗するはずです)。
  • draftMode.enable()を呼び出して cookie を設定します。
  • その後、ブラウザを slug で指定された path へ redirect してください。
app/api/draft/route.ts
// route handler with secret and slug
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET(request: Request) {
  // Parse query string parameters
  const { searchParams } = new URL(request.url)
  const secret = searchParams.get('secret')
  const slug = searchParams.get('slug')

  // Check the secret and next parameters
  // This secret should only be known to this route handler and the CMS
  if (secret !== 'MY_SECRET_TOKEN' || !slug) {
    return new Response('Invalid token', { status: 401 })
  }

  // Fetch the headless CMS to check if the provided `slug` exists
  // getPostBySlug would implement the required fetching logic to the headless CMS
  const post = await getPostBySlug(slug)

  // If the slug doesn't exist prevent draft mode from being enabled
  if (!post) {
    return new Response('Invalid slug', { status: 401 })
  }

  // Enable Draft Mode by setting the cookie
  draftMode().enable()

  // Redirect to the path from the fetched post
  // We don't redirect to searchParams.slug as that might lead to open redirect vulnerabilities
  redirect(post.slug)
}
app/api/draft/route.js
// route handler with secret and slug
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

export async function GET(request) {
  // Parse query string parameters
  const { searchParams } = new URL(request.url)
  const secret = searchParams.get('secret')
  const slug = searchParams.get('slug')

  // Check the secret and next parameters
  // This secret should only be known to this route handler and the CMS
  if (secret !== 'MY_SECRET_TOKEN' || !slug) {
    return new Response('Invalid token', { status: 401 })
  }

  // Fetch the headless CMS to check if the provided `slug` exists
  // getPostBySlug would implement the required fetching logic to the headless CMS
  const post = await getPostBySlug(slug)

  // If the slug doesn't exist prevent draft mode from being enabled
  if (!post) {
    return new Response('Invalid slug', { status: 401 })
  }

  // Enable Draft Mode by setting the cookie
  draftMode().enable()

  // Redirect to the path from the fetched post
  // We don't redirect to searchParams.slug as that might lead to open redirect vulnerabilities
  redirect(post.slug)
}

それが成功すれば、ブラウザはドラフトモードの cookie を使用して表示したい path に Redirect されます。

Step 2: Update page

次のステップは、ページを更新して draftMode().isEnabled の value を確認することです。

あなたが、cookie が設定されているページを request すると、データは build 時間ではなく、request 時間に取得されます。

さらに、isEnabledの value はtrueになります。

app/page.tsx
// page that fetches data
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>
  )
}
app/page.js
// page that fetches data
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 から、または手動でドラフトの Route Handler(secretslugを含む)にアクセスすると、ドラフトの内容を見ることができるはずです。そして、公開せずにドラフトを更新した場合も、そのドラフトを見ることができるはずです。

これをヘッドレス CMS のドラフトの URL として設定するか、手動でアクセスすれば、ドラフトを見ることができるはずです。

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

More Details

default では、ブラウザが閉じられたときに Draft Mode セッションが終了します。

手動で Draft Mode の cookie をクリアするには、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 is disabled')
}
app/api/disable-draft/route.js
import { draftMode } from 'next/headers'

export async function GET(request) {
  draftMode().disable()
  return new Response('Draft mode is disabled')
}

次に、/api/disable-draft に request を送信して、Route Handler を呼び出します。この route をnext/linkを使用して呼び出す場合、prefetch={false}を渡して、prefetch 時に誤って cookie を削除しないようにしなければなりません。

next build ごとにユニーク

新しいバイパスの cookie value は、next buildを実行するたびに生成されます。

これにより、バイパス の cookie が推測できないことが保証されます。

Good to know: test Draft Mode をローカルで HTTP 上で使用するには、ブラウザがサードパーティの cookies とローカルストレージへのアクセスを許可する必要があります。

当社サイトでは、Cookie を使用しています。各規約をご確認の上ご利用ください:
Cookie Policy, Privacy Policy および Terms of Use