关于 headless-hydrogen
用于使用 Hydrogen 或 Storefront API 构建 Shopify 无头店面的开发技能,面向电商开发者,涵盖店面功能、性能模式和 Oxygen 部署。
主要功能
- 01Hydrogen 店面搭建
- 02Storefront API 数据查询
- 03购物车与客户账户模式
- 04搜索和本地化示例
- 05Oxygen 部署指南
适用场景
- 01构建自定义 Shopify 店面
- 02添加购物车和客户登录流程
- 03将 Hydrogen 店面部署到 Oxygen
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ dragnoir/ shopify-agent-skills --skill headless-hydrogen安装后在 Claude Code 中输入/headless-hydrogen确认已加载。
详细介绍
使用 Hydrogen 构建无头商务
何时使用此技能
适用于以下情况:
- 构建自定义无头店面
- 使用 Hydrogen 框架开展电子商务
- 部署到 Oxygen(Shopify 的边缘托管服务)
- 使用 Storefront API
- 创建高性能的自定义店面
- 将 Shopify 与自定义技术栈集成
什么是 Hydrogen?
Hydrogen 是 Shopify 官方的无头商务框架,基于以下技术构建:
- React Router - 用于路由和数据加载
- React - 基于组件的 UI
- GraphQL - 从 Storefront API 获取数据
- Oxygen - 全球边缘部署(免费托管)
主要优势
- 开箱即用的组件 - 预构建的商务组件
- 免费托管 - 可免费部署到 Oxygen
- 默认快速 - SSR、渐进增强、嵌套路由
- 原生支持 Shopify - 与 Shopify API 深度集成
入门指南
1. 创建 Hydrogen 应用
# Create new Hydrogen project
npm create @shopify/hydrogen@latest
# Follow the prompts:
# - Choose a template (Demo store, Hello World, Skeleton)
# - Enter your store URL
# - Select JavaScript or TypeScript
2. 项目结构
hydrogen-app/
├── app/
│ ├── components/ # React components
│ ├── routes/ # Page routes
│ │ ├── _index.tsx # Home page
│ │ ├── products.$handle.tsx # Product page
│ │ └── collections.$handle.tsx
│ ├── styles/ # CSS files
│ ├── entry.client.tsx # Client entry
│ └── entry.server.tsx # Server entry
├── public/ # Static assets
├── .env # Environment variables
├── hydrogen.config.ts # Hydrogen config
└── package.json
3. 配置环境
# .env
SESSION_SECRET=your-session-secret
PUBLIC_STOREFRONT_API_TOKEN=your-storefront-api-token
PUBLIC_STORE_DOMAIN=your-store.myshopify.com
4. 开始开发
npm run dev
核心概念
路由和数据加载
// app/routes/products.$handle.tsx
import { useLoaderData, type LoaderFunctionArgs } from "@remix-run/react";
export async function loader({ params, context }: LoaderFunctionArgs) {
const { storefront } = context;
const { handle } = params;
const { product } = await storefront.query(PRODUCT_QUERY, {
variables: { handle },
});
if (!product) {
throw new Response("Not Found", { status: 404 });
}
return { product };
}
export default function ProductPage() {
const { product } = useLoaderData<typeof loader>();
return (
<div className="product-page">
<h1>{product.title}</h1>
<p>{product.description}</p>
<ProductPrice data={product} />
<AddToCartButton variantId={product.variants.nodes[0].id} />
</div>
);
}
const PRODUCT_QUERY = `#graphql
query Product($handle: String!) {
product(handle: $handle) {
id
title
description
handle
variants(first: 1) {
nodes {
id
price {
amount
currencyCode
}
}
}
featuredImage {
url
altText
}
}
}
`;
Hydrogen 组件
import {
Image,
Money,
CartForm,
CartLineQuantity,
useCart,
} from '@shopify/hydrogen';
// Image Component
<Image
data={product.featuredImage}
aspectRatio="1/1"
sizes="(min-width: 45em) 50vw, 100vw"
/>
// Money Component
<Money data={product.price} />
// Add to Cart
<CartForm
route="/cart"
action={CartForm.ACTIONS.LinesAdd}
inputs={{
lines: [{ merchandiseId: variantId, quantity: 1 }],
}}
>
<button type="submit">Add to Cart</button>
</CartForm>
购物车管理
// app/routes/cart.tsx
import { CartForm } from "@shopify/hydrogen";
import { type ActionFunctionArgs } from "@remix-run/cloudflare";
export async function action({ request, context }: ActionFunctionArgs) {
const { cart } = context;
const formData = await request.formData();
const { action, inputs } = CartForm.getFormInput(formData);
switch (action) {
case CartForm.ACTIONS.LinesAdd:
return await cart.addLines(inputs.lines);
case CartForm.ACTIONS.LinesUpdate:
return await cart.updateLines(inputs.lines);
case CartForm.ACTIONS.LinesRemove:
return await cart.removeLines(inputs.lineIds);
default:
throw new Error("Unknown cart action");
}
}
export default function CartPage() {
const cart = useLoaderData<typeof loader>();
return (
<div className="cart">
{cart.lines.nodes.map((line) => (
<CartLineItem key={line.id} line={line} />
))}
<Money data={cart.cost.totalAmount} />
</div>
);
}
产品系列
// app/routes/collections.$handle.tsx
export async function loader({ params, context }: LoaderFunctionArgs) {
const { handle } = params;
const { storefront } = context;
const { collection } = await storefront.query(COLLECTION_QUERY, {
variables: { handle, first: 24 },
});
return { collection };
}
const COLLECTION_QUERY = `#graphql
query Collection($handle: String!, $first: Int!) {
collection(handle: $handle) {
id
title
description
products(first: $first) {
nodes {
id
title
handle
featuredImage {
url
altText
}
priceRange {
minVariantPrice {
amount
currencyCode
}
}
}
}
}
}
`;
高级模式
搜索实现
// app/routes/search.tsx
export async function loader({ request, context }: LoaderFunctionArgs) {
const url = new URL(request.url);
const searchTerm = url.searchParams.get("q");
if (!searchTerm) {
return { results: null };
}
const { storefront } = context;
const { products } = await storefront.query(SEARCH_QUERY, {
variables: { query: searchTerm, first: 20 },
});
return { results: products };
}
const SEARCH_QUERY = `#graphql
query Search($query: String!, $first: Int!) {
products(query: $query, first: $first) {
nodes {
id
title
handle
featuredImage {
url
altText
}
}
}
}
`;
客户身份验证
// app/routes/account.login.tsx
export async function action({ request, context }: ActionFunctionArgs) {
const { customerAccount } = context;
const formData = await request.formData();
const email = formData.get("email");
const password = formData.get("password");
const { customerAccessTokenCreate } = await customerAccount.mutate(
LOGIN_MUTATION,
{ variables: { input: { email, password } } },
);
if (customerAccessTokenCreate.customerAccessToken) {
// Store token in session
return redirect("/account");
}
return { errors: customerAccessTokenCreate.customerUserErrors };
}
本地化
// Multi-currency and language support
export async function loader({ request, context }: LoaderFunctionArgs) {
const { storefront } = context;
// Get localized data
const { localization } = await storefront.query(LOCALIZATION_QUERY);
// Query with localization context
const { product } = await storefront.query(PRODUCT_QUERY, {
variables: { handle, country: "CA", language: "FR" },
});
return { product, localization };
}
部署到 Oxygen
从 CLI 部署
# Link to Shopify store
npx shopify hydrogen link
# Deploy to Oxygen
npx shopify hydrogen deploy
环境变量
在 Shopify 管理后台设置环境变量:
- 前往 Sales channels > Hydrogen
- 选择你的店面
- 添加环境变量
预览部署
每次 git push 都会创建一个预览 URL,供测试使用。
# Push to create preview
git push origin feature-branch
使用自己的技术栈
如果不使用 Hydrogen,你可以将 Storefront API 与任何框架搭配使用:
安装无头渠道
# In your Shopify admin, install the Headless channel
# Create a storefront and get API credentials
与 Next.js 搭配使用
// lib/shopify.ts
const domain = process.env.SHOPIFY_STORE_DOMAIN;
const storefrontAccessToken = process.env.SHOPIFY_STOREFRONT_ACCESS_TOKEN;
export async function shopifyFetch({ query, variables }) {
const endpoint = `https://${domain}/api/2025-01/graphql.json`;
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Shopify-Storefront-Access-Token": storefrontAccessToken,
},
body: JSON.stringify({ query, variables }),
});
return response.json();
}
店面 Web 组件
<script
type="module"
src="https://cdn.shopify.com/storefront-web-components/v1/storefront.js"
></script>
<shopify-product-provider store-domain="your-store.myshopify.com">
<shopify-product handle="product-handle">
<shopify-product-title></shopify-product-title>
<shopify-product-price></shopify-product-price>
<shopify-add-to-cart></shopify-add-to-cart>
</shopify-product>
</shopify-product-provider>
性能最佳实践
- 服务端渲染 - 使用 SSR 加载初始页面
- 流式传输 - 使用 React Suspense 实现渐进式加载
- 图像优化 - 使用 Hydrogen 的 Image 组件
- 代码拆分 - 延迟加载非关键组件
- 缓存标头 - 配置适当的缓存策略
- 预取 - 鼠标悬停时预取链接
// Streaming example
import { Suspense } from "react";
function ProductPage() {
return (
<div>
<ProductInfo />
<Suspense fallback={<LoadingSkeleton />}>
<ProductRecommendations />
</Suspense>
</div>
);
}
CLI 命令参考
| 命令 | 说明 |
|---|---|
npm create @shopify/hydrogen | 创建新项目 |
npm run dev | 启动开发服务器 |
npm run build | 构建生产版本 |
npx shopify hydrogen link | 链接到商店 |
npx shopify hydrogen deploy | 部署到 Oxygen |
npx shopify hydrogen preview | 预览生产构建版本 |
资源
有关 API 详情,请参阅 api-graphql 技能。
评论
支持 Markdown