Struggling to authenticate draftMode in a Next.js 13 app, seeking advice and finding a solution.

2 replies
Last updated: Sep 27, 2023
Hi πŸ‘‹ - I've been struggling to authenticate
draftMode
- as in, I only want users with write access to be able to access/see draftMode - I've had a look at https://github.com/sanity-io/preview-kit but I'm still stuck (nextjs 13 app)
Any advice?
AI Update

I totally understand the frustration with authenticating draft mode! The good news is that the @sanity/preview-url-secret package is specifically designed to solve this problem. Let me walk you through how to secure it so only users with write access can enable draft mode.

The Solution: Use @sanity/preview-url-secret

This package creates a secure workflow where:

  1. A user clicks "Open preview" in the Presentation tool
  2. Sanity generates a short-lived secret (stored in your dataset)
  3. The secret is validated server-side using a token
  4. Only if valid, draft mode is enabled

Here's how to set it up for Next.js 13 App Router:

Step 1: Install the package

npm install @sanity/preview-url-secret @sanity/client

Step 2: Configure the Presentation Tool

In your sanity.config.ts:

import { defineConfig } from 'sanity'
import { presentationTool } from 'sanity/presentation'

export default defineConfig({
  // ... other config
  plugins: [
    presentationTool({
      previewUrl: {
        origin: 'http://localhost:3000', // your Next.js URL
        previewMode: {
          enable: '/api/draft', // the API route we'll create
        },
      },
    }),
  ],
})

Step 3: Create a Read Token

Create a Sanity API token with Viewer rights (not Editor!). Add it to your .env.local:

SANITY_API_READ_TOKEN=your_token_here

Step 4: Create the Draft Mode API Route

Create app/api/draft/route.ts:

import { client } from '@/sanity/lib/client'
import { validatePreviewUrl } from '@sanity/preview-url-secret'
import { draftMode } from 'next/headers'
import { redirect } from 'next/navigation'

const clientWithToken = client.withConfig({
  token: process.env.SANITY_API_READ_TOKEN,
})

export async function GET(req: Request) {
  const { isValid, redirectTo = '/' } = await validatePreviewUrl(
    clientWithToken, 
    req.url
  )
  
  if (!isValid) {
    return new Response('Invalid secret', { status: 401 })
  }

  draftMode().enable()
  redirect(redirectTo)
}

Create app/api/disable-draft/route.ts:

import { draftMode } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'

export function GET(request: NextRequest) {
  draftMode().disable()
  return NextResponse.redirect(new URL('/', request.nextUrl.origin))
}

How the Security Works

The magic is in validatePreviewUrl(). Here's what it does according to the @sanity/preview-url-secret documentation:

  1. Checks the secret: When a user clicks "Open preview" in Sanity Studio, a secret document is created in your dataset (type: sanity.previewUrlSecret)
  2. Validates permissions: The secret can only be created by users with Contributor role or above (those who can create draft documents)
  3. Short-lived: Secrets expire quickly, preventing unauthorized access
  4. Server-side validation: The token is never exposed to the client

So effectively, only Sanity users with write access can generate valid preview URLs.

Customizing Permissions (Enterprise)

If you're on an Enterprise plan and want even more control, you can create custom roles that restrict access to preview secrets. The preview-url-secret reference explains how to:

  • Grant Viewer roles access to previews
  • Restrict who can enable preview sharing
  • Control who can see shared preview links

Debugging

If it's still not working, add the debug plugin to see the secrets being created:

import { debugSecrets } from '@sanity/preview-url-secret/sanity-plugin-debug-secrets'

export default defineConfig({
  plugins: [
    debugSecrets(), // View secrets in Studio
    // ... other plugins
  ],
})

The key insight is that @sanity/preview-url-secret ties authentication to Sanity's built-in permission system rather than requiring you to build your own auth layer. Users without write access simply can't generate the secrets needed to enable draft mode.

For more details on the complete setup, check out the Visual Editing with Next.js App Router guide and the Draft Mode documentation.

Hope this helps! Let me know if you run into any issues with the setup.

πŸ‘‹ This section of the
next-sanity
package docs should help!
Life saver, it worked, thank you!

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?