Skill v1.0.2
Trusted Publisher100/100Derive NEXT_PUBLIC_AUTH_ENABLED at build time; expose isAuthEnabled from lib/auth (#200)
version: "1.0.2" 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 signatureupdateTag("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.