Visual Editing for interactive live previews
In short, Visual Editing works by dynamically setting the React Loader configured by hydrogen-sanity to query for draft documents using the drafts perspective.
As well as include "stega encoding" to return information about the origin of the content and create interactive overlays so that authors can use the front end as a means to navigate the content. Most commonly, this is all done inside the Presentation tool.
hydrogen-sanity readme for all installation steps if you need further guidance.To support visual editing in the current environment, pass a preview configuration to the loader. In order to enable or disable visual editing for the current request, you'll need to provide a condition like checking whether or not the user's session has the project ID set.
If a preview option isn't provided, then the current environment won't have visual editing setup. In some cases, you may want to only enable visual editing in a preview deployment.
You can check for preview mode using the sanity context's preview?.enabled value.
The token value here will be created later in this lesson. Previews won't work without it.
createSanityContext in your context.ts file to configure server-side data fetching when preview mode is activated.// ...all other importsimport {PreviewSession} from 'hydrogen-sanity/preview/session';import {isPreviewEnabled} from 'hydrogen-sanity/preview';
export async function createHydrogenRouterContext( request: Request, env: Env, executionContext: ExecutionContext,) { const [cache, session, previewSession] = await Promise.all([ caches.open('hydrogen'), AppSession.init(request, [env.SESSION_SECRET]), PreviewSession.init(request, [env.SESSION_SECRET]), ]);
const sanity = await createSanityContext({ request, cache, waitUntil, client: { projectId: env.SANITY_PROJECT_ID, dataset: env.SANITY_DATASET, apiVersion: env.SANITY_API_VERSION || '2025-08-27', useCdn: true, stega: { enabled: isPreviewEnabled(env.SANITY_PROJECT_ID, previewSession), studioUrl: 'http://localhost:3333', } }, preview: { token: env.SANITY_API_TOKEN, session: previewSession, } }); // ...rest of context}Use this to determine whether to render a preprepared VisualEditing component is rendered.
// ...other importsimport {usePreviewMode} from 'hydrogen-sanity/preview'import {VisualEditing} from 'hydrogen-sanity/visual-editing'
export function Layout({children}: {children?: React.ReactNode}) { // ...all other logic const previewMode = usePreviewMode() return ( <html lang={currentLanguage}> // ...all other components <Sanity nonce={nonce} /> {previewMode ? <VisualEditing action="/api/preview" /> : null} </html> );}The Presentation tool can be configured to visit a specific route to potentially enable preview mode. Within the hydrogen-sanity package is a prepared route which will use the Sanity Client you configured in server.ts to check for a secret that Presentation has prepared – and if matched – write the projectId to the current user's session.
export {action, loader} from 'hydrogen-sanity/preview/route'Since Sanity Studio's Presentation tool displays the storefront inside an iframe, you will need to adjust the Content Security Policy (CSP) in entry.server.tsx.
The settings below work for the setup you're creating in this course, but more options are available.
entry.server.tsx with configuration to enable viewing within an iframe.// ...all other imports
export default async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, reactRouterContext: EntryContext, context: HydrogenRouterContextProvider,) { const {env, sanity} = context; const projectId = env.SANITY_PROJECT_ID; const studioHostname = 'http://localhost:3333'; const isPreviewEnabled = sanity.preview?.enabled;
const {nonce, header, NonceProvider} = createContentSecurityPolicy({ // ...all other settings frameAncestors: isPreviewEnabled ? [studioHostname] : [], defaultSrc: ['https://cdn.sanity.io', 'https://lh3.googleusercontent.com'], connectSrc: [ `https://${projectId}.api.sanity.io`, `wss://${projectId}.api.sanity.io`, ], });
// ...all other configuration}In this section you'll setup the Presentation tool – a unique plugin with a Studio tool where authors can browse the website as a way to navigate your Sanity content.
You can associate documents with routes on the website so that content creators can more quickly jump from documents to any page on the front end. These are called "locations" in Presentation.
import { defineLocations } from "sanity/presentation";
export const locations = { product: defineLocations({ select: { title: 'store.title', slug: 'store.slug.current', }, resolve: (doc) => ({ locations: [ { title: doc?.title || 'Untitled', href: `/products/${doc?.slug}`, }, {title: 'Products', href: `/products`}, ], }), }), }Back in your Sanity Studio project, update the plugins inside sanity.config.ts to include the Presentation tool – with one configuration setting to visit the resource route setup earlier in this exercise.
sanity.config.ts file// Add these importsimport { presentationTool } from "sanity/presentation";import { locations } from "./presentation/locations";
export default defineConfig({ // ...all other settings
plugins: [ // ..all other plugins presentationTool({ resolve: {locations}, previewUrl: { origin: "http://localhost:3000", preview: "/", previewMode: { enable: "/api/preview", }, }, }), ],});The origin is necessary if you are deploying your storefront and Studio on different domains. If you provide one, the other options are relative to that domain. Like the origin in your Content Security Policy, you might consider using an environment variable you can assign at runtime.
A viewer token is required for server-side fetching of draft content as well as "stega encoding" for Visual Editing.
Open Manage from the Studio or run the following from the command line:
# In the /studio foldernpx sanity manage.env file in your Hydrogen application.SANITY_API_TOKEN="sk..."You will also need to allow communication from your front end to the Content Lake by adding its domain as a CORS origin.
http://localhost:3000 to the CORS origins in your Sanity project settings at sanity.io/manage.Now visit the Presentation tool in your Studio. You should be redirected to the home page. Navigate to a product that contains some Sanity content and you should be able to click-to-edit and watch previews of draft content rendering in your front end in real time.
Note that because some of the content – such as product names – were rendered from Shopify, you won't be able to click and edit them.