DEV Community

Anas Sheikh
Anas Sheikh

Posted on

Forgetting to Disable Draft Mode in Next.js Can Leave Unpublished Content Visible Longer Than You Think

I covered enabling draft mode as part of the Sanity CMS setup in an earlier post, letting an editor preview unpublished content before it goes live. There's a real, practical gap worth covering on its own, specifically what draft mode actually is and isn't scoped to, since the natural assumption about that scope is wrong in a way that causes genuine, recurring confusion.

What Draft Mode Actually Is

// app/api/draft/route.ts
import { draftMode } from 'next/headers';

export async function GET(request: Request) {
  const draft = await draftMode();
  draft.enable();
  redirect('/blog/some-post');
}
Enter fullscreen mode Exit fullscreen mode

Calling enable() sets a cookie in the current browser session, and Next.js checks for that specific cookie on subsequent requests to decide whether to fetch and render draft content instead of published content. This is genuinely useful, exactly the mechanism that lets an editor click "preview" and see an unpublished change before it goes live.

The Assumption That Causes Real Confusion

The natural, intuitive assumption is that enabling draft mode is a site-wide toggle, flip it on, the whole site shows draft content to everyone, flip it off, everyone's back to published content. That's not what's actually happening. Draft mode is scoped entirely to the cookie in one specific browser, one specific person's session. It has no effect whatsoever on what any other visitor sees, and it's not a global site state at all.

Where This Assumption Actually Causes Problems

An editor previews a draft, sees it looks correct, and reports the change is live, when it isn't. Since draft mode only affects their own browser, they're seeing the preview correctly, and it's genuinely easy to forget, in the moment, that what they're seeing is specifically because of their own enabled draft mode, not because the change has actually been published for everyone else.

An editor forgets to disable draft mode after previewing, and later assumes the live site is broken when they see unexpected content. If draft mode stays enabled in their browser from a previous preview session, every subsequent visit to the site, including regular, non-preview browsing, continues showing draft content in that specific browser, which can look exactly like the site rendering the wrong content, when it's actually correctly rendering draft content because that browser's cookie still has it enabled.

A shared or public computer retains draft mode enabled from a previous session. If draft mode was enabled on a shared device and never explicitly disabled, whoever uses that browser next, potentially someone without any editing permissions or context at all, sees draft, unpublished content without any indication of why, or that it's not what a normal visitor would see.

Why This Doesn't Feel Like a Bug When It Happens

None of this is actually broken, draft mode is behaving exactly as designed, scoped to a cookie in one browser. The confusion comes entirely from a mismatch between that actual, correct behavior and the intuitive mental model of it as some kind of site-wide switch. Nothing in the experience of using it particularly corrects that mental model, since enabling it does show the expected preview, which reinforces the sense that "draft mode is on" as a general, site-wide state, rather than "draft mode is on, in this specific browser, right now."

The Actual Fix: Always Pair Enable With a Clear, Visible Exit Path

// app/api/disable-draft/route.ts
import { draftMode } from 'next/headers';
import { redirect } from 'next/navigation';

export async function GET() {
  const draft = await draftMode();
  draft.disable();
  redirect('/');
}
Enter fullscreen mode Exit fullscreen mode
// A persistent, visible banner whenever draft mode is active in the current browser
// app/layout.tsx
import { draftMode } from 'next/headers';

export default async function RootLayout({ children }) {
  const { isEnabled } = await draftMode();

  return (
    <html>
      <body>
        {isEnabled && (
          <div style={{ background: '#facc15', padding: '8px', textAlign: 'center' }}>
            Draft mode is on in this browser.{' '}
            <a href="/api/disable-draft">Exit preview</a>
          </div>
        )}
        {children}
      </body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

A persistent, visible banner, shown for as long as draft mode remains enabled in that specific browser, with a clear, one-click way to exit it, closes most of the actual confusion here. It makes the scoped, per-browser nature of draft mode visible and obvious in the moment, rather than an invisible state someone has to remember exists and remember to manually undo.

Where This Connects Back to the Original Setup

This banner pattern is worth adding as a standard part of any draft mode implementation, not an optional extra. Without it, draft mode functions correctly but silently, and silent, correctly-functioning features that contradict someone's natural mental model of how they work are exactly the kind of thing that causes recurring, hard-to-diagnose confusion, not because anything's actually broken, but because nothing visible corrects the wrong assumption in the moment it matters.

I build this banner pattern into the CMS setups I put together for client projects and the templates at pixelanas.com, specifically because the confusion this prevents is common enough to be worth the small amount of extra code every time.

The Actual Rule

Draft mode is a per-browser, cookie-scoped state, not a global site toggle, and that gap between actual behavior and intuitive assumption is exactly what causes real confusion. Pairing every draft mode implementation with a persistent, visible indicator and an obvious way to exit closes that gap directly, rather than leaving editors to remember and manually track an invisible state themselves.


If you've implemented draft mode without a visible indicator showing when it's active, worth adding one, this is a small addition that prevents a genuinely common, confusing situation. Drop your own experience with this in the comments, curious whether this confusion is as common elsewhere as it's been on projects I've worked on.

Get the templates: https://pixelanas.gumroad.com


Anas, full-stack Next.js developer building SaaS products and premium templates. X: @ASheikh69751

Top comments (0)