mcpField
H
headless-hydrogen

headless-hydrogen

github.com/dragnoir开发5050 次安装38 周前更新
代码仓库

关于 headless-hydrogen

用于使用 Hydrogen 或 Storefront API 构建 Shopify 无头店面的开发技能,面向电商开发者,涵盖店面功能、性能模式和 Oxygen 部署。

主要功能

  1. 01Hydrogen 店面搭建
  2. 02Storefront API 数据查询
  3. 03购物车与客户账户模式
  4. 04搜索和本地化示例
  5. 05Oxygen 部署指南

适用场景

  1. 01构建自定义 Shopify 店面
  2. 02添加购物车和客户登录流程
  3. 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 管理后台设置环境变量:

  1. 前往 Sales channels > Hydrogen
  2. 选择你的店面
  3. 添加环境变量
预览部署

每次 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>

性能最佳实践

  1. 服务端渲染 - 使用 SSR 加载初始页面
  2. 流式传输 - 使用 React Suspense 实现渐进式加载
  3. 图像优化 - 使用 Hydrogen 的 Image 组件
  4. 代码拆分 - 延迟加载非关键组件
  5. 缓存标头 - 配置适当的缓存策略
  6. 预取 - 鼠标悬停时预取链接
// 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