--- 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`: ```ts import { shopifyFetch } from "@/lib/shopify/client"; import type { Homepage, MarketingPage } from "@/lib/types"; export async function getHomepage(locale: string): Promise { "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 { "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 `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: ```ts 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 ; return ( ); } ``` 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`: ```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 `alternates` field on `MarketingPage` — this powers locale-aware URL switching.