Capítulo 40 de 456
Use Draft Mode quando um editor de CMS precisa ver conteúdo não publicado sem esperar revalidação; ele contorna todas as camadas de cache do Next.js só para a requisição do editor, mantendo o cache normal para os demais visitantes.
draftMode(): função de next/headers; retorna objeto com enable(), disable() e isEnabled.__prerender_bypass: setado por draft.enable(); requisições subsequentes com esse cookie ignoram fetch cache, 'use cache', unstable_cache e o cache de resposta ISR.private, no-cache, no-store, max-age=0, must-revalidate.slug que existe no CMS antes de habilitar o modo.searchParams: evita open redirect vulnerability (redirecionar para post.slug obtido do CMS, não do parâmetro cru).'use cache': isEnabled pode ser lido dentro de um componente 'use cache' para mostrar indicador; o bypass de cache ainda se aplica, então o componente reexecuta a cada request de draft. enable()/disable() NÃO podem ser chamados dentro do escopo de uma diretiva de cache.<Link> para exit: usar <form method="GET"> em vez de <Link> para rotas GET de draft, porque o Next.js faz prefetch de <Link> por padrão, o que limparia o cookie antes do clique; forms nunca são prefetchados.// app/api/draft/route.ts
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')
if (secret !== 'MY_SECRET_TOKEN' || !slug) {
return new Response('Invalid token', { status: 401 })
}
const post = await getPostBySlug(slug)
if (!post) {
return new Response('Invalid slug', { status: 401 })
}
const draft = await draftMode()
draft.enable()
redirect(post.slug) // do dado buscado, não de searchParams
}
// app/preview-banner.tsx
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'
async function exitPreview() {
'use server'
const draft = await draftMode()
draft.disable()
redirect('/')
}
export async function PreviewBanner() {
const { isEnabled } = await draftMode()
if (!isEnabled) return null
return (
<aside role="status">
Preview mode is on.{' '}
<form action={exitPreview}>
<button type="submit">Exit preview</button>
</form>
</aside>
)
}
GET Route Handler para operação que muda estado./api/draft habilitaria Draft Mode para si mesmo; sempre validar token compartilhado.slug direto de searchParams: abre a porta para open redirect; redirecionar a partir do dado retornado pelo CMS.<Link>: prefetch do Next.js dispara a requisição antes do clique, limpando o cookie de forma inesperada.draftMode().enable()/disable() dentro de escopo 'use cache': não suportado; toggle deve ficar em Route Handler ou Server Action.enable() seta cookie → redirect para o slug → página busca fresh porque cache é ignorado.POST para o exit flow (Server Action ou Route Handler POST); o entry flow usa GET porque o CMS abre a URL numa nova aba.fetch com base em isEnabled em vez de depender só do bypass de cache.<form>.