headless-hydrogen
4
总安装量
4
周安装量
#54302
全站排名
安装命令
npx skills add https://github.com/dragnoir/shopify-agent-skills --skill headless-hydrogen
Agent 安装分布
opencode
4
gemini-cli
4
github-copilot
4
codex
4
kimi-cli
4
amp
4
Skill 文档
Headless Commerce with 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
What is 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)
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
Getting Started
1. Create a Hydrogen App
# 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. Project Structure
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. Environment Setup
# .env
SESSION_SECRET=your-session-secret
PUBLIC_STOREFRONT_API_TOKEN=your-storefront-api-token
PUBLIC_STORE_DOMAIN=your-store.myshopify.com
4. Start Development
npm run dev
Core Concepts
Routes and Data Loading
// 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
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>
Cart Management
// 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
// 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
// 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
// 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 };
}
Localization
// 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 Deployment
Deploy from CLI
# Link to Shopify store
npx shopify hydrogen link
# Deploy to Oxygen
npx shopify hydrogen deploy
Environment Variables
Set environment variables in Shopify admin:
- Go to Sales channels > Hydrogen
- Select your storefront
- Add environment variables
Preview Deployments
Every git push creates a preview URL for testing.
# Push to create preview
git push origin feature-branch
Bring Your Own Stack
If not using Hydrogen, you can use the Storefront API with any framework:
Install Headless Channel
# In your Shopify admin, install the Headless channel
# Create a storefront and get API credentials
Use with 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();
}
Storefront Web Components
<!-- 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>
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
// Streaming example
import { Suspense } from "react";
function ProductPage() {
return (
<div>
<ProductInfo />
<Suspense fallback={<LoadingSkeleton />}>
<ProductRecommendations />
</Suspense>
</div>
);
}
CLI Commands Reference
| Command | Description |
|---|---|
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 |
Resources
- Hydrogen Documentation
- Storefront API Reference
- Hydrogen Components
- Oxygen Hosting
- Demo Store Template
For API details, see the api-graphql skill.