UNPKG

hydrogen-sanity

Version:
988 lines (773 loc) 33.4 kB
# hydrogen-sanity > [!TIP] > **Upgrading from v4?** See the [migration guide](https://github.com/sanity-io/hydrogen-sanity/blob/main/packages/hydrogen-sanity/MIGRATE-v4-to-v5.md) for breaking changes and new features. 🎉 [Sanity.io](https://www.sanity.io) toolkit for [Hydrogen](https://hydrogen.shopify.dev/). Requires `@shopify/hydrogen >= 2025.5.0`. Learn more about [getting started with Sanity](https://www.sanity.io/docs/getting-started). **Features:** - Drop-in preview mode handling with pre-built route and session management. - Opinionated data fetching that automatically adapts to preview mode. - Interactive live preview with automatic loader detection for [Visual Editing](https://www.sanity.io/docs/loaders-and-overlays) in Sanity's Presentation tool. - Optimized image URL generation hooks with Sanity's image URL builder. - TypeScript support with [Sanity TypeGen](https://www.sanity.io/docs/sanity-typegen). > [!TIP] > > If you'd prefer a self-paced course on how to use Sanity and Hydrogen, check out the [Sanity and Shopify with Hydrogen on Sanity Learn](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen). - [Installation](#installation) - [Add Vite plugin](#add-vite-plugin) - [Usage](#usage) - [Satisfy TypeScript](#satisfy-typescript) - [Set up the Sanity provider](#set-up-the-sanity-provider) - [Interacting with Sanity data](#interacting-with-sanity-data) - [Recommended: Using `query` and `Query` together](#recommended-using-query-and-query-together) - [Alternative: Cached queries using `loadQuery`](#alternative-cached-queries-using-loadquery) - [Additional `loadQuery` options](#additional-loadquery-options) - [Alternative: Direct queries using `fetch`](#alternative-direct-queries-using-fetch) - [Alternative: Using `client` directly](#alternative-using-client-directly) - [Using Sanity TypeGen](#using-sanity-typegen) - [Working with images](#working-with-images) - [Enable preview mode](#enable-preview-mode) - [Configure preview mode](#configure-preview-mode) - [Add Visual Editing component](#add-visual-editing-component) - [Visual Editing configuration options](#visual-editing-configuration-options) - [Visual Editing components](#visual-editing-components) - [Preview mode route](#preview-mode-route) - [Set up CORS for front-end domains](#set-up-cors-for-front-end-domains) - [Modify storefront's Content Security Policy (CSP)](#modify-storefronts-content-security-policy-csp) - [Set up Presentation tool](#set-up-presentation-tool) - [Troubleshooting](#troubleshooting) - [Using `@sanity/client` directly](#using-sanityclient-directly) - [Migration Guides](#migration-guides) - [License](#license) - [Develop & test](#develop--test) - [Release new version](#release-new-version) > [!NOTE] > This package builds on [`@sanity/react-loader`](https://github.com/sanity-io/visual-editing/tree/main/packages/react-loader) and [`@sanity/visual-editing`](https://github.com/sanity-io/visual-editing/tree/main/packages/visual-editing), providing Hydrogen-specific optimizations and caching integration. For non-Hydrogen React applications, consider using these packages directly. > > Using this package isn't required for working with Sanity in a Hydrogen storefront. If you'd prefer to use `@sanity/react-loader` and/or `@sanity/client` directly, see [Using `@sanity/client` directly](#using-sanityclient-directly) below. ## Installation ```sh npm install hydrogen-sanity @sanity/client ``` ```sh yarn add hydrogen-sanity @sanity/client ``` ```sh pnpm install hydrogen-sanity @sanity/client ``` ### Add Vite plugin Add the Vite plugin to your `vite.config.ts`: ```ts import {defineConfig} from 'vite' import {hydrogen} from '@shopify/hydrogen/vite' import {sanity} from 'hydrogen-sanity/vite' export default defineConfig({ plugins: [hydrogen(), sanity() /** ... */], // ... other config }) ``` ## Usage Create the Sanity context and pass it through to your application, and optionally, configure the preview mode if you plan to setup Visual Editing > [!NOTE] > The examples below are up-to-date as of `npm create @shopify/hydrogen@2025.7.0` ```diff // ./lib/context.ts // ...all other imports + import {createSanityContext, type SanityContext} from 'hydrogen-sanity' // Define the additional context object const additionalContext = { // Additional context for custom properties, CMS clients, 3P SDKs, etc. // These will be available as both context.propertyName and context.get(propertyContext) // Example of complex objects that could be added: // cms: await createCMSClient(env), // reviews: await createReviewsClient(env), } as const; // Automatically augment HydrogenAdditionalContext with the additional context type type AdditionalContextType = typeof additionalContext; declare global { interface HydrogenAdditionalContext extends AdditionalContextType { + + // Augment `HydrogenAdditionalContext` with the Sanity context + sanity: SanityContext; } } export async function createHydrogenRouterContext( request: Request, env: Env, executionContext: ExecutionContext, ) { // ... Leave all other functions as-is const waitUntil = executionContext.waitUntil.bind(executionContext) const [cache, session] = await Promise.all([ caches.open('hydrogen'), AppSession.init(request, [env.SESSION_SECRET]), ]) + // Initialize the Sanity context + const sanity = await createSanityContext({ + request, + + // To use the Hydrogen cache for queries + cache, + waitUntil, + + // Sanity client configuration + client: { + projectId: env.SANITY_PROJECT_ID, + dataset: env.SANITY_DATASET || 'production', + apiVersion: env.SANITY_API_VERSION || 'v2024-08-08', + useCdn: process.env.NODE_ENV === 'production', + }, + + // You can also initialize a client and pass it instead + // client: createClient({ + // projectId: env.SANITY_PROJECT_ID, + // dataset: env.SANITY_DATASET, + // apiVersion: env.SANITY_API_VERSION, + // useCdn: process.env.NODE_ENV === 'production', + // }), + + // Optionally, set a default cache strategy, defaults to `CacheLong` + // strategy: CacheShort() | null, + }) + // Make `sanity` available to loaders and actions in the request context const hydrogenContext = createHydrogenContext( { env, request, cache, waitUntil, session, i18n: {language: 'EN', country: 'US'}, cart: { queryFragment: CART_QUERY_FRAGMENT, }, }, + { + ...additionalContext + sanity, + } as const, + ) + return hydrogenContext } ``` Learn more about [Sanity's JavaScript client configuration](https://www.sanity.io/docs/js-client). Update your environment variables with settings from your Sanity project. - Copy these from [sanity.io/manage](https://sanity.io/manage). - Or run `npx sanity@latest init --env` to fill the minimum required values from a new or existing project. ```sh # Project ID SANITY_PROJECT_ID="" # Dataset name SANITY_DATASET="" # (Optional) Sanity API version SANITY_API_VERSION="" # Sanity token to authenticate requests in "preview" mode # must have `viewer` role or higher access # Create in sanity.io/manage SANITY_PREVIEW_TOKEN="" ``` ### Satisfy TypeScript Update the environment variables in `Env` to include the ones you created above: > [!NOTE] > If you plan to reference any environment variables in the client bundle, say for your embedded Studio configuration, you must prefix them with either `PUBLIC_` or `SANITY_STUDIO_` ```diff // ./env.d.ts declare global { // ...other types interface Env extends HydrogenEnv { // ...other environment variables + SANITY_PROJECT_ID: string + SANITY_DATASET?: string + SANITY_API_VERSION?: string + SANITY_PREVIEW_TOKEN: string } } ``` ### Set up the Sanity provider You now need to wrap your app with the Sanity provider to make Sanity context available to client-side hooks and components like `useImageUrl` and `Query`. **Update entry.server.tsx** Wrap your app with the Sanity provider in your server entry point: ```diff // ./app/entry.server.tsx export default async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, reactRouterContext: EntryContext, - context: AppLoadContext, + context: HydrogenRouterContextProvider, ) { + const {SanityProvider} = context.sanity // ... CSP setup etc ... const body = await renderToReadableStream( <NonceProvider> + <SanityProvider> <ServerRouter context={reactRouterContext} url={request.url} nonce={nonce} /> + </SanityProvider> </NonceProvider>, // ... render options ) } ``` **Update root.tsx** Add the `Sanity` component to your root layout: ```diff // ./app/root.tsx + import {Sanity} from 'hydrogen-sanity' export function Layout({children}: {children?: React.ReactNode}) { const nonce = useNonce() return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <Meta /> <Links /> </head> <body> {children} + {/* Add Sanity client-side script */} + <Sanity nonce={nonce} /> <ScrollRestoration nonce={nonce} /> <Scripts nonce={nonce} /> </body> </html> ) } ``` ## Interacting with Sanity data ### Recommended: Using `query` and `Query` together The `query` method and `Query` component work together to provide an optimized data fetching and rendering experience that automatically adapts based on whether Sanity preview mode is active: **Best for**: Most applications wanting opinionated best practices with automatic optimization - **Opinionated approach**: Curated patterns that codify Hydrogen + Sanity best practices - Automatic preview mode detection and switching - Built-in Visual Editing with click-to-edit overlays - **Bundle optimization**: Preview-related code is conditionally loaded only when needed - **Configuration decisions made for you**: Cache strategies, session management, and Visual Editing setup follow recommended approaches **How it works**: - **When preview mode is active**: Dynamically imports `@sanity/react-loader` with `loadQuery` for loader integration and client-side re-rendering with `useQuery` for real-time Studio updates - **When preview mode is inactive**: Uses lightweight direct client `fetch` for optimal performance with static rendering - **Bundle efficiency**: The `Query` component uses React's `lazy()` to conditionally load client-side preview components only when needed, while server-side `loadQuery` imports happen at runtime only when preview mode is detected **Step 1: Fetch data in your loader with `query`** ```ts import {defineQuery} from 'groq' const HOMEPAGE_QUERY = defineQuery(`*[_id == "home"][0]{ _id, title, hero { title, description, image { asset->{ _id, url }, alt } }, modules[] { _type, _type == "productShowcase" => { products[]-> { _id, store { title, slug, previewImageUrl } } } } }`) export async function loader({context}: LoaderFunctionArgs) { const initial = await context.sanity.query(HOMEPAGE_QUERY, undefined, { tag: 'homepage', hydrogen: {debug: {displayName: 'query Homepage'}}, }) return {initial} } ``` **Step 2: Render with the `Query` component** ```tsx import {Query} from 'hydrogen-sanity' export default function HomePage({loaderData}: {loaderData: {initial: any}}) { const {initial} = loaderData return ( <Query query={HOMEPAGE_QUERY} options={{initial}}> {(homepage, encodeDataAttribute) => ( <div> <h1>{homepage?.hero?.title}</h1> <p>{homepage?.hero?.description}</p> {homepage?.modules?.map((module) => { switch (module._type) { case 'productShowcase': return ( <div key={module._key} data-sanity={encodeDataAttribute(['modules', {_key: module._key}, '_type'])} > <div className="products"> {module.products?.map((product) => ( <div key={product._id}> <h3>{product.store?.title}</h3> {product.store?.slug && ( <Link to={`/products/${product.store.slug}`}>View Product</Link> )} </div> ))} </div> </div> ) default: return null } })} </div> )} </Query> ) } ``` > [!NOTE] > The `encodeDataAttribute` function enables click-to-edit functionality in Sanity Studio's Presentation tool. It's only available when Sanity preview mode is active (managed via preview session) and returns `undefined` otherwise. **Advanced Options** Both methods accept the same options as their underlying implementations: ```ts // In your loader const page = await context.sanity.query<HomePage>(queryString, params, { // Hydrogen caching options hydrogen: { cache: CacheShort(), debug: {displayName: 'query Homepage'}, }, // Sanity request options tag: 'home', }) // In your component <Query query={queryString} params={params} options={initial} fallback={<div>Loading...</div>} // React Suspense props > {(data) => <YourComponent data={data} />} </Query> ``` ### Alternative: Cached queries using `loadQuery` **Best for**: Full control over preview mode behavior and loader integration (alternative to `query`/`Query`) - Manual loader integration with real-time Studio updates when preview mode is active - Uses `useQuery` hooks for client-side re-rendering with Sanity Studio Query Sanity's API and use Hydrogen's cache to store the response (defaults to `CacheLong` caching strategy). When Sanity preview mode is active, `loadQuery` automatically bypasses the cache. Learn more about configuring [caching in Hydrogen](https://shopify.dev/docs/custom-storefronts/hydrogen/caching). Sanity queries appear in Hydrogen's [Subrequest Profiler](https://shopify.dev/docs/custom-storefronts/hydrogen/debugging/subrequest-profiler). By default, they're titled `Sanity query`. You can give your queries more descriptive titles by using the request option below. ```ts export async function loader({context, params}: LoaderFunctionArgs) { const query = `*[_type == "page" && _id == $id][0]` const params = {id: 'home'} const initial = await context.sanity.loadQuery(query, params) return {initial} } ``` #### Additional `loadQuery` options If you need to pass any additional options to the request provide `queryOptions` like so: ```ts const page = await context.sanity.loadQuery<HomePage>(query, params, { // Optionally customize the cache strategy for this request hydrogen: { cache: CacheShort(), // Or disable caching for this request // cache: CacheNone(), // If you'd like to add a custom display title that will // display in the Subrequest Profiler, you can pass that here: // debug: { // displayName: 'query Homepage' // }, // You can also pass a function do determine whether or not to cache the response // shouldCacheResult(value){ // return true // }, }, // ...as well as other request options // tag: 'home', // headers: { // 'Accept-Encoding': 'br, gzip, *', // }, }) ``` > [!TIP] > Learn more about [request tagging](https://www.sanity.io/docs/reference-api-request-tags). ### Alternative: Direct queries using `fetch` **Best for**: When preview mode is not needed and bundle optimization is priority - Lightweight with direct client results - No preview or loader integration For Sanity queries that don't need loader integration, there is a `fetch` method that also integrates with Hydrogen's cache: ```ts export async function loader({context, params}: LoaderFunctionArgs) { const query = `*[_type == "page" && _id == $id][0]` const params = {id: 'home'} const page = await context.sanity.fetch<HomePage>(query, params, { hydrogen: { cache: CacheShort(), debug: {displayName: 'fetch Homepage'}, }, tag: 'home', }) return {page} } ``` ### Alternative: Using `client` directly The Sanity client (either instantiated from your configuration or passed through directly) is also available in your app's context. It is recommended to use `query` for data fetching; but the Sanity client can be used for mutations within actions, for example: ```ts export async function action({context, request}: ActionFunctionArgs) { if (!isAuthenticated(request)) { return redirect('/login') } return context.sanity .withConfig({ token: context.env.SANITY_WRITE_TOKEN, }) .client.create({ _type: 'comment', text: request.body.get('text'), }) } ``` ### Using Sanity TypeGen [Sanity TypeGen](https://www.sanity.io/docs/sanity-typegen) generates TypeScript definitions for your [GROQ](https://www.sanity.io/docs/groq) queries. To use TypeGen with `hydrogen-sanity`, install `groq` as a dependency: ```sh npm install groq ``` > [!TIP] > Refer to TypeGen steps covered in the [Sanity TypeGen documentation](https://www.sanity.io/docs/sanity-typegen). TypeGen with `overloadClientMethods: true` uses [declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) to automatically generate types for your GROQ queries. Now your queries will have automatic type inference: ```ts import {defineQuery} from 'groq' const HOMEPAGE_QUERY = defineQuery(`*[_id == "home"][0]`) export async function loader({context, params}: LoaderFunctionArgs) { const params = {id: 'home'} const initial = await context.sanity.loadQuery(HOMEPAGE_QUERY, params) return {initial} } ``` ## Working with images The `useImageUrl` hook provides a convenient way to generate optimized image URLs from Sanity image assets with the [image URL builder](https://www.sanity.io/docs/image-url). ```tsx import {useImageUrl} from 'hydrogen-sanity' function HeroBanner({hero}: {hero: {image: SanityImageSource}}) { const imageUrl = useImageUrl(hero.image) return ( <div className="hero-banner"> <img src={imageUrl.width(1200).height(600).format('auto').url()} alt="Hero banner" width={1200} height={600} /> </div> ) } ``` ## Enable preview mode Enabling preview mode provides real-time content editing with visual overlays inside the [Presentation tool](https://www.sanity.io/docs/presentation). `hydrogen-sanity` includes everything needed to make your storefront Presentation-aware. > [!NOTE] > > These instructions assume some familiarity with Sanity's Visual Editing concepts, like loaders and overlays. To learn more, please visit the [Visual Editing documentation](https://www.sanity.io/docs/introduction-to-visual-editing). ### Configure preview mode For Visual Editing to work, you need to configure Sanity preview mode in your context. Preview mode is a session-based state that gets activated when users visit your storefront through Sanity Studio's Presentation tool or through a preview link shared from Studio. First, initialize the preview session: ```diff // ./lib/context.ts // ...all other imports import {createSanityContext, type SanityContext} from 'hydrogen-sanity' + import {PreviewSession} from 'hydrogen-sanity/preview/session' + import {isPreviewEnabled} from 'hydrogen-sanity/preview' export async function createHydrogenRouterContext( request: Request, env: Env, executionContext: ExecutionContext, ) { // ... Leave all other functions as-is const waitUntil = executionContext.waitUntil.bind(executionContext) - const [cache, session] = await Promise.all([ + const [cache, session, previewSession] = await Promise.all([ caches.open('hydrogen'), AppSession.init(request, [env.SESSION_SECRET]), + // Initialize the preview session + PreviewSession.init(request, [env.SESSION_SECRET]), ]) const sanity = await createSanityContext({ request, cache, waitUntil, client: { projectId: env.SANITY_PROJECT_ID, dataset: env.SANITY_DATASET || 'production', apiVersion: env.SANITY_API_VERSION || 'v2024-08-08', useCdn: process.env.NODE_ENV === 'production', + // Enable stega encoding only when in preview mode + stega: { + enabled: isPreviewEnabled(env.SANITY_PROJECT_ID, previewSession), + }, }, + // Preview configuration + preview: { + token: env.SANITY_PREVIEW_TOKEN, + session: previewSession, + }, }) } ``` > [!NOTE] > **Stega encoding** enables click-to-edit functionality by encoding content source information directly into strings. Learn more about [Content Source Maps and stega](https://www.sanity.io/docs/visual-editing/stega). ### Add Visual Editing component Set up your root route to enable Visual Editing across the entire application when preview mode is active: ```diff // ./app/root.tsx // ...other imports + import {usePreviewMode} from 'hydrogen-sanity/preview' + import {VisualEditing} from 'hydrogen-sanity/visual-editing' export function Layout({children}: {children?: React.ReactNode}) { const nonce = useNonce() const data = useRouteLoaderData<RootLoader>('root') + const previewMode = usePreviewMode() return ( <html lang="en"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1" /> <Meta /> <Links /> </head> <body> {/* ...rest of the root layout */} + {/* Conditionally render `VisualEditing` component only when in preview mode */} + {previewMode ? <VisualEditing action="/api/preview" /> : null} <ScrollRestoration nonce={nonce} /> <Scripts nonce={nonce} /> </body> </html> ) } ``` #### Visual Editing configuration options The `VisualEditing` component provides flexible configuration for different data loading patterns: **Server-Only Setup (default):** Best when using only server-side data fetching (direct `fetch` or `loadQuery` without client components). ```tsx <VisualEditing /> // Overlays only with server revalidation ``` **With Client-Side Loaders (recommended for `Query` component or `useQuery` hooks):** The component automatically detects when you're using client-side loaders (`Query` components or `useQuery` hooks) and enables live mode accordingly: ```tsx // Default usage - automatically enables live mode when needed <VisualEditing action="/api/preview" /> ``` > [!NOTE] > **Automatic Detection**: Live mode automatically activates when: > > - `Query` components are rendered > - `useQuery` hooks are called #### Visual Editing components For advanced use cases, you can use the individual components: ```tsx import {Overlays, LiveMode} from 'hydrogen-sanity/visual-editing' // Overlays only (server-only setups) <Overlays action="/api/preview" /> // Live mode only (client-side data sync) <LiveMode /> // Both (hybrid setups) <Overlays action="/api/preview" /> <LiveMode /> ``` This Visual Editing component provides a complete Visual Editing experience, including: - **Context-aware behavior**: Auto-detects Studio vs standalone preview contexts - **Real-time preview**: Updates content as you edit in Studio - **Visual overlays**: Click-to-edit functionality with element highlighting - **Perspective switching**: Draft/published content switching - **Server revalidation**: Smart refresh logic for server-side data - **Custom revalidation**: Customizable refresh logic for more control ### Preview mode route For Sanity's Presentation tool to activate preview mode, you need to set up a route that handles authentication and session management. When users work in Sanity Studio's Presentation tool, it automatically calls this endpoint to enable preview mode. `hydrogen-sanity` comes with a preconfigured route for this purpose. When Sanity's Presentation tool loads your storefront, it automatically makes requests to this route with a secret token. If the secret is valid, the route activates preview mode by writing the `projectId` to the preview session. > [!NOTE] > > For Visual Editing overlays and click-to-edit functionality to work, you must configure `stega.enabled: true` in your Sanity client configuration. > > You can learn more about [Content Source Maps and working with stega-encoded strings](https://www.sanity.io/docs/stega). Add this route to your project like below, or view the source to copy and modify it in your project. ```tsx // ./app/routes/api.preview.ts export {action, loader} from 'hydrogen-sanity/preview/route' ``` ### Set up CORS for front-end domains If your Sanity Studio is not embedded in your Hydrogen App, you will need to add a Cross-Origin Resource Sharing (CORS) origin to your project for every URL where your app is hosted or running in development. Add `http://localhost:3000` to the CORS origins in your Sanity project settings at [sanity.io/manage](https://sanity.io/manage). Learn more about [CORS configuration in Sanity](https://www.sanity.io/docs/front-ends-and-cors). ### Modify storefront's Content Security Policy (CSP) 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`. > [!TIP] > > Review Hydrogen's [content security policy documentation](https://shopify.dev/docs/storefronts/headless/hydrogen/content-security-policy) to ensure your storefront is secure. ```diff // ./app/entry.server.tsx // ...all other imports export default async function handleRequest( request: Request, responseStatusCode: number, responseHeaders: Headers, reactRouterContext: EntryContext, - context: AppLoadContext, + context: HydrogenRouterContextProvider, ) { + const {env, sanity} = context + const projectId = env.SANITY_PROJECT_ID + const studioHostname = env.SANITY_STUDIO_HOSTNAME || 'http://localhost:3333' + const isPreviewEnabled = sanity.preview?.enabled const {nonce, header, NonceProvider} = createContentSecurityPolicy({ // If your storefront and Studio are on separate domains... // ...allow Sanity assets loaded from the CDN to be loaded in your storefront + defaultSrc: ['https://cdn.sanity.io'], // ...allow Studio to load your storefront in Presentation's iframe + frameAncestors: isPreviewEnabled ? [studioHostname] : [], // If you've embedded your Studio in your storefront... // ...allow Sanity assets to be loaded in your storefront and allow user avatars in Studio + defaultSrc: ['https://cdn.sanity.io', 'https://lh3.googleusercontent.com'], // ...allow client-side requests for Studio to do realtime collaboration + connectSrc: [`https://${projectId}.api.sanity.io`, `wss://${projectId}.api.sanity.io`], // ...allow embedded Studio to load storefront + frameAncestors: [`'self'`], }) // ...and the rest } ``` ### Set up Presentation tool Now in your Sanity Studio config, import the Presentation Tool with the preview URL set to the preview route you created. > [!TIP] > > Consult the [Visual Editing documentation](https://www.sanity.io/docs/introduction-to-visual-editing) for how to [configure the Presentation tool](https://www.sanity.io/docs/configuring-the-presentation-tool). ```diff // ./sanity.config.ts // Add this import + import {presentationTool} from 'sanity/presentation' export default defineConfig({ // ...all other settings plugins: [ + presentationTool({ + previewUrl: { + // If you're hosting your storefront on a separate domain, you'll need to provide an `origin`: + // origin: process.env.SANITY_STUDIO_STOREFRONT_ORIGIN + previewMode: { + // This path is relative to the origin above and should match the route in your storefront that you've setup above + enable: '/api/preview', + }, + }, + }), // ..all other plugins ], }) ``` You should now be able to view your Hydrogen app in the Presentation Tool, click to edit any Sanity content and see live updates as you make changes. > [!NOTE] > > If you're able to see Presentation working locally, but not when you've deployed your application, check that your session cookie is using `sameSite: 'none'` and `secure: true`. > > Since Presentation displays your site in an iframe, the session cookie by default won't be sent through. You can learn more about session cookie configuation in [MDN's documentation](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value). ### Troubleshooting Are you getting the following error when trying to load your storefront in the Presentation Tool? > Unable to connect to Visual Editing. Make sure you've setup '@sanity/visual-editing' correctly Presentation will throw this error if it can't establish a connection to your storefront. Here are a few things to double-check in your setup to try and troubleshoot: 1. Confirm that you've provided `preview` configuration to the Sanity context. 2. Confirm that you've added the `VisualEditing` component to your root layout. 3. If you've followed the instructions above, the `VisualEditing` component will be conditionally rendered if the app has been successfully put into preview mode. 4. If you're using a session cookie, check your browser devtools and confirm that the cookie has been set as expected. 5. Since Presentation loads your storefront in an `iframe`, double check your cookie and CSP configuration. ## Using `@sanity/client` directly If you prefer not to use `hydrogen-sanity`, you can configure [`@sanity/client`](https://www.sanity.io/docs/js-client) directly in your Hydrogen storefront. The following example configures Sanity Client and provides it in the request context. ```diff // ./lib/context.ts // ...all other imports + import {createClient} from '@sanity/client' + import {createSanityContext, type SanityContext} from 'hydrogen-sanity' // Define the additional context object const additionalContext = { // Additional context for custom properties, CMS clients, 3P SDKs, etc. // These will be available as both context.propertyName and context.get(propertyContext) // Example of complex objects that could be added: // cms: await createCMSClient(env), // reviews: await createReviewsClient(env), } as const; // Automatically augment HydrogenAdditionalContext with the additional context type type AdditionalContextType = typeof additionalContext; declare global { interface HydrogenAdditionalContext extends AdditionalContextType { + + // Augment `HydrogenAdditionalContext` with the Sanity context + sanity: SanityContext; + withCache: WithCache; } } export async function createHydrogenRouterContext( request: Request, env: Env, executionContext: ExecutionContext, ) { // ... all other functions + const withCache = createWithCache({cache, waitUntil, request}) + // Create the Sanity Client + const sanity = createClient({ + projectId: env.SANITY_PROJECT_ID, + dataset: env.SANITY_DATASET, + apiVersion: env.SANITY_API_VERSION ?? 'v2025-02-19', + useCdn: process.env.NODE_ENV === 'production', + }) const hydrogenContext = createHydrogenContext( { env, request, cache, waitUntil, session, i18n: {language: 'EN', country: 'US'}, cart: { queryFragment: CART_QUERY_FRAGMENT, }, }, + { + ...additionalContext, + sanity, + withCache, + } as const, + ) + + return hydrogenContext } ``` Then, in your loaders and actions you'll have access to Sanity Client in context: ```ts export async function loader({context, params}: LoaderArgs) { const {sanity} = context const homepage = await sanity.fetch(`*[_type == "page" && _id == $id][0]`, {id: 'home'}) return {homepage} } ``` To cache your query responses in Hydrogen, use the [`withCache` utility](https://shopify.dev/docs/custom-storefronts/hydrogen/caching/third-party#hydrogen-s-built-in-withcache-utility): ```ts export async function loader({context, params}: LoaderArgs) { const {withCache, sanity} = context const homepage = await withCache('home', CacheLong(), () => sanity.fetch(`*[_type == "page" && _id == $id][0]`, {id: 'home'}), ) return {homepage} } ``` ## Migration Guides - [From `v3` to `v4`](https://github.com/sanity-io/hydrogen-sanity/blob/main/packages/hydrogen-sanity/MIGRATE-v3-to-v4.md) - [From `v4` to `v5`](https://github.com/sanity-io/hydrogen-sanity/blob/main/packages/hydrogen-sanity/MIGRATE-v4-to-v5.md) ## License [MIT](LICENSE) © Sanity.io <hello@sanity.io> ## Develop & test This plugin uses [@sanity/pkg-utils](https://github.com/sanity-io/pkg-utils) with default configuration for build & watch scripts. ### Release new version Run ["CI & Release" workflow](https://github.com/sanity-io/hydrogen-sanity/actions/workflows/main.yml). Make sure to select the main branch and check "Release new version". Semantic release will only release on configured branches, so it is safe to run release on any branch.