
Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag storeI can see you're experiencing a crash when you log out of Sanity Studio while Draft Mode is still active in your Next.js app. The issue is that the Draft Mode cookie persists after logout, but your app throws an error when it can't authenticate, causing the entire application to crash.
Your onPublicAccessOnly callback in definePreview throws an error when the user isn't authenticated, which crashes your app. According to the Draft Mode documentation, Draft Mode uses cookie-based authentication that persists independently of your Sanity Studio session. When you log out of Studio, the Draft Mode cookie stays active in your browser, but the authentication tokens needed to fetch preview content are no longer valid.
The definePreview and usePreview approach you're using is part of an older preview implementation pattern. Sanity has moved toward newer approaches for live content and preview, including the Live Content API and modern Visual Editing patterns. However, if you're maintaining an existing codebase using this pattern, here's how to fix your immediate issue.
Instead of throwing an error in onPublicAccessOnly, you need to handle the unauthenticated state gracefully. Here are the best approaches:
Modify your sanity.preview.ts to redirect to a route that disables Draft Mode when authentication fails:
// sanity.preview.ts
"use client"
import { definePreview } from "next-sanity/preview"
import type { UsePreview } from "next-sanity/preview"
import { config } from "./sanity.client"
const onPublicAccessOnly = () => {
// Instead of throwing, redirect to disable draft mode
if (typeof window !== 'undefined') {
window.location.href = '/api/draft/disable'
}
}
export const usePreview: UsePreview = definePreview({
projectId: config.projectId,
dataset: config.dataset,
onPublicAccessOnly
})Then create or verify you have a disable route:
// app/api/draft/disable/route.ts
import { draftMode } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'
export async function GET(request: NextRequest) {
draftMode().disable()
// Redirect back to the current page or home
const redirectUrl = request.nextUrl.searchParams.get('redirect') || '/'
return NextResponse.redirect(new URL(redirectUrl, request.url))
}This approach automatically cleans up the Draft Mode cookie when authentication fails, preventing the error from recurring and eliminating the need for manual cookie deletion.
A simpler approach that doesn't throw an error:
const onPublicAccessOnly = () => {
// Silently handle the error and trigger cleanup
if (typeof window !== 'undefined') {
// Attempt to disable draft mode
fetch('/api/draft/disable').then(() => {
// Optionally reload to show published content
window.location.reload()
})
}
// Return null instead of throwing
return null
}Wrap your preview component in an error boundary that catches authentication failures:
// components/previews/PreviewErrorBoundary.tsx
"use client"
import { Component, ReactNode } from "react"
interface Props {
children: ReactNode
fallback: ReactNode
}
interface State {
hasError: boolean
}
export class PreviewErrorBoundary extends Component<Props, State> {
constructor(props: Props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error: Error) {
// Check if it's an authentication error
if (error.message.includes("not logged in") || error.message.includes("preview")) {
// Disable draft mode
fetch('/api/draft/disable').then(() => {
window.location.reload()
})
return { hasError: true }
}
throw error // Re-throw if it's a different error
}
render() {
if (this.state.hasError) {
return this.props.fallback
}
return this.props.children
}
}Then update your page to use it:
// page.tsx
if (isEnabled) {
return (
<PreviewSuspense fallback={<PageLayout page={page} />}>
<PreviewErrorBoundary fallback={<PageLayout page={page} />}>
<PagePreview query={pageQuery} variables={{ slug: "frontpage" }} />
</PreviewErrorBoundary>
<ExitPreviewMode />
</PreviewSuspense>
)
}According to Sanity's documentation on presenting and previewing content, when Draft Mode is enabled, Next.js sets a cookie that bypasses static generation and fetches draft content using authenticated queries. The authentication tokens are only valid while you're logged into Studio. When you log out:
onPublicAccessOnly callback is triggeredOption 1 is the cleanest solution because it:
Make sure your "Exit Preview Mode" button also uses the same /api/draft/disable route for consistency.
If you're starting a new project or can refactor your existing one, consider using Sanity's modern Live Content API with the next-sanity toolkit's newer patterns. The Live Content API provides real-time content updates without the complexity of Draft Mode cookie management, making "live by default" content much simpler to implement. This approach eliminates many of the authentication and cookie management issues you're experiencing with the older preview patterns.
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.
Content operations
Content backend


The only platform powering content operations
By Industry


Tecovas strengthens their customer connections
Build and Share

Grab your gear: The official Sanity swag store
Read Grab your gear: The official Sanity swag store