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:
npx croct@latest add slot --exampleThe CLI generates the TypeScript types and a working example you can adapt to your storefront.
The CLI generates type definitions so that fetching a slot returns the correct content type with full autocomplete. It also downloads default content that the SDK uses as automatic fallback when dynamic content is unavailable.
Fetch the content in your loader, then render it from the loader data:
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
You can skip this step if you added the slot using the Croct CLI, since the fallback content is already included based on the slot’s default content. See the fallback hierarchy for how the SDK resolves content.
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:
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:
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:
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:
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};}