hydrogen-sanity
TypeScript icon, indicating that this package has built-in type declarations

4.0.2 • Public • Published

hydrogen-sanity

Sanity.io toolkit for Hydrogen. Requires @shopify/hydrogen >= 2023.7.0.

Features:

Note

Using this package isn't strictly required for working with Sanity in a Hydrogen storefront. If you'd like to use @sanity/react-loader and/or @sanity/client directly, see Using @sanity/client directly below.

Installation

npm install hydrogen-sanity
yarn add hydrogen-sanity
pnpm install hydrogen-sanity

Usage

Update the server file to include the Sanity Loader, and optionally, configure the preview mode if you plan to setup Visual Editing

// ./server.ts

// ...all other imports
// Add imports for Sanity Loader and Preview Session
import {createSanityLoader, createClient} from 'hydrogen-sanity'

// Inside the default export
export default () => {
  // ... Leave all other functions like the storefront client as-is

  // (Prerequisite) If not already initialized, create a `withCache` handler...
  const withCache = createWithCache({cache, waitUntil, request})

  // 1. Configure the Sanity Loader and preview mode
  const sanity = createSanityLoader({
    // Required, to cache responses
    withCache,

    // Required:
    client: {
      projectId: env.SANITY_PROJECT_ID,
      dataset: env.SANITY_DATASET,
      apiVersion: env.SANITY_API_VERSION || '2023-03-30',
      useCdn: process.env.NODE_ENV === 'production',

      // In preview mode, `stega` will be enabled automatically
      // If you need to configure the client's steganography settings,
      // you can do so here
      // stega: {
      //   logger: console
      // }
    }),
    // 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 || '2023-03-30',
    //   useCdn: process.env.NODE_ENV === 'production',
    // }),

    // Optionally, set a default cache strategy, defaults to `CacheLong`
    // strategy: CacheShort() | null,

    // Optionally, enable Visual Editing
    // See "Visual Editing" section below to setup the preview route
    preview: env.SANITY_API_TOKEN
      ? {
          enabled: session.get('projectId') === env.SANITY_PROJECT_ID,
          token: env.SANITY_API_TOKEN,
          studioUrl: 'http://localhost:3333',
        }
      : undefined,
  })

  // 2. Make Sanity available to all action and loader contexts
  const handleRequest = createRequestHandler({
    // ...other settings
    getLoadContext: () => ({
      // ...other providers
      withCache,
      sanity,
    }),
  })
}

Update your environment variables with settings from your Sanity project.

  • Copy these from sanity.io/manage
  • or run npx sanity@latest init --env to fill the minimum required values from a new or existing project
# 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_API_TOKEN=""

Satisfy TypeScript

Update the environment variables in Env and AppLoadContext to include the Sanity configuration:

// ./remix.env.d.ts
import type {Sanity} from 'hydrogen-sanity'

declare global {
  // ...other Types

  interface Env {
    // ...other variables
    SANITY_PROJECT_ID: string
    SANITY_DATASET: string
    SANITY_API_VERSION: string
    SANITY_API_TOKEN: string
  }
}

declare module '@shopify/remix-oxygen' {
  export interface AppLoadContext {
    // ...other Types
    sanity: Sanity
  }
}

Interacting with Sanity data

Preferred: Cached fetches using loadQuery

Query Sanity's API and use Hydrogen's cache to store the response (defaults to CacheLong caching strategy).

loadQuery results will skip Hydrogen's caching when in preview mode.

Learn more about configuring caching in Hydrogen on the Shopify documentation.

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 json({initial})
}

loadQuery Request Options

If you need to pass any additional options to the request provide queryOptions like so:

const page = await context.sanity.loadQuery<HomePage>(query, params, {
  // Optionally customize the cache strategy for this request
  hydrogen: {
    strategy: CacheShort(),
    // Or disable caching for this request
    // strategy: 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'
    // }
  },

  // These additional options will be passed to sanity.loadQuery
  queryOptions: {
    tag: 'home',
    headers: {
      'Accept-Encoding': 'br, gzip, *',
    },
  },
})

Alternatively: Using client directly

The Sanity Client is also configured in context, but will not return data in the same shape as loadQuery. It is recommended to use loadQuery for data fetching.

Sanity Client can be used for mutations within actions, for example:

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'),
    })
}

Visual Editing

Enable real-time, interactive live preview inside the Presentation Tool of your Sanity Studio.

First set up your root route to enable preview mode across the entire application, if the preview session is active:

// ./app/root.tsx

// ...other imports
import {VisualEditing} from 'hydrogen-sanity/visual-editing'

export async function loader({context}: LoaderArgs) {
  return json({
    // ... other loader data
    preview: context.sanity.preview?.enabled,
  })
}

export default function App() {
  const {preview, ...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>
        <Outlet />
        {preview ? <VisualEditing /> : null}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  )
}

This Visual Editing component will trigger incremental updates to draft documents from the server for users with a valid preview session. Duplicate its source into your own project if you wish to customize its behavior.

These updates are faster when your initial server-side content is passed through an optional useQuery hook.

// Any route file, such as ./app/routes/index.tsx

// ...all other imports
import {useQuery} from '@sanity/react-loader'

export async function loader({context, params}: LoaderArgs) {
  const query = `*[_type == "page" && _id == $id][0]`
  const params = {id: 'home'}
  const initial = await context.sanity.loadQuery(query, params)

  return json({initial, query, params})
}

// Default export where content is rendered
export default function Index() {
  // Get initial data, passing it as snapshot to render preview...
  const {initial, query, params} = useLoaderData<typeof loader>()
  // Optional, pass query, params and initial data to useQuery for faster updates
  const {loading, data} = useQuery(query, params, initial)

  return loading ? <div>Loading</div> : <Page page={data} />
}

Enabling preview mode

For users to enter preview mode, they will need to visit a route that performs some authentication and then writes to the session.

hydrogen-sanity comes with a preconfigured route for this purpose. It checks the value of a secret in the URL used by Presentation Tool - and if valid - writes the projectId to the Hydrogen session.

Add this route to your project like below, or view the source to copy and modify it in your project.

// ./app/routes/resource.preview.ts

export {loader} from 'hydrogen-sanity/preview/route'

// Optionally, export the supplied action which will disable preview mode when POSTed to
// export {action, loader} from 'hydrogen-sanity/preview/route'

Setup CORS for front-end domains

If your Sanity Studio is not embedded in your Hydrogen App, you will need to add a 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.

Modify Content Security Policy for Studio domains

You may receive errors in the console due to Content Security Policy (CSP) restrictions due to the default configuration. Modify entry.server.tsx to allow any URL that the Studio runs on to display the app in an Iframe.

// ./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'`],
})

Setup Presentation Tool

Now in your Sanity Studio config, import the Presentation tool with the Preview URL set to the preview route you created.

// ./sanity.config.ts

// Add this import
import {presentationTool} from 'sanity/presentation'

export default defineConfig({
  // ...all other settings

  plugins: [
    presentationTool({
      previewUrl: {previewMode: {enable: '/resource/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.

Using @sanity/client instead of hydrogen-sanity

For whatever reason, if you choose not to use hydrogen-sanity you could still configure @sanity/react-loader or @sanity/client to get Sanity content into your Hydrogen storefront.

The following example configures Sanity Client.

// ./server.ts

// ...all other imports
import {createClient} from '@sanity/client';

export default {
  // ... 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 ?? '2023-03-30',
    useCdn: process.env.NODE_ENV === 'production',
  });

  // Pass it along to every request by
  // adding it to `handleRequest`
  const handleRequest = createRequestHandler({
    // ...other settings
    getLoadContext: () => ({
      // ...other context items
      withCache,
      sanity,
    }),
  });
}

Then, in your loaders and actions you'll have access to Sanity Client in context:

export async function loader({context, params}: LoaderArgs) {
  const {sanity} = context
  const homepage = await sanity.fetch(`*[_type == "page" && _id == $id][0]`, {id: 'home'})

  return json({homepage})
}

If you want to cache your query responses in Hydrogen, you can use the withCache utility:

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 json({homepage})
}

Migration Guides

License

MIT © Sanity.io hello@sanity.io

Develop & test

This plugin uses @sanity/pkg-utils with default configuration for build & watch scripts.

Release new version

Run "CI & Release" workflow. 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.

Package Sidebar

Install

npm i hydrogen-sanity

Weekly Downloads

1,757

Version

4.0.2

License

MIT

Unpacked Size

79.7 kB

Total Files

37

Last publish

Collaborators

  • sanity-io