Sanity
Learn
CoursesSanity and Shopify with HydrogenVisual Editing for interactive live previews

Sanity and Shopify with Hydrogen

Lesson
7

Visual Editing for interactive live previews

To dramatically improve the content creation experience for your authors, configure your Hydrogen front end and Sanity Studio for Visual Editing.

Log in to mark your progress for each Lesson and Task

In short, Visual Editing works by dynamically setting the React Loader configured by hydrogen-sanity to query for draft documents using the previewDrafts 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.

See the hydrogen-sanity readme for all installation steps if you need further guidance.
See Visual Editing in the Sanity documentation.

Sanity ships some utilities to assist with the setup of Visual Editing.

Install the Visual Editing package into your Hydrogen front end
npm install @sanity/visual-editing

The sanity object passed into context can include a preview value which is true when preview mode is activated.

The token value here will be created later in this lesson. Previews won't work without it.

Update createSanityLoader in your server.ts file to configure server-side data fetching when preview mode is activated.
server.ts
const sanity = createSanityLoader({
// ...other settings
preview: session.get('projectId') === env.SANITY_PROJECT_ID
? {token: env.SANITY_API_TOKEN, studioUrl: 'http://localhost:3333'}
: undefined
})

Use this to determine whether to render a preprepared VisualEditing component is rendered.

This component listens to changes and reloads your server-side data, powering live previews with minimal latency.
Update your root file to include the VisualEditing component when preview mode is active:
./app/root.tsx
// ...other imports
import {VisualEditing} from '@sanity/visual-editing/remix'
export async function loader({context}: LoaderArgs) {
return defer({
// ... other loader data
preview: context.sanity.preview,
})
}
export default function App() {
const data = useLoaderData<typeof loader>()
return (
<html>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
</head>
<body>
<Layout {...data}>
<Outlet />
</Layout>
{data.preview ? <VisualEditing /> : null}
<ScrollRestoration />
<Scripts />
</body>
</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.

This route, and the previous step's VisualEditing component are convenient, opinionated defaults. If necessary you can copy the source and modify these implementation details in your own production projects.
Create a new resource route to handle enabling preview mode
./app/routes/resource.preview.ts
export {loader} from 'hydrogen-sanity/preview/route'

Hydrogen comes preconfigured with strict Content Security Policy (CSP) restrictions – one of which prevents the front end from rendering inside an iframe.

These settings are a single string sent along from the server in the header of the response. The code below modifies a section to allow a specific URL to render the page within an iframe.

You would need to update this code when deploying your application to allow for requests from any other domains – not just localhost.
Update the entry.server.tsx file to allow the Studio to view the site in an iframe when in development.
./app/entry.server.tsx
const projectId = loadContext.env.SANITY_PROJECT_ID
const {nonce, header, NonceProvider} = createContentSecurityPolicy({
// Include Sanity domains in the CSP
defaultSrc: ['https://cdn.sanity.io', 'https://lh3.googleusercontent.com'],
connectSrc: [`https://${projectId}.api.sanity.io`, `wss://${projectId}.api.sanity.io`],
frameAncestors: [`'self'`],
})

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.

Create a new document with the following location configuration
./presentation/locate.ts
import {DocumentLocationResolver, DocumentLocationsState} from 'sanity/presentation'
import {map, Observable} from 'rxjs'
export const locate: DocumentLocationResolver = (params, context) => {
if (params.type === 'page' || params.type === 'product') {
const doc$ = context.documentStore.listenQuery(
`*[_id == $id]{
"title": select(
_type == "page" => title,
_type == "product" => store.title,
null,
),
"href": select(
_type == "page" && defined(slug.current) => "/" + slug.current,
_type == "product" && defined(store.slug.current) => "/products/" + store.slug.current,
null
),
}`,
params,
{perspective: 'previewDrafts'},
) as Observable<
| { title: string, href: string }[]
| null
>
return doc$.pipe(
map((docs) => {
if (!docs) {
return {
message: 'Unable to map document type to locations',
tone: 'critical',
} satisfies DocumentLocationsState
}
return {
locations: docs.filter(Boolean),
} satisfies DocumentLocationsState
}),
)
}
return null
}

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.

Update your sanity.config.ts file
sanity.config.ts
// ./sanity.config.ts
// Add these imports
import {presentationTool} from 'sanity/presentation'
import {locate} from './presentation/locate'
export default defineConfig({
// ...all other settings
plugins: [
presentationTool({
locate,
previewUrl: {previewMode: {enable: 'http://localhost:3000/resource/preview'}},
}),
// ..all other plugins
],
})

A viewer token is required for server-side fetching of draft content as well as "stega encoding" for Visual Editing.

Create a new token with Viewer permissions in Sanity Manage
Add it to the .env file in your Hydrogen application.
.env
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.

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