# Course: Sanity and Shopify with Hydrogen
https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen

Combine Shopify Store products with Sanity's structured content to build an ultra-modern e-commerce website stack. Where Shopify's product data is embellished in Sanity Studio and automatically kept in sync with Sanity Connect.

---

## Navigation

## Contents

1. [Prerequisites](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/hydrogen-prerequisites) · [markdown](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/hydrogen-prerequisites.md)
2. [Preparing your Studio](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/preparing-your-studio) · [markdown](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/preparing-your-studio.md)
3. [Linking Shopify to your Sanity project](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/linking-shopify-to-your-sanity-project) · [markdown](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/linking-shopify-to-your-sanity-project.md)
4. [Creating a Hydrogen front end](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/creating-a-hydrogen-front-end) · [markdown](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/creating-a-hydrogen-front-end.md)
5. [Fetching Shopify products](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/fetching-shopify-products) · [markdown](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/fetching-shopify-products.md)
6. [Fetching Sanity content](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/fetching-sanity-content) · [markdown](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/fetching-sanity-content.md)
7. [Visual Editing for interactive live previews ](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/visual-editing-for-interactive-live-previews) · [markdown](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/visual-editing-for-interactive-live-previews.md)
8. [Next Steps with Hydrogen and Sanity](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/next-steps-with-hydrogen-and-sanity) · [markdown](https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/next-steps-with-hydrogen-and-sanity.md)

---

## Lesson 1: Prerequisites
https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/hydrogen-prerequisites

Sanity Studio is the only CMS to be Shopify Plus Certified, making it a perfect match for building e-commerce experiences. You'll connect them along with the purpose-built Hydrogen front-end framework.

[**Shopify**](https://www.shopify.com/) is the market leader for managing online stores, offering options to create websites using their templating language, "Liquid,” or headless with their APIs. This module will leverage the latter.



[**Hydrogen**](https://hydrogen.shopify.dev/) is a front end framework maintained by Shopify and built on [React Router 7](https://reactrouter.com/) (formerly known as [Remix 2](https://remix.run/)). It powers modern applications and websites with fine-grained controls over data fetching, caching, and delivery. It was designed for headless e-commerce websites.



[**Sanity Studio**](https://www.sanity.io/studio) is the primary content management system for the [Sanity Content Operating System](https://www.sanity.io/). The admin dashboard your content operators will use to embellish raw product details with rich descriptions and connected media to tell more complete stories.



[**Sanity Connect**](https://www.sanity.io/docs/sanity-connect-for-shopify) is a Shopify application to link your Shopify store to your Sanity project. Product changes are instantly written to Sanity, so you are always working with the most up-to-date content, thanks to Sanity’s real-time APIs.



## Prerequisites



The following assumptions are made for you to be able to complete these exercises:



- You have a Shopify Partners account to create demo stores.

- If not, sign up for free at [partners.shopify.com](https://www.shopify.com/partners)

- You have a Sanity account.

- If not, sign up for free at [sanity.io/get-started](https://sanity.io/get-started)

- You are familiar with the **command line** for creating and running code locally.

- You are skilled with **JavaScript**. 

- You are comfortable with code snippets being written in **TypeScript**.

- You have a reasonable level of understanding of **React**. 

- You must have at least Node 20.11 installed.

- CLI commands are written for [**pnpm**](https://pnpm.io/), though you could use whichever package manager you prefer

- Firefox or Chrome (Hydrogen's development environment can be unstable in Safari)


### Create a Shopify development store



Your first task is to log in to your Shopify Partners dashboard and create a development store to use through this module.



- [ ] Create a free **Shopify** development store in the [Shopify Partners](https://www.shopify.com/partners) dashboard.


![Shopify Partners dashboard with "create development store" highlighted](https://cdn.sanity.io/images/3do82whm/next/4e1325720e91f1d3f48ea2b1be7464bc7b81ac17-2144x1388.png)

Your store can have any name.



- In "Purpose," select **"Create a store" **to test and build**.**

- The store can have any available name.

- In "Build version," select** "Current release."**

- In "Data and **configurations," select "Start with an empty store,"** as you'll import test data in a later exercise.


### Alternatives



- If you don’t need an e-commerce store, you may prefer to complete the [Day one content operations](https://www.sanity.io/learn/course/day-one-with-sanity-studio) module, which also looks at setting up Sanity Studio with a Next.js or Remix front-end.

- If you already have a Sanity Studio and want to make it even better, look at the [Studio excellence](https://www.sanity.io/learn/course/studio-excellence) module. This Shopify-specific module will use a Studio template with an existing schema.

- While this module deals exclusively with Shopify, the patterns displayed could be adapted to other e-commerce providers. 


## Ready? 



Let’s begin by setting up Sanity Studio!



---

## Lesson 2: Preparing your Studio
https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/preparing-your-studio

Sanity Studio is where your content creators will go to create and maintain content about your business, such as extended storytelling for your products.

Later, you'll use [Sanity Connect](https://www.sanity.io/docs/sanity-connect-for-shopify) to write product data from Shopify to your Sanity project automatically. 



However, first, you should prepare a Sanity Studio to display that product data, so your content creators can enhance it with additional content.



- [ ] **Run** the following in the command line to create a new project:


```sh
pnpm dlx create-sanity@latest --template shopify --create-project "Sanity and Shopify" --output-path ./studio --dataset production --typescript
```

You may be prompted to log in to your Sanity account.



This command creates a new **project** and **dataset,** and sets up a new project folder with all the files and dependencies you need to get started.



The `shopify` template used by this install command will start your Studio with predefined schema types. These can be extended or removed as you need.



> [!TIP]
> This course does not deeply explore setting up Sanity schema types. Consider completing [Day one content operations](https://www.sanity.io/learn/course/day-one-with-sanity-studio) to build a Sanity Studio from a blank start.


> [!TIP]
> Also see [Studio excellence](https://www.sanity.io/learn/course/studio-excellence) for "next level" considerations to upgrade your Sanity Studio's content editing experience.



Sanity Studio is the ultimate customizable content creation platform. The code you run locally in development writes in real-time to a cloud-hosted content store and can be deployed anywhere you can run a React single-page application.



In the `studio` directory, **run** the following command to start the development server:



```sh
# in /studio
npm run dev
```

Open [http://localhost:3333](http://localhost:3333) in your browser and log in. You should now see a Sanity Studio with a few predefined schema types but no content.



![Sanity Studio using the Shopify starter template](https://cdn.sanity.io/images/3do82whm/next/8edb0224eac74978c9971e19b4b89f00b910da92-2240x1488.png)

You can create content like **Pages** using the schemas registered to the Studio. But for **Products** and **Collections**,** **it is best if that content comes from a single source of truth – Shopify.



In the next exercise, you will connect your Shopify store to Sanity and see the content update in the Studio.



---

## Lesson 3: Linking Shopify to your Sanity project
https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/linking-shopify-to-your-sanity-project

You now have a Shopify-compatible Sanity Studio. Let's fill it with Shopify product and collection content. 

## Visit your development store



You were asked to create a new development store in [Shopify Partners](https://partners.shopify.com) in the Prerequisites section. Locate that project, or create one if you haven't already done so.



## Set up Sanity Connect



For your convenience, Sanity has created the Shopify app [Sanity Connect](https://www.sanity.io/docs/sanity-connect-for-shopify)*. *It seamlessly syncs data from a Shopify store to a Sanity project.



If you have multiple Shopify stores, you may need to click "Switch stores" in the top right to install Sanity Connect into your new development store.



![Image](https://cdn.sanity.io/images/3do82whm/next/1be25634854beac7e8c5e17e181117335e6ef0a1-2144x1388.png)

- [ ] **Install** the [Sanity Connect](https://apps.shopify.com/sanity-connect) app on your development store and follow the prompts.

- [ ] **Connect** the development Shopify Store to the Sanity project you created in the previous exercise.


![Shopify Connect app screen on Shopify store dashboard](https://cdn.sanity.io/images/3do82whm/next/f3c6176a70efe32e14396619d45074b59d117ab1-2240x1488.png)

You will use the **default** sync options for this exercise:



- Choose **Direct sync**

- Choose **Sync automatically**


Now connected, Sanity Connect will automatically write product data from Shopify into your project whenever changes are made. The Studio you created in the previous exercise has the schema type shapes required to view this content.



In a production or other project, it’s possible to use a custom sync function to modify the fields synced to Sanity. While automatic sync—powered by Shopify webhooks—is typically sufficient for most use cases, it’s also possible to trigger it manually.



## Add some products



Now you have a Shopify store connected to Sanity with Sanity Connect – but there are no products to display. 



- [ ] [Download this CSV file](https://cdn.sanity.io/files/3do82whm/next/ff648931c35ad2ac6f3f1d4c8f4e30ef9da23446.csv), go to **Products** in Shopify admin, and import it into your development store


The import may take a little time; once finished, it'll look like the one below. But while you wait...



![Shopify dashboard showing imported products](https://cdn.sanity.io/images/3do82whm/next/dd34934146c4380571ba07491a5fb91184a390a8-2240x1488.png)

...take a look at your Sanity Studio and watch as the products begin importing immediately, thanks to Sanity Connect.



![Sanity Studio showing product documents](https://cdn.sanity.io/images/3do82whm/next/9c81d47d85143d595614d001288010360969a557-2240x1488.png)

Try publishing some changes in Shopify to these products – such as pricing or availability – you’ll see them update in Sanity.



You’ll also notice in Sanity Studio that the fields this data is being written to are read-only. It’s not the intention for Sanity Studio to be a place to edit product details, just to embellish and editorialize those that come from Shopify, as it is the source of truth.



The editable fields on a product add extra information about a product. 



> [!TIP]
> There are a few assumptions in the Shopify / Sanity setup. For example, the HTML description is synced from Shopify as a locked `readOnly` field in Sanity. Extra content should be added using [Portable Text](https://www.sanity.io/docs/block-content) in Sanity. Read more about other assumptions made in the [Shopify Studio repo](https://github.com/sanity-io/sanity-shopify-studio#assumptions).



## Going further



With more work, you could add schemas for different product types, develop a content structure for variants and products, or create new document types to build a complex content model.



For now, let’s press on with just the product details.



The next step is to create a front end to display this content. Let’s set up Hydrogen.



---

## Lesson 4: Creating a Hydrogen front end
https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/creating-a-hydrogen-front-end

Hydrogen is a front-end framework based on Remix and preconfigured to query from Shopify stores. 

> [!TIP]
> If you get stuck, [consult the Hydrogen documentation](https://hydrogen.shopify.dev/) for installation instructions


- [ ] Initiate a new Hydrogen project from the command line in a **separate directory** from your Sanity Studio.


```sh
pnpm create @shopify/hydrogen@latest --install-deps --mock-shop --path web --language ts --shortcut h2 --styling tailwind
```

The command above sets a number of defaults such as TypeScript, Tailwind and the directory of the project.



Follow the prompts:



- When prompted, choose "**Set up later"** for any additional routing options


### Check your project structure



You should now have two **separate** directories, one for your Sanity Studio, the other for your Hydrogen app. 



```
./
├── /studio # Sanity Studio
└── /web    # Hydrogen front-end
```

This will be important later when we begin generating Types from the Studio for Hydrogen.



### Connecting Hydrogen to Shopify



For your development store you'll need to install the Headless Shopify app to connect your front end to Shopify.



> [!TIP]
> If you're **not** working on a development store you can connect by choosing a shop with the Hydrogen / Oxygen sales channel using `h2 link` – but development stores don't currently support this. We highly recommend switching to a paid plan to test Oxygen deployments if that's the final destination of the experience, as it's easier to troubleshoot deploy issues as you build!



## Install Headless



The `web/.env` file in your project will need to be updated with your Shopify URL and access tokens from the Headless sales channel. 



- [ ] Install the [Headless sales channel](https://apps.shopify.com/headless) onto your dev store


![Shopify "headless" app install page](https://cdn.sanity.io/images/3do82whm/next/035e8a5fef43b766ceff5e5deb52fa62b0311836-2144x1388.png)

Once installed, click the "Manage" button next to **Storefront API**, copy your access tokens and insert them into the `.env` file your project.



![Shopify admin showing access tokens](https://cdn.sanity.io/images/3do82whm/next/7b91a0e873072696fa81308c51db6e2eb72747e6-2240x1488.png)

- [ ] **Update** your `.env` file to look something like the below


```text:web/.env
# Found in Storefront API section of the Headless App in Shopify Admin
PUBLIC_STOREFRONT_API_TOKEN="..."
PRIVATE_STOREFRONT_API_TOKEN="shpat_..."
# Found in your Shopify dashboard
PUBLIC_STORE_DOMAIN="<your-domain>.myshopify.com"
PUBLIC_CHECKOUT_DOMAIN="<your-domain>.myshopify.com"
# Any random string, but it must be kept secret
SESSION_SECRET="..."
```

## Run it!



Now, enter the `web` folder, run the application in development and open [http://localhost:3000](http://localhost:3000) in your browser.



```sh
# in /web
pnpm run dev
```

> [!TIP]
> If you see a message about connecting your storefront, it must not be reading the contents of your `.env` file correctly.



You should see a page like the one below with instructions on setting up a route.



![Hello page of a new Hydrogen application](https://cdn.sanity.io/images/3do82whm/next/b07833833f8693b1d6dfd73a2c9fb4f609cf8fcf-2240x1488.png)

Hydrogen is now connected to Shopify and data-ready!



---

## Lesson 5: Fetching Shopify products
https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/fetching-shopify-products

With the Hydrogen app connected to Shopify, you will next need to create “routes” to fetch and display products. 

For a more functional Hydrogen app, we’ll need these **three** new route documents.



## Create the “home” route



> [!TIP]
> If you get stuck or need more details, see the [React Router documentation on routes](https://reactrouter.com/start/framework/routing).


- [ ] **Create** a home page route with a link to the product index page.


```tsx:web/app/routes/_index.tsx
import {Link} from 'react-router';

export default function Index() {
  return (
    <div className="mx-auto p-12 grid grid-cols-1 gap-4">
      <h1 className="text-3xl font-bold">Home</h1>
      <p>
        <Link className="text-blue-500 underline" to="/collections/all">
          All Products
        </Link>
      </p>
    </div>
  );
}
```

The home page of your Hydrogen App should look much like the image below.



![Home page of a new blank store](https://cdn.sanity.io/images/3do82whm/next/bb399783ddc61d2245cec8f9e87336c7fe555272-2144x1388.png)

You can click the “All Products” link, but it will 404.



## Create the “products” route



This route uses React Router's [loader function convention](https://reactrouter.com/start/data/route-object#loader) to query data on the server and pass it down to the client.



Hydrogen has a preconfigured `storefront` variable which is available to all loaders via the `context` variable. This is explained further in the next exercise.



Using `storefront`, queries to Shopify can be performed. 



- [ ] **Create** a products index route


```tsx:web/app/routes/collections.all.tsx
import {Link, useLoaderData, type LoaderFunctionArgs} from 'react-router';
import {type Product} from '@shopify/hydrogen/storefront-api-types';

export async function loader({
  params,
  context: {storefront},
}: LoaderFunctionArgs) {
  const {products} = await storefront.query<{
    products: {nodes: Product[]};
  }>(
    `#graphql
      query Products {
        products(first: 10) {
          nodes { id title handle }
        }
      }
    `,
  );

  if (!products.nodes.length) {
    throw new Response('Not found', {status: 404});
  }

  return {products: products.nodes};
}

export default function Index() {
  const {products} = useLoaderData<typeof loader>();

  return (
    <div className="mx-auto p-12 grid grid-cols-1 gap-4">
      <h1 className="text-3xl font-bold">All Products</h1>
      {products.map((product) => (
        <p key={product.id}>
          <Link className="text-blue-500" to={`/products/${product.handle}`}>
            {product.title}
          </Link>
        </p>
      ))}
    </div>
  );
}
```

Visit `/collections/all` on your Hydrogen store now, you should see a list of up to 10 products with links to their individual product pages. 



![Products index page showing products from Shopify](https://cdn.sanity.io/images/3do82whm/next/6eb7b95a1e0def53169f9ec49da868317d252f26-2144x1388.png)

These will return 404 if you click them; let’s fix that.



## Create the “product” route



This route below also uses the same loader to fetch data but also takes advantage of the variable `$handle` to find a specific product.



- [ ] **Create** a new product page route


```tsx:web/app/routes/products.$handle.tsx
import {Link, useLoaderData, type LoaderFunctionArgs} from 'react-router';
import {type Product} from '@shopify/hydrogen/storefront-api-types';

export async function loader({
  params,
  context: {storefront},
}: LoaderFunctionArgs) {
  const {product} = await storefront.query<{product: Product}>(
    `#graphql
      query Product($handle: String!) {
        product(handle: $handle) { id title }
      }
    `,
    {variables: params},
  );

  return {product};
}

export default function Product() {
  const {product} = useLoaderData<typeof loader>();

  return (
    <div className="mx-auto p-12">
      <h1 className="text-3xl font-bold">{product.title}</h1>
      <Link className="text-blue-500" to="/collections/all">
        &larr; Back to All Products
      </Link>
    </div>
  );
}
```

Now you should be able to view a specific product from the `/products` page and click through.



![Individual product page showing content from Shopify](https://cdn.sanity.io/images/3do82whm/next/e090166993538ca182182298051a8cdade9d0837-2144x1388.png)

If your site looks like this, congratulations! Your Hydrogen app now queries Shopify content directly from your store.



What you don’t have is the additional content from your Sanity Studio. Let’s fix that next.



---

## Lesson 6: Fetching Sanity content
https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/fetching-sanity-content

Your front-end is only telling half the story. Bring your products to life with extra content from your Sanity project. 

## Install Sanity packages



The `hydrogen-sanity` package contains a number of useful functions to simplify querying and displaying content from Sanity.



- [ ] **Run** the following command to install** **`hydrogen-sanity` and other packages


```sh
# in the /web directory
pnpm add hydrogen-sanity @sanity/client @portabletext/react
```

- [hydrogen-sanity](https://github.com/sanity-io/hydrogen-sanity) is a multi-purpose toolkit for integrating Sanity with Hydrogen

- [@sanity/client](https://github.com/sanity-io/client) is the primary way for JavaScript applications to interact with Sanity content and API's

- [@portabletext/react](https://www.npmjs.com/package/@portabletext/react) is a React component for rendering block content which is authored in Sanity Studio


### Update Vite config



You may encounter a Vite error in the next few steps, to get ahead of that we'll add the following to our `vite.config.ts` file:



```typescript:web/vite.config.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
})
```

### Create a client in the React Router server context



The Sanity Client allows you to query (and mutate) content.



You’ll need a Sanity Client configured with your Project ID and dataset name available to any route. 



Add your Sanity project details to the Hydrogen project's `.env` file. You can find these either in [sanity.io/manage](https://sanity.io/manage) or in the `sanity.config.ts` file of your Studio.



- [ ] **Update** the `web/.env` file to include Sanity project details.


```text:web/.env
# Project ID
SANITY_PROJECT_ID=""
# Dataset name
SANITY_DATASET=""
# (Optional) Sanity API version
SANITY_API_VERSION=""
```

- [ ] **Update** the TypeScript configuration by adding these variables to Remix's global types.


```typescript:web/env.d.ts
// ...all other type settings

declare global {
  interface Env extends HydrogenEnv {
    SANITY_PROJECT_ID: string;
    SANITY_DATASET: string;
    SANITY_API_VERSION: string;
    SANITY_API_TOKEN: string;
  }
}
```

The code example below contains new lines to paste into your Hydrogen project’s context file, this was previously handled in server.ts.



- [ ] **Update** `web/lib/context.ts` and update it to include Sanity Client


```typescript:web/app/lib/context.ts
import {createHydrogenContext} from '@shopify/hydrogen';
import {createSanityContext, type SanityContext} from 'hydrogen-sanity';
import {AppSession} from '~/lib/session';
import {CART_QUERY_FRAGMENT} from '~/lib/fragments';

// ...other types and imports

declare global {
  interface HydrogenAdditionalContext extends AdditionalContextType {
    // Augment `HydrogenAdditionalContext` with the Sanity context
    sanity: SanityContext;
  }
}

/**
 * Creates Hydrogen context for React Router 7.9.x
 * Returns HydrogenRouterContextProvider with hybrid access patterns
 * */
export async function createHydrogenRouterContext(
  request: Request,
  env: Env,
  executionContext: ExecutionContext,
) {
  // ...other functions

  // 1. Add `sanity` configuration
  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 || 'v2025-11-01',
      useCdn: process.env.NODE_ENV === 'production',
    },
  });

  // 2. Make `sanity` available to loaders and actions in the request context
  const hydrogenContext = createHydrogenContext(
    {
      env,
      request,
      cache,
      waitUntil,
      session,
      // Or detect from URL path based on locale subpath, cookies, or any other strategy
      i18n: {language: 'EN', country: 'US'},
      cart: {
        queryFragment: CART_QUERY_FRAGMENT,
      },
    },
    {...additionalContext, sanity},
  );

  return hydrogenContext;
}
```

Now a configured Sanity React Loader is available inside the Hydrogen “context” as `sanity`, just like the preconfigured `storefront`. 



You'll use `loadQuery` to retrieve content from Sanity. It's configured in a way to make setting up Visual Editing simple. For now, you'll just use the initial data in the default export to render content.



- [ ] **Update** the route for all product pages to query for Sanity content


```tsx:web/app/routes/products.$handle.tsx
import {Link, useLoaderData, type LoaderFunctionArgs} from 'react-router';
import {type Product} from '@shopify/hydrogen/storefront-api-types';
import {PortableText, type PortableTextBlock} from '@portabletext/react';

export async function loader({
  params,
  context: {storefront, sanity},
}: LoaderFunctionArgs) {
  const {product} = await storefront.query<{product: Product}>(
    `#graphql
      query Product($handle: String!) {
        product(handle: $handle) { id title }
      }
    `,
    {variables: params},
  );

  const PRODUCT_QUERY = `*[_type == "product" && store.slug.current == $handle][0]{
    body,
    "image": store.previewImageUrl
  }`;

  const initial = await sanity.fetch<{
    body: PortableTextBlock[] | null;
    image: string | null;
  } | null>(PRODUCT_QUERY, params, {
    tag: 'homepage',
    hydrogen: {debug: {displayName: 'query Homepage'}},
  });

  if (!initial) {
    throw new Response('Product not found', {status: 404});
  }

  return {product, initial};
}

export default function Product() {
  const {product, initial} = useLoaderData<typeof loader>();

  return (
    <div className="mx-auto p-12 grid grid-cols-1 gap-4">
      <h1 className="text-3xl font-bold">{product.title}</h1>
      {initial?.image ? (
        <img
          alt={product.title}
          src={initial.image}
          className="size-32 mb-6 mr-6 object-cover float-left rounded-xl"
        />
      ) : null}
      {Array.isArray(initial?.body) ? (
        <PortableText value={initial.body} />
      ) : null}
      <Link className="text-blue-500" to="/collections/all">
        &larr; Back to All Products
      </Link>
    </div>
  );
}
```

> [!TIP]
> Read the [documentation on GROQ](https://www.sanity.io/docs/groq) for more on querying content from Sanity



Visit any product page now, and you should see both the product title from Shopify and the contents of the Portable Text field in the Sanity document for that same product. 



If there's no extra text showing, [edit the LULU Mini Pot product](http://localhost:3333/structure/products;shopifyProduct-9024565674264;shopifyProduct-9024565674264) in your Studio and add the following text:



```
All of our products are made from 100% recycled materials and are finished with a waterproof sealant. We recommend cleaning your LULU Pot regularly with hot soapy water. Please do not leave the pot to soak. Alternatively our products are dishwasher safe.
```

Your page should now look like this.



![Product display page](https://cdn.sanity.io/images/3do82whm/next/f124d9d1f445fd2bf2a21ebe1028e9c14f097884-2144x1388.png)

If your CSS looks slightly off, try emptying the contents of `reset.css` as it conflicts with some Tailwind Typography styles.



Your Hydrogen app now queries content from both Sanity and Shopify independently, displaying in a consistent front-end.



Sanity content is more than just a paragraph of text – let’s embellish this with some rich content blocks!



---

## Lesson 7: Visual Editing for interactive live previews 
https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/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.

In short, Visual Editing works by dynamically setting the React Loader configured by `hydrogen-sanity` to query for draft documents using the `drafts` [perspective](https://www.sanity.io/docs/perspectives). 



As well as include "[stega encoding](https://www.sanity.io/docs/loaders-and-overlays)" 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](https://www.sanity.io/docs/presentation).



> [!TIP]
> See the `hydrogen-sanity` [readme](https://github.com/sanity-io/hydrogen-sanity/) for all installation steps if you need further guidance.


> [!TIP]
> See [Visual editing](https://www.sanity.io/learn/visual-editing/introduction-to-visual-editing) in the Sanity documentation.



## Update your Hydrogen front end



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.



### Server context



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



- [ ] **Update** `createSanityContext` in your `context.ts` file to configure server-side data fetching when preview mode is activated.


```typescript:web/app/lib/context.ts
// ...all other imports
import {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. 



### Root route



> [!TIP]
> 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:


```tsx:web/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}) {
  // ...all other logic
  const previewMode = usePreviewMode()
  
  return (
    <html lang={currentLanguage}>
      // ...all other components
      <Sanity nonce={nonce} />
      {previewMode ? <VisualEditing action="/api/preview" /> : null}
    </html>
  );
}
```

### Preview route



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.



> [!TIP]
> 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


```typescript:web/app/routes/api.preview.ts
export {action, loader} from 'hydrogen-sanity/preview/route'
```

### Content security policy



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](https://github.com/sanity-io/hydrogen-sanity?tab=readme-ov-file#modify-storefronts-content-security-policy-csp).



- [ ] **Update** `entry.server.tsx` with configuration to enable viewing within an iframe.


```tsx:web/entry.server.tsx
// ...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
}
```

## Update your Sanity Studio



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.



### Setup locations



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](https://www.sanity.io/docs/configuring-the-presentation-tool#585b86f5d415).



- [ ] **Create** a new document** **with the following location configuration


```typescript:studio/presentation/locations.ts
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`},
        ],
      }),
    }),
  }
```

### Setup Presentation tool



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


```typescript:studio/sanity.config.ts
// Add these imports
import { 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.



### Sanity Manage: Create a viewer token



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:



```sh
# In the /studio folder
pnpm dlx sanity manage
```

- [ ] **Create** a new token with Viewer permissions in Sanity Manage


![Tokens in Sanity Manage](https://cdn.sanity.io/images/3do82whm/next/e9a4db4ffd99a9f4b010ab5f66ff2b2921f9ede2-2144x1388.png)

- [ ] Add it to the `.env` file in your Hydrogen application.


```:web/.env
SANITY_API_TOKEN="sk..."
```

### Sanity Manage: Update CORS origins



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](https://sanity.io/manage).


![CORS origins settings in Sanity Manage](https://cdn.sanity.io/images/3do82whm/next/c920718a3366bfbd668390cc463c612e0ca2189d-2144x1388.png)

### Start editing, visually!



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.



![Sanity Studio showing Presentation tool](https://cdn.sanity.io/images/3do82whm/next/a84b0ceabb27a7b0945f4821eef93181d8e7ef6d-2240x1488.png)

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.



---

## Lesson 8: Next Steps with Hydrogen and Sanity
https://www.sanity.io/learn/course/sanity-and-shopify-with-hydrogen/next-steps-with-hydrogen-and-sanity

Now that you've got some simple content rendered in Hydrogen, we wanted to give you some ideas for elevating experiences using this stack.

## Popping product pages



Check out [5 Ways To Make Your E-commerce Product Pages Pop](https://www.youtube.com/watch?v=SiCK6qC8mPc), a video from our co-founder Simen.



- Create some **brand-specific content** and display this on each product from this brand.

- **Programmatically** change the product page layout based on the type of product you’re showing - lifestyle products like a fire pit deserve a better layout than a replacement bolt!

- Use your Sanity Studio to add personalization to your storefront by configuring the display of different product references on a page according to which group a customer is in on Shopify.

- Create fields related to **product specifications** and display these in a table on-site.

- **Call out a key specification** (according to the product type) at the top of the page rather than in the main spec table.

- Show **images related to specifications** alongside a specification table rather than in the main gallery based on image metadata.

- Create content **linked to products based on data and category**. For example, for a nutritional product, create an ingredients document type and tell the story of this ingredient, where it comes from, and the benefits of it on the product page.

- Create **image hotspots** to draw attention to certain product features.

- Create a document/field variation model to facilitate **A/B testing** of product content.


## Build out your Hydrogen store



- See the Hydrogen documentation for how to set up e-commerce components like [Add to Cart](https://shopify.dev/docs/api/hydrogen-react/2023-04/components/addtocartbutton)

- Extend the storefront query to include more details from the product, like variants and images


### Deploy Hydrogen to Oxygen



Hydrogen applications can be deployed to many different hosting environments. Oxygen is a hosting environment provided by Shopify and may be preferable for Hydrogen applications. 



> [!TIP]
> [Learn more in the Oxygen documentation](https://shopify.dev/docs/custom-storefronts/oxygen/getting-started).



## Build out your Sanity Studio



[Studio excellence](https://www.sanity.io/learn/course/studio-excellence) module is an overview of how you can refine the experience of creating content for your authors. The preconfigured e-commerce starter for this project has many features, but you can do so much more!



- Install plugins like [Media](https://www.sanity.io/plugins/sanity-plugin-media) and [Scheduled Publishing](https://www.sanity.io/plugins/scheduled-publishing)

- Colorize your Studio with a custom logo and brand colors using [Themer](https://themer.sanity.build/)


### Deploy your Sanity Studio



Your Sanity Studio can be deployed anywhere you can host a React application. Sanity offers one Studio hosting per project, more complex setups may benefit from hosting on other providers.



> [!TIP]
> [More details about deploying your Sanity Studio](https://www.sanity.io/docs/deployment)



---

## Related Resources

- [All courses and lessons](https://www.sanity.io/learn/sitemap.md)
- [Complete content for LLMs](https://www.sanity.io/learn/llms-full.txt)
