关于 shopify-hydrogen
面向需要定制 Shopify 前端的开发者,介绍使用 Hydrogen 构建无头店面的流程。该 React 框架提供 Storefront API 查询、购物车工具和缓存策略,并可部署到 Oxygen。
主要功能
- 01基于 React 与 Remix 构建店面
- 02查询 Shopify Storefront API
- 03使用 Hydrogen 工具管理购物车
- 04配置缓存策略
- 05部署到 Oxygen
适用场景
- 01定制 Shopify 店面的设计与体验
- 02为产品和集合页面获取数据
- 03实现服务端购物车操作
- 04将 Hydrogen 店面部署到 Oxygen
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ finsilabs/ awesome-ecommerce-skills --skill shopify-hydrogen安装后在 Claude Code 中输入/shopify-hydrogen确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
tile.jsonevals/cart-implementation-with-server-side-sta/criteria.jsontask.mdevals/project-configuration-and-deployment/criteria.jsontask.mdevals/storefront-api-querying-and-caching/criteria.jsontask.md详细介绍
Shopify Hydrogen
概述
Hydrogen 是 Shopify 官方推出的基于 React 的无头店面构建框架,基于 Remix 构建,并部署到 Oxygen(Shopify 的边缘托管平台)。它为 Storefront API 提供一流的基础功能——商品查询、购物车管理、客户账户——以及 Shopify 专用组件和钩子,可自动处理缓存、流式传输和 SEO。本技能涵盖 Hydrogen 项目的脚手架搭建、Storefront API 查询、购物车功能实现以及部署到 Oxygen。
何时使用此技能
- 需要构建自定义 Shopify 店面,并完全掌控设计和用户体验时
- Shopify Online Store 的默认主题无法满足设计要求时
- 需要服务端渲染、流式传输和边缘部署性能时
- 需要将第三方服务(忠诚度计划、CMS、个性化服务)直接集成到店面时
- 希望使用 Shopify 管理的后端,同时采用完全自定义的前端技术栈时
先决条件与平台说明
本技能适用于自定义/无头店面(Node.js、Python 或类似后端)。代码示例使用 TypeScript/Node.js,可适配任何技术栈。
Shopify:Shopify Hydrogen 是 Shopify 的无头框架。将 Shopify 用作电商后端并搭配自定义前端,或将 Shopify 与其他同类最佳服务结合使用时,可采用 MACH/可组合模式。 WooCommerce:WooCommerce 可通过其 REST API 和 WPGraphQL 充当无头后端。将前端与 WordPress 解耦时,可采用这些模式。 Magento:Magento 的 GraphQL API 和 PWA Studio 支持无头架构。在 MACH 技术栈中将 Magento 用作后端服务时,可采用这些可组合模式。
你需要:
- Node.js 18+(或适配为你的后端语言)
- 用于缓存/队列的 Redis
- 电子邮件发送服务(SendGrid、AWS SES 或 Postmark)
- CDN(Cloudflare、CloudFront 或 Fastly)
核心说明
-
搭建 Hydrogen 项目脚手架
npm create @shopify/hydrogen@latest -- --quickstart # or with options: npm create @shopify/hydrogen@latest # Follow prompts: project name, language (TypeScript), mock shop or real credentials cd my-hydrogen-store npm run dev # http://localhost:3000项目结构遵循 Remix 基于文件的路由规则:
app/ routes/ _index.tsx # Homepage products.$handle.tsx # Product detail page collections.$handle.tsx cart.tsx components/ lib/ fragments.ts # Reusable GraphQL fragments server.ts # Hydrogen + Remix entry point -
配置 Storefront API 凭据
在 Shopify 管理后台中,前往 Apps → Develop apps → Create an app → Storefront API,创建 Storefront API 令牌。
# .env SESSION_SECRET="your-session-secret" PUBLIC_STOREFRONT_API_TOKEN="your-public-token" PUBLIC_STORE_DOMAIN="your-store.myshopify.com" PUBLIC_STOREFRONT_API_VERSION="2025-01"server.ts将 Hydrogen 接入 Remix:import {createHydrogenContext} from '@shopify/hydrogen'; const hydrogenContext = createHydrogenContext({ storefront: { apiVersion: env.PUBLIC_STOREFRONT_API_VERSION, privateStorefrontToken: env.PRIVATE_STOREFRONT_API_TOKEN, publicStorefrontToken: env.PUBLIC_STOREFRONT_API_TOKEN, storeDomain: env.PUBLIC_STORE_DOMAIN, }, session: HydrogenSession.init(request, [env.SESSION_SECRET]), }); -
查询 Storefront API
Hydrogen 提供
storefront.query方法,并内置缓存策略。// app/routes/products.$handle.tsx import {useLoaderData} from '@remix-run/react'; import {json, type LoaderFunctionArgs} from '@shopify/remix-oxygen'; export async function loader({params, context}: LoaderFunctionArgs) { const {storefront} = context; const {product} = await storefront.query(PRODUCT_QUERY, { variables: {handle: params.handle}, cache: storefront.CacheLong(), // Cache at CDN for 24h }); if (!product) throw new Response('Not Found', {status: 404}); return json({product}); } const PRODUCT_QUERY = `#graphql query Product($handle: String!) { product(handle: $handle) { id title descriptionHtml featuredImage { url altText width height } variants(first: 20) { nodes { id title price { amount currencyCode } availableForSale selectedOptions { name value } } } } } ` as const; -
使用 Hydrogen 购物车工具实现购物车
Hydrogen 通过 Remix action 函数提供服务端购物车操作:
// app/routes/cart.tsx import {CartForm} from '@shopify/hydrogen'; import type {ActionFunctionArgs} from '@shopify/remix-oxygen'; export async function action({request, context}: ActionFunctionArgs) { const {cart} = context; const formData = await request.formData(); const {action, inputs} = CartForm.getFormInput(formData); let result; switch (action) { case CartForm.ACTIONS.LinesAdd: result = await cart.addLines(inputs.lines); break; case CartForm.ACTIONS.LinesUpdate: result = await cart.updateLines(inputs.lines); break; case CartForm.ACTIONS.LinesRemove: result = await cart.removeLines(inputs.lineIds); break; default: throw new Error(`Unhandled cart action: ${action}`); } const headers = cart.setCartId(result.cart.id); return json(result, {headers}); } // Add to cart form component export function AddToCartButton({variantId}: {variantId: string}) { return ( <CartForm route="/cart" action={CartForm.ACTIONS.LinesAdd} inputs={{lines: [{merchandiseId: variantId, quantity: 1}]}} > <button type="submit">Add to Cart</button> </CartForm> ); } -
使用 Hydrogen 缓存策略
Hydrogen 提供具名缓存策略,可映射到 CDN 缓存控制标头:
// Long cache for static catalog data const {collections} = await storefront.query(COLLECTIONS_QUERY, { cache: storefront.CacheLong(), // s-maxage=3600, stale-while-revalidate=82800 }); // Short cache for inventory-sensitive data const {product} = await storefront.query(PRODUCT_WITH_INVENTORY, { cache: storefront.CacheShort(), // s-maxage=1, stale-while-revalidate=9 }); // No cache for personalized/cart data const {customer} = await storefront.query(CUSTOMER_QUERY, { cache: storefront.CacheNone(), }); // Custom strategy const {data} = await storefront.query(QUERY, { cache: storefront.CacheCustom({ mode: 'public', maxAge: 600, staleWhileRevalidate: 3000, }), }); -
部署到 Oxygen
npm install -g @shopify/cli shopify hydrogen deploy # Creates a deployment in your Shopify admin under Online Store → Themes → Headless对于 CI/CD,请使用 GitHub Action:
# .github/workflows/oxygen.yml - uses: Shopify/hydrogen-action@v1 with: shop: ${{ secrets.SHOPIFY_SHOP_DOMAIN }} token: ${{ secrets.SHOPIFY_CLI_TOKEN }}
示例
带筛选和排序的系列页面
// app/routes/collections.$handle.tsx
export async function loader({params, request, context}: LoaderFunctionArgs) {
const {storefront} = context;
const url = new URL(request.url);
const sortKey = url.searchParams.get('sort') as ProductCollectionSortKeys | null;
const {collection} = await storefront.query(COLLECTION_QUERY, {
variables: {
handle: params.handle,
first: 24,
sortKey: sortKey ?? 'BEST_SELLING',
reverse: sortKey === 'PRICE' ? false : true,
},
cache: storefront.CacheShort(),
});
return json({collection});
}
const COLLECTION_QUERY = `#graphql
query Collection(
$handle: String!
$first: Int
$sortKey: ProductCollectionSortKeys
$reverse: Boolean
) {
collection(handle: $handle) {
id
title
description
image { url altText }
products(first: $first, sortKey: $sortKey, reverse: $reverse) {
nodes {
id
title
handle
priceRange { minVariantPrice { amount currencyCode } }
featuredImage { url altText }
}
pageInfo { hasNextPage endCursor }
}
}
}
` as const;
使用新版 Customer Account API 进行客户身份验证
// Hydrogen supports the new Customer Account API (OAuth-based)
// app/lib/customer-account.server.ts
export async function loader({context}: LoaderFunctionArgs) {
const {customerAccount} = context;
const isLoggedIn = await customerAccount.isLoggedIn();
if (!isLoggedIn) {
return redirect('/account/login');
}
const {data} = await customerAccount.query(`#graphql
query Customer {
customer {
id
firstName
lastName
emailAddress { emailAddress }
orders(first: 10) {
nodes {
id
number
processedAt
financialStatus
totalPrice { amount currencyCode }
}
}
}
}
`);
return json({customer: data.customer});
}
最佳实践
- 对目录数据使用
storefront.CacheLong()—— 商品和商品集合数据很少变化;较长的缓存 TTL 可大幅改善其在 Oxygen 边缘网络上的 TTFB - 将 GraphQL 查询与路由放在一起 —— 在与 loader 相同的文件中定义
as const片段字符串;这样数据需求一目了然,也能启用 TypeScript 类型推断 - 使用 Hydrogen 的
<Image>和<Money>组件 —— 它们会自动处理 Shopify CDN 图片优化 URL 和货币格式 - 对非关键数据使用 Remix defer + Suspense —— 立即渲染商品,并通过
defer()流式传输推荐内容或评价 - 通过 Cookie 在服务端保存购物车状态 —— Hydrogen 的购物车工具会将购物车 ID 存储在签名 Cookie 中;避免仅在客户端保存购物车状态,以免破坏 SSR
- 使用 Storefront API 的
@inContext指令 —— 传入language和country上下文,以便按请求获取本地化价格和翻译内容 - 在
.env中固定 Storefront API 版本 —— Shopify 会弃用旧版 API;显式固定版本可避免 API 更新时意外出现问题
常见问题
| 问题 | 解决方案 |
|---|---|
| “Storefront API token not authorized” | 确保令牌具有 unauthenticated_read_* 范围;私有令牌仅用于服务端请求 |
| 页面导航后购物车状态丢失 | 使用 cart.setCartId() 将购物车 ID 存储在会话 Cookie 中;切勿将购物车 ID 存储在组件状态中 |
| 图片在 Oxygen 上未优化 | 使用 Hydrogen 的 <Image> 组件或 getImageData() 辅助函数,添加 Shopify CDN 转换参数 |
| GraphQL 查询出现 TypeScript 错误 | 更改查询后运行 npm run codegen 以重新生成类型;查询必须标记为 as const |
| 部署失败并提示“missing environment variables” | Oxygen 环境变量必须在 Shopify 管理后台的 Hydrogen 部署设置中添加,不能只添加到 .env 中 |
相关技能
- @saleor-development
- @jamstack-storefront
- @composable-commerce
- @pwa-storefront
- @commerce-api-gateway