mcpField
S
shopify-storefront-api

shopify-storefront-api

github.com/finsilabs开发6096 次安装5 天前更新
代码仓库

关于 shopify-storefront-api

面向构建自定义 Shopify 店面的开发者,介绍用于前端访问产品、系列、购物车和结账的 GraphQL API。内容包括令牌设置、查询与购物车管理,适用于无头店面及移动应用等场景。

主要功能

  1. 01创建 Storefront Access Token
  2. 02通过 GraphQL 查询产品和系列
  3. 03创建购物车并管理商品行
  4. 04保存购物车并跳转结账
  5. 05实现预测搜索和变体选择

适用场景

  1. 01构建 Next.js 或 Remix 无头店面
  2. 02为移动应用读取产品与购物车数据
  3. 03在非 Shopify 网站嵌入购买组件
  4. 04实现跨会话购物车持久化

安装

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

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

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

包含的文件

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

tile.json
evals/cart-persistence-usererrors-handling-var/
criteria.json
task.md
evals/cursor-pagination-incontext-directive-fr/
criteria.json
task.md
evals/storefront-client-setup-with-public-and-/
criteria.json
task.md

详细介绍

Shopify Storefront API

概述

Shopify Storefront API 是面向公众的 GraphQL API,可从任何前端读写商店的产品、系列、购物车和结账信息。它使用 Storefront Access Token(与 Admin API 令牌不同),可安全地公开在客户端 JavaScript 中。可用它搭建无头店面,搭配 Next.js、Remix/Hydrogen 或任何 JS 框架。

何时使用此技能

  • 使用自定义前端框架构建无头 Shopify 店面时
  • 创建需要产品和购物车数据的 React Native 或 Flutter 移动应用时
  • 在非 Shopify 网站中嵌入 Shopify 购买按钮或产品组件时
  • 使用 Shopify Hydrogen(基于 Remix)打造完全自定义的店面体验时
  • 需要实时获取产品库存情况或价格,且不想承担 Admin API 的开销时
  • 使用 Shopify 托管的购物车实现跨会话购物车持久化时

核心说明

  1. 创建 Storefront Access Token

    在 Shopify Admin → Apps → Develop apps → Your App → API credentials → Storefront API access token 中创建。也可以通过 Admin API 创建:

    // Via Admin API (one-time setup)
    const token = await admin.graphql(`
      mutation {
        storefrontAccessTokenCreate(input: { title: "Headless Frontend" }) {
          storefrontAccessToken {
            accessToken
            title
          }
          userErrors { field message }
        }
      }
    `);
    

    Storefront Access Token 不使用 shpat_ 前缀(该前缀用于 Admin API 令牌)。Storefront Token 是不透明字符串,可安全地用于浏览器代码中;它们仅允许执行 Storefront 范围内的操作。

  2. 设置 Storefront API 客户端

    使用官方的 @shopify/storefront-api-client:

    npm install @shopify/storefront-api-client
    
    // lib/shopify.ts
    import { createStorefrontApiClient } from "@shopify/storefront-api-client";
    
    export const storefront = createStorefrontApiClient({
      storeDomain: process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN!, // e.g. "mystore.myshopify.com"
      apiVersion: "2025-01",
      publicAccessToken: process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_TOKEN!,
    });
    

    使用私有访问令牌进行服务端调用(速率限制更高):

    export const storefrontServer = createStorefrontApiClient({
      storeDomain: process.env.SHOPIFY_STORE_DOMAIN!,
      apiVersion: "2025-01",
      privateAccessToken: process.env.SHOPIFY_STOREFRONT_PRIVATE_TOKEN!,
    });
    
  3. 查询产品和系列

    // lib/products.ts
    export async function getProducts(first = 20, after?: string) {
      const { data, errors } = await storefront.request(`
        query GetProducts($first: Int!, $after: String) {
          products(first: $first, after: $after, sortKey: BEST_SELLING) {
            pageInfo {
              hasNextPage
              endCursor
            }
            edges {
              node {
                id
                title
                handle
                availableForSale
                priceRange {
                  minVariantPrice { amount currencyCode }
                  maxVariantPrice { amount currencyCode }
                }
                images(first: 1) {
                  edges {
                    node { url altText width height }
                  }
                }
                variants(first: 10) {
                  edges {
                    node {
                      id
                      title
                      availableForSale
                      selectedOptions { name value }
                      price { amount currencyCode }
                    }
                  }
                }
              }
            }
          }
        }
      `, { variables: { first, after } });
    
      if (errors) throw new Error(errors.message);
      return data.products;
    }
    
  4. 创建和管理购物车

    // lib/cart.ts
    
    // Create a new cart
    export async function cartCreate(lines: { merchandiseId: string; quantity: number }[]) {
      const { data } = await storefront.request(`
        mutation CartCreate($lines: [CartLineInput!]) {
          cartCreate(input: { lines: $lines }) {
            cart {
              id
              checkoutUrl
              lines(first: 50) {
                edges {
                  node {
                    id
                    quantity
                    merchandise {
                      ... on ProductVariant {
                        id
                        title
                        price { amount currencyCode }
                        product { title handle }
                      }
                    }
                  }
                }
              }
              cost {
                subtotalAmount { amount currencyCode }
                totalAmount { amount currencyCode }
              }
            }
            userErrors { field message }
          }
        }
      `, { variables: { lines } });
      return data.cartCreate;
    }
    
    // Add lines to existing cart
    export async function cartLinesAdd(cartId: string, lines: { merchandiseId: string; quantity: number }[]) {
      const { data } = await storefront.request(`
        mutation CartLinesAdd($cartId: ID!, $lines: [CartLineInput!]!) {
          cartLinesAdd(cartId: $cartId, lines: $lines) {
            cart { id checkoutUrl }
            userErrors { field message }
          }
        }
      `, { variables: { cartId, lines } });
      return data.cartLinesAdd;
    }
    
  5. 持久化购物车 ID 并跳转到结账页面

    // hooks/useCart.ts
    import { useState, useEffect } from "react";
    import { cartCreate, cartLinesAdd } from "../lib/cart";
    
    const CART_ID_KEY = "shopify_cart_id";
    
    export function useCart() {
      const [cartId, setCartId] = useState<string | null>(null);
      const [checkoutUrl, setCheckoutUrl] = useState<string | null>(null);
    
      useEffect(() => {
        setCartId(localStorage.getItem(CART_ID_KEY));
      }, []);
    
      const addToCart = async (variantId: string, quantity = 1) => {
        const lines = [{ merchandiseId: variantId, quantity }];
        if (cartId) {
          const result = await cartLinesAdd(cartId, lines);
          setCheckoutUrl(result.cart.checkoutUrl);
        } else {
          const result = await cartCreate(lines);
          const newCartId = result.cart.id;
          localStorage.setItem(CART_ID_KEY, newCartId);
          setCartId(newCartId);
          setCheckoutUrl(result.cart.checkoutUrl);
        }
      };
    
      const goToCheckout = () => {
        if (checkoutUrl) window.location.href = checkoutUrl;
      };
    
      return { addToCart, goToCheckout, cartId };
    }
    

示例

带有变体选择的产品详情页(Next.js)
// app/products/[handle]/page.tsx
import { storefront } from "@/lib/shopify";

async function getProduct(handle: string) {
  const { data } = await storefront.request(`
    query GetProduct($handle: String!) {
      product(handle: $handle) {
        id
        title
        descriptionHtml
        seo { title description }
        images(first: 10) {
          edges { node { url altText } }
        }
        options {
          id name values
        }
        variants(first: 100) {
          edges {
            node {
              id
              availableForSale
              selectedOptions { name value }
              price { amount currencyCode }
              compareAtPrice { amount currencyCode }
            }
          }
        }
      }
    }
  `, { variables: { handle } });
  return data.product;
}

export default async function ProductPage({ params }: { params: { handle: string } }) {
  const product = await getProduct(params.handle);
  // Render product with client-side variant picker
  return <ProductDetail product={product} />;
}

// Generate static params for all products
export async function generateStaticParams() {
  const { data } = await storefront.request(`
    query { products(first: 200) { edges { node { handle } } } }
  `);
  return data.products.edges.map(({ node }: { node: { handle: string } }) => ({
    handle: node.handle,
  }));
}
预测性搜索
export async function predictiveSearch(query: string) {
  const { data } = await storefront.request(`
    query PredictiveSearch($query: String!) {
      predictiveSearch(query: $query, limit: 5, types: [PRODUCT, COLLECTION, ARTICLE]) {
        products {
          id title handle
          featuredImage { url altText }
          priceRange { minVariantPrice { amount currencyCode } }
        }
        collections {
          id title handle
          image { url altText }
        }
      }
    }
  `, { variables: { query } });
  return data.predictiveSearch;
}

最佳实践

  • 在服务端使用私有令牌——私有 Storefront Access Token 的速率限制更高(1000 req/s,而非 100 req/s),且绝不可暴露给浏览器
  • 尽可能在构建时获取产品数据(ISR 或 SSG)——Storefront API 的速率限制按商店计算,而非按客户计算
  • 在显示“加入购物车”之前,始终检查产品和变体的 availableForSale——产品可能有货,但个别变体可能已售罄
  • 使用 after 游标分页,不要使用偏移量——Storefront API 使用基于游标的分页;保存 endCursor 以便查询下一页
  • 使用 Next.js fetch 缓存标签或 React cache 缓存系列和产品查询——产品数据很少实时变化
  • 使用 @inContext 指令处理国际定价——@inContext(country: CA, language: EN) 会以买家使用的货币返回价格
  • 重复使用片段——定义 GraphQL 片段(例如 ProductFragment),避免在多个查询中重复选择字段
  • 处理所有变更操作中的 userErrors——购物车变更操作会返回 userErrors 数组;更新本地状态前请先检查该数组

常见问题

问题解决方案
速率限制错误(429)在服务端使用私有访问令牌并实现请求批处理;避免 N+1 产品查询
页面重新加载后购物车 ID 丢失将 cartId 持久化到 localStorage 或 cookie 中;仅在购物车不存在时创建新购物车
产品价格显示的货币不正确添加 @inContext(country: $country) 指令,并通过地理位置传入买家所在国家/地区
product(handle:) 返回 null正确处理 slug 化的 handle——Shopify handle 使用小写字母和连字符;请检查 slug 是否完全匹配
在移动版 Safari 中跳转结账失败在用户手势处理函数中使用 window.location.href = checkoutUrl,不要在异步回调中使用
选择选项时找不到变体在客户端筛选 variants.edges,匹配所有 selectedOptions,而不只是一个

相关技能

  • @shopify-admin-api
  • @shopify-app-development
  • @shopify-checkout-extensions
  • @headless-commerce-architecture
  • @graphql-api-design

评论

支持 Markdown