headless-hydrogen
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseHeadless Commerce with Hydrogen
基于Hydrogen的无头电商
When to use this skill
何时使用本技能
Use this skill when:
- Building a custom headless storefront
- Using Hydrogen framework for e-commerce
- Deploying to Oxygen (Shopify's edge hosting)
- Working with the Storefront API
- Creating high-performance, custom storefronts
- Integrating Shopify with custom tech stacks
在以下场景使用本技能:
- 构建自定义无头店铺前端
- 使用Hydrogen框架开发电商项目
- 部署至Oxygen(Shopify的边缘托管服务)
- 调用Storefront API
- 创建高性能的自定义店铺前端
- 将Shopify与自定义技术栈集成
What is Hydrogen?
什么是Hydrogen?
Hydrogen is Shopify's official headless commerce framework built on:
- React Router - For routing and data loading
- React - Component-based UI
- GraphQL - Data fetching from Storefront API
- Oxygen - Global edge deployment (free hosting)
Hydrogen是Shopify官方的无头电商框架,基于以下技术构建:
- React Router - 用于路由和数据加载
- React - 基于组件的UI开发
- GraphQL - 从Storefront API获取数据
- Oxygen - 全球边缘部署(免费托管)
Key Benefits
核心优势
- Build-ready components - Pre-built commerce components
- Free hosting - Deploy to Oxygen at no extra cost
- Fast by default - SSR, progressive enhancement, nested routes
- Shopify-native - Deep integration with Shopify APIs
- 开箱即用的组件 - 预构建的电商组件
- 免费托管 - 无需额外成本即可部署至Oxygen
- 默认高性能 - 服务端渲染(SSR)、渐进式增强、嵌套路由
- Shopify原生集成 - 与Shopify APIs深度集成
Getting Started
快速开始
1. Create a Hydrogen App
1. 创建Hydrogen应用
bash
undefinedbash
undefinedCreate new Hydrogen project
创建新的Hydrogen项目
npm create @shopify/hydrogen@latest
npm create @shopify/hydrogen@latest
Follow the prompts:
按照提示操作:
- Choose a template (Demo store, Hello World, Skeleton)
- 选择模板(演示店铺、Hello World、骨架模板)
- Enter your store URL
- 输入你的店铺URL
- Select JavaScript or TypeScript
- 选择JavaScript或TypeScript
undefinedundefined2. Project Structure
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.jsonhydrogen-app/
├── app/
│ ├── components/ # React组件
│ ├── routes/ # 页面路由
│ │ ├── _index.tsx # 首页
│ │ ├── products.$handle.tsx # 商品页面
│ │ └── collections.$handle.tsx
│ ├── styles/ # CSS文件
│ ├── entry.client.tsx # 客户端入口
│ └── entry.server.tsx # 服务端入口
├── public/ # 静态资源
├── .env # 环境变量
├── hydrogen.config.ts # Hydrogen配置文件
└── package.json3. Environment Setup
3. 环境配置
env
undefinedenv
undefined.env
.env
SESSION_SECRET=your-session-secret
PUBLIC_STOREFRONT_API_TOKEN=your-storefront-api-token
PUBLIC_STORE_DOMAIN=your-store.myshopify.com
undefinedSESSION_SECRET=your-session-secret
PUBLIC_STOREFRONT_API_TOKEN=your-storefront-api-token
PUBLIC_STORE_DOMAIN=your-store.myshopify.com
undefined4. Start Development
4. 启动开发服务器
bash
npm run devbash
npm run devCore Concepts
核心概念
Routes and Data Loading
路由与数据加载
tsx
// 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
}
}
}
`;tsx
// 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 Components
Hydrogen组件
tsx
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>tsx
import {
Image,
Money,
CartForm,
CartLineQuantity,
useCart,
} from '@shopify/hydrogen';
// 图片组件
<Image
data={product.featuredImage}
aspectRatio="1/1"
sizes="(min-width: 45em) 50vw, 100vw"
/>
// 价格组件
<Money data={product.price} />
// 加入购物车
<CartForm
route="/cart"
action={CartForm.ACTIONS.LinesAdd}
inputs={{
lines: [{ merchandiseId: variantId, quantity: 1 }],
}}
>
<button type="submit">Add to Cart</button>
</CartForm>Cart Management
购物车管理
tsx
// 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>
);
}tsx
// 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>
);
}Collections
商品集合
tsx
// 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
}
}
}
}
}
}
`;tsx
// 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
}
}
}
}
}
}
`;Advanced Patterns
高级模式
Search Implementation
搜索功能实现
tsx
// 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
}
}
}
}
`;tsx
// 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
}
}
}
}
`;Customer Authentication
客户认证
tsx
// 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 };
}tsx
// 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) {
// 将令牌存储到会话中
return redirect("/account");
}
return { errors: customerAccessTokenCreate.customerUserErrors };
}Localization
本地化
tsx
// 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 };
}tsx
// 多币种与多语言支持
export async function loader({ request, context }: LoaderFunctionArgs) {
const { storefront } = context;
// 获取本地化数据
const { localization } = await storefront.query(LOCALIZATION_QUERY);
// 结合本地化上下文查询数据
const { product } = await storefront.query(PRODUCT_QUERY, {
variables: { handle, country: "CA", language: "FR" },
});
return { product, localization };
}Oxygen Deployment
Oxygen部署
Deploy from CLI
通过CLI部署
bash
undefinedbash
undefinedLink to Shopify store
关联至Shopify店铺
npx shopify hydrogen link
npx shopify hydrogen link
Deploy to Oxygen
部署至Oxygen
npx shopify hydrogen deploy
undefinednpx shopify hydrogen deploy
undefinedEnvironment Variables
环境变量配置
Set environment variables in Shopify admin:
- Go to Sales channels > Hydrogen
- Select your storefront
- Add environment variables
在Shopify后台设置环境变量:
- 进入销售渠道 > Hydrogen
- 选择你的店铺前端
- 添加环境变量
Preview Deployments
预览部署
Every git push creates a preview URL for testing.
bash
undefined每次Git推送都会生成一个预览URL用于测试。
bash
undefinedPush to create preview
推送代码以创建预览版本
git push origin feature-branch
undefinedgit push origin feature-branch
undefinedBring Your Own Stack
自定义技术栈集成
If not using Hydrogen, you can use the Storefront API with any framework:
如果不使用Hydrogen,你可以将Storefront API与任意框架结合使用:
Install Headless Channel
安装无头渠道
bash
undefinedbash
undefinedIn your Shopify admin, install the Headless channel
在Shopify后台安装无头渠道
Create a storefront and get API credentials
创建店铺前端并获取API凭证
undefinedundefinedUse with Next.js
与Next.js集成
typescript
// 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();
}typescript
// 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();
}Storefront Web Components
店铺前端Web组件
html
<!-- Embed products anywhere with Web Components -->
<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>html
<!-- 使用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>Performance Best Practices
性能最佳实践
- Server-side rendering - SSR for initial page load
- Streaming - Use React Suspense for progressive loading
- Image optimization - Use Hydrogen's Image component
- Code splitting - Lazy load non-critical components
- Cache headers - Configure appropriate cache policies
- Prefetching - Prefetch links on hover
tsx
// Streaming example
import { Suspense } from "react";
function ProductPage() {
return (
<div>
<ProductInfo />
<Suspense fallback={<LoadingSkeleton />}>
<ProductRecommendations />
</Suspense>
</div>
);
}- 服务端渲染 - 用于初始页面加载
- 流式渲染 - 使用React Suspense实现渐进式加载
- 图片优化 - 使用Hydrogen的Image组件
- 代码分割 - 懒加载非核心组件
- 缓存头配置 - 设置合适的缓存策略
- 预获取 - 在鼠标悬停时预获取链接
tsx
// 流式渲染示例
import { Suspense } from "react";
function ProductPage() {
return (
<div>
<ProductInfo />
<Suspense fallback={<LoadingSkeleton />}>
<ProductRecommendations />
</Suspense>
</div>
);
}CLI Commands Reference
CLI命令参考
| Command | Description |
|---|---|
| Create new project |
| Start dev server |
| Build for production |
| Link to store |
| Deploy to Oxygen |
| Preview production build |
| 命令 | 描述 |
|---|---|
| 创建新项目 |
| 启动开发服务器 |
| 构建生产版本 |
| 关联至店铺 |
| 部署至Oxygen |
| 预览生产构建版本 |
Resources
资源
- Hydrogen Documentation
- Storefront API Reference
- Hydrogen Components
- Oxygen Hosting
- Demo Store Template
For API details, see the api-graphql skill.
如需API详细信息,请查看api-graphql技能。