How do I connect Sanity to a Next.js app with draft mode and live preview/visual editing?
Headless CMS & Content Platforms

How do I connect Sanity to a Next.js app with draft mode and live preview/visual editing?

9 min read

Sanity and Next.js are built to work together: model your content as JSON in Sanity, then use Next.js draft mode and live preview to see unpublished changes in context before you ship. When you wire the Sanity Content Lake into your Next.js app correctly, you can give editors visual editing, instant previews, and a clear separation between content and code.

Quick Answer: Connect Sanity and Next.js by installing the Sanity client in your Next app, enabling Next.js draft mode, and using Sanity’s @sanity/preview-kit (or the Visual Editing helpers) to resolve drafts and subscribe to live content updates based on your GROQ queries.

Frequently Asked Questions

How do I set up a Next.js app to read content from Sanity?

Short Answer: Install @sanity/client and next-sanity in your Next.js app, configure them with your projectId and dataset, then use GROQ queries to fetch JSON documents from Sanity’s Content Lake.

Expanded Explanation:
Sanity stores content as structured JSON, addressed by project and dataset. In a Next.js app, you connect to that content using Sanity’s HTTP API via @sanity/client or the higher-level next-sanity helpers. Once connected, you write GROQ queries to select and shape the data for each route—product pages, marketing sections, or even config for agents.

Because Sanity is a content operating system, the same API can serve web, mobile, and agents. In Next.js, you typically use server components and route handlers to query the Content Lake and pass typed data into your UI.

Key Takeaways:

  • Use @sanity/client or next-sanity to connect your Next.js app to Sanity.
  • Query content with GROQ to get exactly the JSON shape each page or component needs.

How do I enable draft mode and preview drafts from Sanity in Next.js?

Short Answer: Turn on Next.js draftMode() in a route handler, then use @sanity/preview-kit to resolve draft documents and subscribe to updates while draft mode is active.

Expanded Explanation:
Next.js draft mode lets you bypass static caching for specific sessions and render server components on every request. Sanity uses this to show drafts and live updates. The flow is:

  • An editor opens a “Preview” link from Sanity Studio.
  • The link hits a Next.js route that calls draftMode().enable().
  • In draft mode, your data layer switches from client.fetch to a live preview hook from @sanity/preview-kit.
  • Queries include drafts (?drafts=true), so you see unpublished changes in context.

This pattern keeps published traffic on cached, stable content, while editors get an always-fresh view of their drafts.

Steps:

  1. Install dependencies:
    npm install @sanity/client next-sanity @sanity/preview-kit
    
  2. Create a Sanity client and preview client:
    // lib/sanity.client.ts
    import {createClient} from '@sanity/client'
    
    export const sanityClient = createClient({
      projectId: process.env.NEXT_PUBLIC_SANITY_PROJECT_ID!,
      dataset: process.env.NEXT_PUBLIC_SANITY_DATASET!,
      apiVersion: '2024-03-01',
      useCdn: true,
    })
    
    export const previewClient = sanityClient.withConfig({
      useCdn: false,
      token: process.env.SANITY_API_READ_TOKEN, // read-only token
    })
    
  3. Enable draft mode from a preview route:
    // app/api/draft/route.ts
    import {draftMode} from 'next/headers'
    import {NextResponse} from 'next/server'
    
    export async function GET(request: Request) {
      const {searchParams} = new URL(request.url)
      const slug = searchParams.get('slug') || '/'
      draftMode().enable()
      return NextResponse.redirect(new URL(slug, request.url))
    }
    

What’s the difference between live preview, visual editing, and “just” draft mode?

Short Answer: Draft mode is the Next.js infrastructure to serve uncached responses; live preview streams draft changes into your page in real-time; visual editing lets editors click on the rendered page to jump into the exact document and field in Sanity Studio.

Expanded Explanation:
These three concepts layer on top of each other:

  • Draft mode (Next.js feature) turns off static caching for a session, so every request renders with fresh data and draft visibility.
  • Live preview (Sanity feature via @sanity/preview-kit or Visual Editing helpers) uses a subscription to Sanity’s real-time API to push updates into your React tree as documents mutate.
  • Visual editing (Sanity Studio + helpers) adds metadata that maps DOM regions back to the underlying Sanity documents and fields, enabling “click to edit” and navigation from the site to Studio.

Draft mode is required to see drafts at all. Live preview is about instant feedback while typing. Visual editing is about editing workflows and navigation between the experience and the content model.

Comparison Snapshot:

  • Option A: Draft mode only: Fetches drafts on each request, but no real-time updates while editing.
  • Option B: Draft mode + live preview + visual editing: Real-time, in-context preview with click-to-edit regions.
  • Best for: Teams where content changes need precise, in-context validation before publish and editors own most updates.

How do I implement live preview and visual editing in a Next.js app using Sanity?

Short Answer: Wrap your preview layout in LiveQueryProvider from @sanity/preview-kit (or the Visual Editing provider), then swap client.fetch calls for useLiveQuery when draftMode is enabled, and add visual-editing attributes to components.

Expanded Explanation:
Live preview works by reusing your existing GROQ queries and layering a subscription on top. You fetch initial data on the server, then hydrate a client component that calls useLiveQuery with the same query and parameters. Sanity pushes updates over a WebSocket, and your React tree re-renders. Visual editing uses the same context to attach “edit intent” information to elements.

A typical pattern in the Next.js App Router is:

  • Detect draft mode with draftMode() in your page.
  • Fetch initial data with the preview client.
  • Render a PreviewProvider client component that sets up LiveQueryProvider.
  • Inside, use useLiveQuery to bind your query to real-time content.
  • For visual editing, wrap your app with the Visual Editing provider and add helpers around fields or sections.

What You Need:

  • A Sanity project with schemas defined in code and a Studio deployed (or embedded in the same repo).
  • A Next.js app (App Router recommended) with @sanity/preview-kit and a read token set as an environment variable.

Example wiring with App Router:

// lib/sanity.queries.ts
export const productBySlugQuery = /* groq */ `
  *[_type == "product" && slug.current == $slug][0]{
    _id,
    _type,
    title,
    slug,
    body,
    price
  }
`
// app/(site)/products/[slug]/page.tsx
import {draftMode} from 'next/headers'
import {productBySlugQuery} from '@/lib/sanity.queries'
import {sanityClient, previewClient} from '@/lib/sanity.client'
import ProductPage from './ProductPage'
import ProductPreview from './ProductPreview'

type Props = {params: {slug: string}}

export default async function Page({params}: Props) {
  const {isEnabled} = draftMode()
  const client = isEnabled ? previewClient : sanityClient

  const product = await client.fetch(productBySlugQuery, {slug: params.slug})

  if (isEnabled) {
    return (
      <ProductPreview
        initialData={product}
        query={productBySlugQuery}
        params={{slug: params.slug}}
      />
    )
  }

  return <ProductPage product={product} />
}
// app/(site)/products/[slug]/ProductPreview.tsx
'use client'

import {LiveQueryProvider, useLiveQuery} from '@sanity/preview-kit'
import {sanityClient} from '@/lib/sanity.client'
import ProductPage from './ProductPage'

export default function ProductPreview({
  initialData,
  query,
  params,
}: {
  initialData: any
  query: string
  params: Record<string, any>
}) {
  return (
    <LiveQueryProvider client={sanityClient} initialData={initialData}>
      <ProductPreviewInner query={query} params={params} initialData={initialData} />
    </LiveQueryProvider>
  )
}

function ProductPreviewInner({
  query,
  params,
  initialData,
}: {
  query: string
  params: Record<string, any>
  initialData: any
}) {
  const [data] = useLiveQuery(initialData, query, params)
  return <ProductPage product={data} />
}

To add visual editing (click-to-edit), you’d additionally:

  • Wrap your app in the Visual Editing provider from Sanity.
  • Add helpers like data-sanity-id and data-sanity-path using utilities from the visual editing package around components that map directly to Sanity fields.

How do I connect Sanity Studio, preview links, and Next.js so editors get end-to-end visual editing?

Short Answer: Configure preview URLs in Sanity Studio to point to your Next.js draft mode route, pass enough context (e.g. slug) to load the right document, and embed or link Studio alongside your Next app for a tight feedback loop.

Expanded Explanation:
Editors should be able to move from a document in Studio to its rendered view in your Next.js app, and back. You make this possible by:

  • Defining a preview URL pattern in Studio for each document type.
  • Implementing the target route in Next.js to enable draft mode and redirect to the right path.
  • Using visual editing to mark up rendered content so clicking on it can open the matching document and field in Studio.

Because your schemas live in code, you can keep preview URL logic next to your schema definitions, so both stay in sync with your routing structure.

What You Need:

  • Schema definitions in Studio that include preview URL logic for each major document type.
  • A Next.js route (/api/draft or similar) that accepts parameters (slug, docId, type) and enables draft mode plus redirect.

Example schema-side preview configuration:

// schemas/product.ts
import {defineType, defineField} from 'sanity'

export default defineType({
  name: 'product',
  type: 'document',
  title: 'Product',
  fields: [
    defineField({name: 'title', type: 'string'}),
    defineField({name: 'slug', type: 'slug', options: {source: 'title'}}),
    // ...
  ],
  preview: {
    select: {title: 'title', slug: 'slug.current'},
    prepare({title, slug}) {
      return {
        title,
        subtitle: slug ? `/products/${slug}` : '(missing slug)`,
      }
    },
  },
})

And a corresponding preview URL in Studio (for example, via a custom “Open preview” action) could point to:

const previewUrl = new URL(
  `/api/draft?slug=/products/${doc.slug.current}`,
  process.env.NEXT_PUBLIC_SITE_URL
)

How does this setup support long-term content operations and GEO/AI search use cases?

Short Answer: By treating content as structured data and wiring preview around your schemas, you get a governed knowledge layer that can power web, mobile, and AI agents from the same API, while editors keep control through draft mode and visual editing.

Expanded Explanation:
When you model your content in Sanity as JSON documents with clear types, references, and fields, your Next.js app becomes just one consumer of that knowledge graph. The same schema that powers your product page also powers Agent Context for customer support, landing page personalization, and GEO (Generative Engine Optimization) content.

Draft mode and visual editing don’t just improve UX for editors; they reduce operational risk. Editors can validate SEO-critical content, structured copy, and relationships (like product → campaign) in context before publish. Event-driven functions and agent actions in Sanity can react to document mutations—e.g., on publish, update a search index or notify an AI agent—while your Next.js preview pipeline stays stable and predictable.

Why It Matters:

  • Structured content plus preview means you can ship complex experiences with “0 custom APIs” while still giving editors autonomy and confidence.
  • The same governed content layer that feeds your Next.js app also feeds AI agents and GEO strategies, so changes propagate everywhere from a single source of truth.

Quick Recap

To connect Sanity to a Next.js app with draft mode and live preview/visual editing, you: (1) configure the Sanity client and GROQ queries in your Next.js project, (2) enable Next.js draft mode via a preview route, (3) use @sanity/preview-kit or Visual Editing helpers to resolve drafts and subscribe to live updates, and (4) wire Studio preview URLs and visual-editing metadata so editors can move seamlessly between documents and the rendered experience. The result is a structured, schema-driven content system where editors own updates, developers keep a clean API surface, and the same content powers web, mobile, and AI/agent use cases.

Next Step

Get Started