| name | enable-shopify-cms |
| description | Wire Shopify metaobjects as the CMS for homepage and marketing page content. Adds GraphQL queries for cms_homepage and cms_page metaobject types and transforms them into domain types. |
Enable Shopify CMS
Add Shopify metaobject-based CMS support to the shop template. This replaces the hardcoded homepage content with Shopify-managed content using cms_homepage and cms_page metaobject definitions.
Prerequisites
- Shopify store with metaobject definitions for
cms_homepage and cms_page
- Storefront API access token configured
Metaobject content model
cms_homepage
| Field handle | Type | Description |
|---|
title | single_line_text | Page title |
meta_title | single_line_text | SEO title override |
meta_description | single_line_text | SEO description override |
hero_headline | single_line_text | Hero banner headline |
hero_subheadline | single_line_text | Hero banner subheadline |
hero_image | file | Hero background image |
hero_cta_text | single_line_text | Hero call-to-action label |
hero_cta_link | single_line_text | Hero call-to-action URL |
sections | json | Array of content section definitions |
cms_page
| Field handle | Type | Description |
|---|
slug | single_line_text | URL slug |
title | single_line_text | Page title |
locale | single_line_text | Locale code (e.g. en-US) |
meta_title | single_line_text | SEO title override |
meta_description | single_line_text | SEO description override |
hero_headline | single_line_text | Hero banner headline |
hero_subheadline | single_line_text | Hero banner subheadline |
hero_image | file | Hero background image |
hero_cta_text | single_line_text | Hero call-to-action label |
hero_cta_link | single_line_text | Hero call-to-action URL |
sections | json | Array of content section definitions |
Implementation steps
1. Create lib/shopify/operations/cms.ts
Implement three operations that return domain types from lib/types.ts:
import { defaultLocale } from "@/lib/i18n";
import { assertStorefrontOk } from "@/lib/shopify/errors";
import { storefront } from "@/lib/shopify/storefront";
import type { Homepage, MarketingPage } from "@/lib/types";
export async function getHomepage({
locale = defaultLocale,
}: {
locale?: string;
} = {}): Promise<Homepage | null> {
"use cache";
cacheLife("max");
cacheTag("cms:all", "cms:homepage");
}
export async function getMarketingPage({
slug,
locale = defaultLocale,
}: {
slug: string;
locale?: string;
}): Promise<MarketingPage | null> {
"use cache";
cacheLife("max");
cacheTag("cms:all", "cms:pages", `cms:page:${slug}`);
}
export async function getAllMarketingPageSlugs(): Promise<
Array<{ slug: string; updatedAt: string }>
> {
"use cache: remote";
cacheLife("max");
cacheTag("cms:all", "cms:pages");
}
2. Write GraphQL queries
Use Shopify AI Toolkit's custom-data skill first, then validate the final Storefront GraphQL documents with its Storefront skill. Use metaobjects(type: "cms_homepage") and metaobjects(type: "cms_page") queries with @inContext locale directives.
3. Transform metaobject responses
Create lib/shopify/transforms/cms.ts to convert raw metaobject fields into the domain types:
- Parse the
sections JSON field into ContentSection[]
- Resolve product references in sections to
ProductCard[] using getProductsByIds
- Map hero fields to
HeroSection
- Map image references to
MarketingImage
4. Wire into routes
Update app/page.tsx to use the CMS operation:
import { getHomepage } from "@/lib/shopify/operations/cms";
import { MarketingPageRenderer } from "@/components/cms/page-renderer";
export default async function HomePage() {
const locale = await getLocale();
const page = await getHomepage(locale);
if (!page) return <FallbackHomepage />;
return (
<Container>
<MarketingPageRenderer page={page} />
</Container>
);
}
Update app/pages/[slug]/page.tsx to use getMarketingPage and getAllMarketingPageSlugs. To index CMS pages in the sitemap, add a cms-pages-{n} shard type to app/sitemap/[shard]/route.ts and include it in app/sitemap.xml/route.ts.
The homepage and marketing-page reads use plain "use cache" so their stable bodies can be included coherently in prerendered shells. The slug enumeration uses "use cache: remote" because sitemap and discovery reads can run at request time and share results.
5. Use the existing cache invalidation webhook
Keep metaobject invalidation in app/api/webhooks/shopify/route.ts, which already verifies Shopify's signature and maps metaobjects/* topics to cms:all, cms:homepage, cms:pages, and page-specific tags. Extend that mapping if the content model adds another CMS metaobject type; do not create a second unverified CMS webhook endpoint.
Guardrails
- Return the exact domain types from
lib/types.ts — Homepage, MarketingPage, ContentSection.
- Resolve product references to
ProductCard[] — components expect ready-to-render product data.
- Handle locale fallback gracefully — return default locale content if requested locale is unavailable, never throw.
- Support the
alternates field on MarketingPage — this powers locale-aware URL switching.
- Keep CMS cache tags aligned with
app/api/webhooks/shopify/route.ts.