headless-hydrogen

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Headless 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
undefined
bash
undefined

Create 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

undefined
undefined

2. 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.json
hydrogen-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.json

3. Environment Setup

3. 环境配置

env
undefined
env
undefined

.env

.env

SESSION_SECRET=your-session-secret PUBLIC_STOREFRONT_API_TOKEN=your-storefront-api-token PUBLIC_STORE_DOMAIN=your-store.myshopify.com
undefined
SESSION_SECRET=your-session-secret PUBLIC_STOREFRONT_API_TOKEN=your-storefront-api-token PUBLIC_STORE_DOMAIN=your-store.myshopify.com
undefined

4. Start Development

4. 启动开发服务器

bash
npm run dev
bash
npm run dev

Core 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
undefined
bash
undefined

Link to Shopify store

关联至Shopify店铺

npx shopify hydrogen link
npx shopify hydrogen link

Deploy to Oxygen

部署至Oxygen

npx shopify hydrogen deploy
undefined
npx shopify hydrogen deploy
undefined

Environment Variables

环境变量配置

Set environment variables in Shopify admin:
  1. Go to Sales channels > Hydrogen
  2. Select your storefront
  3. Add environment variables
在Shopify后台设置环境变量:
  1. 进入销售渠道 > Hydrogen
  2. 选择你的店铺前端
  3. 添加环境变量

Preview Deployments

预览部署

Every git push creates a preview URL for testing.
bash
undefined
每次Git推送都会生成一个预览URL用于测试。
bash
undefined

Push to create preview

推送代码以创建预览版本

git push origin feature-branch
undefined
git push origin feature-branch
undefined

Bring Your Own Stack

自定义技术栈集成

If not using Hydrogen, you can use the Storefront API with any framework:
如果不使用Hydrogen,你可以将Storefront API与任意框架结合使用:

Install Headless Channel

安装无头渠道

bash
undefined
bash
undefined

In your Shopify admin, install the Headless channel

在Shopify后台安装无头渠道

Create a storefront and get API credentials

创建店铺前端并获取API凭证

undefined
undefined

Use 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

性能最佳实践

  1. Server-side rendering - SSR for initial page load
  2. Streaming - Use React Suspense for progressive loading
  3. Image optimization - Use Hydrogen's Image component
  4. Code splitting - Lazy load non-critical components
  5. Cache headers - Configure appropriate cache policies
  6. Prefetching - Prefetch links on hover
tsx
// Streaming example
import { Suspense } from "react";

function ProductPage() {
  return (
    <div>
      <ProductInfo />
      <Suspense fallback={<LoadingSkeleton />}>
        <ProductRecommendations />
      </Suspense>
    </div>
  );
}
  1. 服务端渲染 - 用于初始页面加载
  2. 流式渲染 - 使用React Suspense实现渐进式加载
  3. 图片优化 - 使用Hydrogen的Image组件
  4. 代码分割 - 懒加载非核心组件
  5. 缓存头配置 - 设置合适的缓存策略
  6. 预获取 - 在鼠标悬停时预获取链接
tsx
// 流式渲染示例
import { Suspense } from "react";

function ProductPage() {
  return (
    <div>
      <ProductInfo />
      <Suspense fallback={<LoadingSkeleton />}>
        <ProductRecommendations />
      </Suspense>
    </div>
  );
}

CLI Commands Reference

CLI命令参考

CommandDescription
npm create @shopify/hydrogen
Create new project
npm run dev
Start dev server
npm run build
Build for production
npx shopify hydrogen link
Link to store
npx shopify hydrogen deploy
Deploy to Oxygen
npx shopify hydrogen preview
Preview production build
命令描述
npm create @shopify/hydrogen
创建新项目
npm run dev
启动开发服务器
npm run build
构建生产版本
npx shopify hydrogen link
关联至店铺
npx shopify hydrogen deploy
部署至Oxygen
npx shopify hydrogen preview
预览生产构建版本

Resources

资源