mcpField
S
shopify-hydrogen

shopify-hydrogen

github.com/finsilabs开发6083 次安装1 周前更新
代码仓库

关于 shopify-hydrogen

面向需要定制 Shopify 前端的开发者,介绍使用 Hydrogen 构建无头店面的流程。该 React 框架提供 Storefront API 查询、购物车工具和缓存策略,并可部署到 Oxygen。

主要功能

  1. 01基于 React 与 Remix 构建店面
  2. 02查询 Shopify Storefront API
  3. 03使用 Hydrogen 工具管理购物车
  4. 04配置缓存策略
  5. 05部署到 Oxygen

适用场景

  1. 01定制 Shopify 店面的设计与体验
  2. 02为产品和集合页面获取数据
  3. 03实现服务端购物车操作
  4. 04将 Hydrogen 店面部署到 Oxygen

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/finsilabs/awesome-ecommerce-skills --skill shopify-hydrogen

安装后在 Claude Code 中输入/shopify-hydrogen确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

tile.json
evals/cart-implementation-with-server-side-sta/
criteria.json
task.md
evals/project-configuration-and-deployment/
criteria.json
task.md
evals/storefront-api-querying-and-caching/
criteria.json
task.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)

核心说明

  1. 搭建 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
    
  2. 配置 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]),
    });
    
  3. 查询 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;
    
  4. 使用 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>
      );
    }
    
  5. 使用 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,
      }),
    });
    
  6. 部署到 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

评论

支持 Markdown