mcpField
S
shopify-storefront-api

shopify-storefront-api

github.com/finsilabs開発6096 回インストール5 日前に更新
リポジトリ

shopify-storefront-api について

Storefront GraphQL APIでカスタムShopifyストアフロントを構築する開発者向けスキルです。トークン設定、商品やコレクションの取得、カート操作を説明し、ヘッドレスサイトやモバイルアプリに対応します。

主な機能

  1. 01Storefront Access Tokenの作成
  2. 02GraphQLによる商品・コレクション取得
  3. 03カート作成と明細管理
  4. 04カートIDの保存とチェックアウトへの遷移
  5. 05予測検索とバリエーション選択の実装

活用シーン

  1. 01Next.jsやRemixでヘッドレスストアを構築
  2. 02モバイルアプリで商品・カート情報を取得
  3. 03Shopify以外のサイトに購入ウィジェットを埋め込み
  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 トークンはブラウザーのコードで安全に使用できる不透明な文字列であり、ストアフロントのスコープ内の操作のみを許可します。

  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 はレート制限が高く(100 req/s に対して 1000 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 を返すスラッグ化されたハンドルを正しく処理します。Shopify のハンドルは小文字とハイフンで構成されます。スラッグが正確か確認してください
モバイル Safari でチェックアウトへのリダイレクトに失敗する非同期コールバックではなく、ユーザー操作のイベントハンドラー内で window.location.href = checkoutUrl を使用します
オプションの選択時にバリエーションが見つからない1 つだけでなく、すべての selectedOptions が一致する variants.edges をクライアント側で絞り込みます

関連スキル

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

コメント