Visual editing and the Sanity presentation tool in Next.js 16
Click on any text in the preview and jump straight to editing it in the studio. Here's how we set up Sanity's visual editing with Next.js 16.

Visual editing is the feature that makes editors fall in love with Sanity. Click on any text in the live preview. An overlay appears. Click it, and the studio opens directly to that field in that document.
No searching through the content tree. No guessing which field maps to which piece of text on the page. Just click and edit.
Here is how we wired it up.
Stega encoding on the client
The magic starts with stega encoding. Sanity embeds invisible markers in your content strings that tell the visual editing overlay which document and field each piece of text belongs to.
This is configured on the Sanity client:
import { createClient } from 'next-sanity'
export const client = createClient({
projectId,
dataset: dataset || 'production',
apiVersion,
useCdn: process.env.NODE_ENV === 'production',
perspective: 'published',
resultSourceMap: 'withKeyArraySelector',
stega: {
enabled: true,
studioUrl: '/studio',
},
})Two settings do the work. `resultSourceMap: 'withKeyArraySelector'` tells Sanity to return source maps with every query response. These source maps describe exactly where each value came from, down to the array index and field path. `stega.enabled: true` encodes that mapping into the actual string values using invisible Unicode characters.
The `studioUrl` tells the overlay where to link. Since our studio is embedded at `/studio`, clicking any overlay opens the studio on the same domain.
defineLive: the bridge between CMS and frontend
`defineLive` from `next-sanity/live` creates two exports that tie everything together:
import { defineLive } from 'next-sanity/live'
import { client } from './client'
export const { sanityFetch, SanityLive } = defineLive({
client,
serverToken: process.env.SANITY_API_TOKEN,
browserToken: process.env.SANITY_API_TOKEN // use a read-only viewer token in production,
})`sanityFetch` is used in server components to query content. It automatically handles cache tags for revalidation. `SanityLive` is a client component that sits in the root layout and manages the real-time connection when draft mode is active.
Draft mode
Visual editing requires draft mode. Next.js needs to know it should render unpublished content and enable the overlay.
Enabling draft mode is a one-liner thanks to `next-sanity`:
import { defineEnableDraftMode } from 'next-sanity/draft-mode'
import { client } from '@/sanity/lib/client'
export const { GET } = defineEnableDraftMode({
client: client.withConfig({
token: process.env.SANITY_API_TOKEN,
}),
})This creates a route at `/api/draft-mode/enable` that Sanity's presentation tool calls to activate draft mode in the Next.js preview.
The VisualEditing component
In the root layout, `VisualEditing` renders conditionally when draft mode is active:
import { VisualEditing } from 'next-sanity/visual-editing'
import { draftMode } from 'next/headers'
import { SanityLive } from '@/sanity/lib/live'
export default async function RootLayout({ children }) {
const isDraftMode = (await draftMode()).isEnabled
return (
<html lang="en">
<body>
{children}
<SanityLive />
{isDraftMode && <VisualEditing />}
</body>
</html>
)
}`SanityLive` is always present. It handles the real-time data connection. `VisualEditing` only appears in draft mode and renders the click-to-edit overlays.
The presentation tool
On the Sanity studio side, the presentation tool embeds a live preview of the site:
import { presentationTool } from 'sanity/presentation'
export default defineConfig({
plugins: [
presentationTool({
previewUrl: {
previewMode: {
enable: '/api/draft-mode/enable',
},
},
}),
],
})When an editor opens the presentation tool in the studio, it loads the site in an iframe with draft mode enabled. They see the actual site, with actual styling, with live content. Click any text and the studio panel on the left navigates to that exact field.
What the editor experience looks like
The editor opens `/studio`, clicks the presentation tool icon. The site loads in the right panel. They hover over a heading. A blue overlay appears showing the field name. They click it. The left panel scrolls to that field. They type. The preview updates in real-time.
No context switching. No mental mapping between "heroHeadline in the CMS" and "that big text at the top of the page". The connection is visual and immediate.
This is probably the single feature that makes the Sanity rebuild feel worth it from an editorial perspective. The code is minimal. The impact on usability is significant.
This is the last post in the technical series. For the full story of why we rebuilt, read [Why I rebuilt innatus.digital](/digital-insights/why-i-rebuilt-innatus-digital).

Chris Ryan
Managing Director
17+ years in full-stack web development, most of it leading teams agency-side across e-commerce, CMS platforms, and bespoke applications. Specialises in infrastructure, system integration, and data privacy, with hands-on experience as a Data Protection Officer. Founded Innatus Digital in 2020 to offer the kind of honest, technically-led partnership that he felt was missing from the agency world.