用途
enable-shopify-cms 用于给 shop 模板添加基于 Shopify metaobject 的 CMS 支持。它把原本硬编码的首页内容替换为 Shopify 托管的内容,使用 cms_homepage 和 cms_page 两种 metaobject 定义来驱动首页与营销页。
前提条件
- Shopify 店铺中已定义 cms_homepage 和 cms_page 的 metaobject 定义
- 已配置 Storefront API 访问令牌
内容模型
cms_homepage 字段
- title(single_line_text):页面标题
- meta_title(single_line_text):SEO 标题覆盖
- meta_description(single_line_text):SEO 描述覆盖
- hero_headline(single_line_text):Hero 横幅主标题
- hero_subheadline(single_line_text):Hero 横幅副标题
- hero_image(file):Hero 背景图
- hero_cta_text(single_line_text):Hero 行动号召标签
- hero_cta_link(single_line_text):Hero 行动号召 URL
- sections(json):内容区块定义数组
cms_page 字段
- slug(single_line_text):URL slug
- title(single_line_text):页面标题
- locale(single_line_text):语言代码(如 en-US)
- meta_title(single_line_text):SEO 标题覆盖
- meta_description(single_line_text):SEO 描述覆盖
- hero_headline(single_line_text):Hero 横幅主标题
- hero_subheadline(single_line_text):Hero 横幅副标题
- hero_image(file):Hero 背景图
- hero_cta_text(single_line_text):Hero 行动号召标签
- hero_cta_link(single_line_text):Hero 行动号召 URL
- sections(json):内容区块定义数组
实现步骤
1. 创建 lib/shopify/operations/cms.ts
实现三个返回 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");
// Query cms_homepage metaobject, transform to Homepage type
}
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}`);
// 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:all", "cms:pages");
// Query all cms_page metaobjects, return slugs
}
2. 编写 GraphQL 查询
先使用 Shopify AI Toolkit 的 custom-data 技能,再用其 Storefront 技能校验最终的 Storefront GraphQL 文档。使用 metaobjects(type: "cms_homepage") 和 metaobjects(type: "cms_page") 查询,并配合 @inContext locale 指令。
3. 转换 metaobject 响应
创建 lib/shopify/transforms/cms.ts,把原始 metaobject 字段转换为领域类型:
- 将 sections JSON 字段解析为 ContentSection[]
- 使用 getProductsByIds 把 sections 中的商品引用解析为 ProductCard[]
- 将 hero 字段映射为 HeroSection
- 将图片引用映射为 MarketingImage
4. 接入路由
更新 app/page.tsx 使用 CMS 操作:
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>
);
}
更新 app/pages/[slug]/page.tsx 使用 getMarketingPage 和 getAllMarketingPageSlugs。若要在站点地图中索引 CMS 页面,需在 app/sitemap/[shard]/route.ts 中添加 cms-pages-{n} 分片类型,并在 app/sitemap.xml/route.ts 中包含它。
首页和营销页读取使用普通 "use cache",以便其稳定内容能一致地包含在预渲染外壳中。slug 枚举使用 "use cache: remote",因为站点地图和发现类读取可在请求时运行并共享结果。
5. 使用现有的缓存失效 webhook
将 metaobject 失效逻辑保留在 app/api/webhooks/shopify/route.ts 中,该文件已校验 Shopify 签名,并把 metaobjects/* 主题映射到 cms:all、cms:homepage、cms:pages 以及页面级标签。如果内容模型新增了其他 CMS metaobject 类型,请扩展该映射;不要创建第二个未校验的 CMS webhook 端点。
约束
- 返回 lib/types.ts 中确切的领域类型:Homepage、MarketingPage、ContentSection。
- 将商品引用解析为 ProductCard[],组件期望可直接渲染的商品数据。
- 优雅处理语言回退:当请求的语言不可用时返回默认语言内容,绝不抛出异常。
- 支持 MarketingPage 上的 alternates 字段,它用于驱动语言感知的 URL 切换。
- 保持 CMS 缓存标签与 app/api/webhooks/shopify/route.ts 一致。