Discussion about resolving an issue with preview mode not logging out properly and causing errors.

45 replies
Last updated: Jun 26, 2023
How can I solve a problem where when you logout during a Preview mode from the studio and return back to the website that it removes the cookie or cache?I am getting an error after logging out that I have no access to the preview mode because I am not logged in, resulting in the app crashing. I have to manually remove the cookies in order to make it work.


to load resource: the server responded with a status of 404 ()
26488-4e81157118c9fc11.js:1 Error: Unable to load preview as you're not logged in
    at onPublicAccessOnly (9151-fc58c70d1eec9468.js:1:10281)
    at 9151-fc58c70d1eec9468.js:1:12816
    at s.default (9151-fc58c70d1eec9468.js:1:9360)
    at rs (bce60fc1-d12f33ec043fc570.js:9:38883)
    at lg (bce60fc1-d12f33ec043fc570.js:9:54283)
    at iv (bce60fc1-d12f33ec043fc570.js:9:112451)
    at oR (bce60fc1-d12f33ec043fc570.js:9:90087)
    at bce60fc1-d12f33ec043fc570.js:9:89936
    at oD (bce60fc1-d12f33ec043fc570.js:9:89943)
    at ow (bce60fc1-d12f33ec043fc570.js:9:87645)
window.console.error @ 6488-4e81157118c9fc11.js:1
<http://sociaaladvocatenrotterdam.vercel.app/:1|sociaaladvocatenrotterdam.vercel.app/:1>     Failed 

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 = () => {
  throw new Error(`Unable to load preview as you're not logged in`)
}

export const usePreview: UsePreview = definePreview({
  projectId: config.projectId,
  dataset: config.dataset,
  onPublicAccessOnly
})

page.tsx:

import { draftMode } from "next/headers"
import { notFound } from "next/navigation"

import { getPage, getPageMeta } from "@/lib/sanity/sanity.helpers"

import { PageLayout } from "@/components/layout"
import { ExitPreviewMode, MetaData } from "@/components/shared"
import { PagePreview, PreviewSuspense } from "@/components/previews"

import { pageQuery } from "@/lib/queries"

export const generateMetadata = async () => {
  const page = await getPageMeta("frontpage")

  return MetaData({ page })
}

export const revalidate = 60 // revalidate this page every 60 seconds

const HomePage = async () => {
  const { isEnabled } = draftMode()

  const page = await getPage("frontpage")

  if (!page) {
    notFound()
  }

  if (isEnabled) {
    return (
      <PreviewSuspense fallback={<PageLayout page={page} />}>
        <PagePreview query={pageQuery} variables={{ slug: "frontpage" }} />
        <ExitPreviewMode />
      </PreviewSuspense>
    )
  }

  return <PageLayout page={page} />
}

export default HomePage
AI Update

I 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.

The Core Problem

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.

Important Context: Deprecated API

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.

Solution: Handle Authentication Failures Gracefully

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.

Option 2: Silent Return with Cleanup

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
}

Option 3: Error Boundary with Auto-Recovery

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>
  )
}

Why This Happens

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:

  1. The Studio session ends and tokens are invalidated
  2. The Draft Mode cookie remains active in your browser
  3. Your app tries to fetch preview content with invalid credentials
  4. The onPublicAccessOnly callback is triggered
  5. Your current implementation throws an error, crashing the app

Option 1 is the cleanest solution because it:

  1. Automatically cleans up the problematic state by disabling Draft Mode
  2. Prevents the error from recurring without manual cookie deletion
  3. Provides a smooth user experience with an automatic redirect
  4. Keeps your error handling logic simple and centralized

Make sure your "Exit Preview Mode" button also uses the same /api/draft/disable route for consistency.

Consider Upgrading to Live Content API

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.

I have seen examples where an area on the header (on your site) checks if you are in preview mode (draft). If it is, a text will appear saying something like "you are in preview... click link to exit". The link will then call the exit preview page and remove the cookie. It's not exactly what you asked but it fits nicely imo.
Could you share the link?
Well I do i have ExitPrevieMode shows a button Exit preview mode. But Once you logout and refresh the page page will crash
But the problem is I am not logged in to the STudio anymore. ANd the Preview button still showing. Only removes if cookies are deleted
yeah I understand now, my bad.
The moment you click on “logout” from the Studio while a page or post is still in preview mode and when you return to the homepage page will crash saying you are not logged in to use preview mode. This is still active
what I was suggesting was a link (on your website, like in the header) to disable preview (
/api/exit-preview
for example). you would display the link only if you are in preview.
this will effectively clear the cookies and let you go back to live content
I have that yes. My button is “Exit preview mode” and a link to the api exit preview
It works if you are logged in
I see
But once you logout cookies are still there
When i comment code isDraft it works
So something is missing there
that's odd, it should not matter really as this is the Next draft mode implementation and should not interfere with Sanity imo
Was hoping that Sanity has a function to check if user is logged in
If you are ysing previews could you test this?
Same action
I could try
Like logging out
while in preview
See what happens
Thanks
works for me
How do you check if you are in preview mode?
Do you use “pages” or “app” folder
I am usingg isDraft new function in next 13
I don't (haven't implemented it yet) but I just logged out while in preview and got the same error. Then I manually went to the exit preview page and it's all good
So its a bug
yeah, I'd say that's the way for checking (I'm not using drafts yet)
Somewhere
good to know
still the good old preview and pages routes
try to go manually to the exit preview page and check, it should work
Works manually yes
what error do you get when you check
isDraft()
?
But when you set to return null the Button is still showing to exit preview mode even if not logged in and to delete the cookies.
import { definePreview } from "next-sanity/preview"
import type { UsePreview } from "next-sanity/preview"
import { config } from "./sanity.client"

const exitPreviewMode = async () => {
  const exitPreviewUrl = "/api/exit-preview"

  try {
    const response = await fetch(exitPreviewUrl)

    if (response.ok) {
      console.log("Exited preview mode successfully.")
    } else {
      console.error("Failed to exit preview mode:", response.statusText)
    }
  } catch (error) {
    console.error("Failed to exit preview mode:", error)
  }
}

const onPublicAccessOnly = () => {
  exitPreviewMode()
}

export const usePreview: UsePreview = definePreview({
  projectId: config.projectId,
  dataset: config.dataset,
  onPublicAccessOnly
})
Let's see if this works 😄
🙂 I was trying something similar just now
import { definePreview } from "next-sanity/preview";
import type { UsePreview } from "next-sanity/preview";
import { config } from "./sanity.client";

const exitPreviewMode = async () => {
  const exitPreviewUrl = "/api/exit-preview";

  try {
    const response = await fetch(exitPreviewUrl);

    if (response.ok) {
      console.log("Exited preview mode successfully.");
      location.reload(); // Refresh the page
    } else {
      console.error("Failed to exit preview mode:", response.statusText);
    }
  } catch (error) {
    console.error("Failed to exit preview mode:", error);
  }
};

const onPublicAccessOnly = () => {
  exitPreviewMode();
};

export const usePreview: UsePreview = definePreview({
  projectId: config.projectId,
  dataset: config.dataset,
  onPublicAccessOnly,
});
yeah, that looks good
Lets see
🤞
Hi Nino, were you able to solve this? I am stuck on the same error message currently, while trying to implement live preview in a next js 13.4 pages dir project.

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?