Content rendering

Learn how to fetch and render content for your slots.

This guide shows how to fetch and render a slot in your Shopify Hydrogen storefront.

Because Hydrogen renders on the server, you fetch content in your loaders and render it directly from the loader data. The page arrives already personalized, with no client-side flicker.

Basic usage

Start by adding the slot using the CLI:

Command to add a slot to your project
npx croct@latest add slot --example

The CLI generates the TypeScript types and a working example you can adapt to your storefront.

Fetch the content in your loader, then render it from the loader data:

app/routes/_index.jsx
12345678910111213141516171819202122
import {useLoaderData} from 'react-router';import {fetchContent} from '@croct/plug-hydrogen/server';
export async function loader({context}) {  const {content} = await fetchContent('home-hero', {    scope: context,  });
  return {hero: content};}
export default function Index() {  const {hero} = useLoaderData();
  return (    <div>      <strong>{hero.title}</strong>      <p>{hero.subtitle}</p>      <a href={hero.button.link}>{hero.button.label}</a>    </div>  );}

The following examples use the React Router 7 loader signature. On Remix, change the loader arguments type as shown above. For content that must update on the client after the initial load, use the content hook or the slot component.

Typing

If you are using TypeScript, use the slot content type and component content type to type variables and props based on your slot or component schemas:

import type {SlotContent} from '@croct/plug-hydrogen';
type HeroProps = SlotContent<'home-hero@1'>;

These types are automatically available when you add slots or components using the CLI. See type generation for details.

Fault tolerance

Always provide a fallback content to make your storefront resilient to errors, downtime, and network failures. Specify the content to use when the dynamic content is unavailable:

app/routes/_index.jsx
1234567891011121314151617
import {fetchContent} from '@croct/plug-hydrogen/server';
export async function loader({context}) {  const {content} = await fetchContent('home-hero', {    scope: context,    fallback: {      title: 'Welcome to Croct!',      subtitle: 'The easiest way to personalize your storefront.',      button: {        label: 'Get started',        link: '/',      },    },  });
  return {hero: content};}

Version control

Lock a specific slot version to keep the content structure aligned with your storefront’s expectations. Pass a versioned ID in the form <id>@<version>, such as home-hero@2. Omitting the version is the same as requesting the latest one:

app/routes/_index.jsx
123456789
import {fetchContent} from '@croct/plug-hydrogen/server';
export async function loader({context}) {  const {content} = await fetchContent('home-hero@2', {    scope: context,  });
  return {hero: content};}

For more information, see slot versioning.

Localization

By default, the SDK resolves the content locale from the storefront’s internationalization configuration. To request a specific locale, pass the preferred locale in BCP‑47 form:

app/routes/_index.jsx
12345678910
import {fetchContent} from '@croct/plug-hydrogen/server';
export async function loader({context}) {  const {content} = await fetchContent('home-hero', {    scope: context,    preferredLocale: 'en-CA',  });
  return {hero: content};}

If the content is not available in the preferred locale, it is returned in the default locale of your workspace.

Context variables

To personalize content with application-specific data, pass an evaluation context with custom attributes when you fetch. The values become available in the context variable:

app/routes/_index.jsx
1234567891011121314
import {fetchContent} from '@croct/plug-hydrogen/server';
export async function loader({context}) {  const {content} = await fetchContent('upgrade-banner', {    scope: context,    context: {      attributes: {        plan: 'premium',      },    },  });
  return {banner: content};}