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 を呼び出します。
// 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')
}
// 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
にあることを前提としています。
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 してください。
// 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)
}
// 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
になります。
// 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>
)
}
// 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(secret
とslug
を含む)にアクセスすると、ドラフトの内容を見ることができるはずです。そして、公開せずにドラフトを更新した場合も、そのドラフトを見ることができるはずです。
これをヘッドレス CMS のドラフトの URL として設定するか、手動でアクセスすれば、ドラフトを見ることができるはずです。
https://<your-site>/api/draft?secret=<token>&slug=<path>
More Details
ドラフトモードの cookie をクリアする
default では、ブラウザが閉じられたときに Draft Mode セッションが終了します。
手動で Draft Mode の cookie をクリアするには、draftMode().disable()
を呼び出す Route Handler を作成します。
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
に 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 とローカルストレージへのアクセスを許可する必要があります。