typescriptadvanced

Draft Mode for CMS Preview

Enable draft mode to preview unpublished CMS content in production with cookie-based toggling.

typescript
// 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');

  // Validate secret token
  if (secret !== process.env.DRAFT_SECRET) {
    return new Response('Invalid token', { status: 401 });
  }

  // Verify the content exists
  const post = await getPostBySlug(slug);
  if (!post) {
    return new Response('Post not found', { status: 404 });
  }

  // Enable draft mode
  const draft = await draftMode();
  draft.enable();

  // Redirect to the post page
  redirect(`/blog/${slug}`);
}

// app/api/draft/disable/route.ts
export async function GET() {
  const draft = await draftMode();
  draft.disable();
  redirect('/');
}

// app/blog/[slug]/page.tsx
import { draftMode } from 'next/headers';

export default async function BlogPost({
  params,
}: {
  params: Promise<{ slug: string }>;
}) {
  const { slug } = await params;
  const { isEnabled } = await draftMode();

  // Fetch draft or published content based on mode
  const post = isEnabled
    ? await getDraftPost(slug)
    : await getPublishedPost(slug);

  return (
    <article>
      {isEnabled && (
        <div className="bg-yellow-500 text-black p-2 text-center text-sm">
          Draft Mode — <a href="/api/draft/disable" className="underline">Exit</a>
        </div>
      )}
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

Use Cases

  • CMS preview
  • content editing
  • draft publishing

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.