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_homepageandcms_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 { shopifyFetch } from "@/lib/shopify/client";
import type { Homepage, MarketingPage } from "@/lib/types";
export async function getHomepage(locale: string): Promise<Homepage | null> {
"use cache: remote";
cacheLife("max");
cacheTag("cms-content");
// Query cms_homepage metaobject, transform to Homepage type
}
export async function getMarketingPage(
slug: string,
locale: string,
): Promise<MarketingPage | null> {
"use cache: remote";
cacheLife("max");
cacheTag("cms-content");
// Query cms_page metaobject by slug, transform to MarketingPage type
}
export async function getAllMarketingPageSlugs(): Promise<
Array<{ slug: string; updatedAt: string }>
> {
"use cache: remote";
cacheLife("max");
cacheTag("cms-content");
// Query all cms_page metaobjects, return slugs
}
2. Write GraphQL queries
Validate field names with shopify-ai-toolkit or vercel-shop:fetch-shopify-schema. 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
sectionsJSON field intoContentSection[] - Resolve product references in sections to
ProductCard[]usinggetProductsByIds - 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 and app/sitemap.ts to use getMarketingPage and getAllMarketingPageSlugs.
5. Add cache invalidation webhook
Create app/api/webhooks/shopify-cms/route.ts:
import { updateTag } from "next/cache";
export async function POST(request: Request) {
// Verify Shopify webhook signature
updateTag("cms-content");
return new Response("OK");
}
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
alternatesfield onMarketingPage— this powers locale-aware URL switching.