mcpField
F
faststore-data-fetching

faststore-data-fetching

github.com/vtex開発4780 回インストール2 週間前に更新
リポジトリ

faststore-data-fetching について

FastStore プロジェクト向けのコーディングスキルで、カタログデータの取得と GraphQL API 拡張を案内します。VTEX や外部データの扱いに加え、サーバー側での認証情報管理と所定の拡張構成を重視します。

主な機能

  1. 01FastStore GraphQL によるカタログ取得
  2. 02VTEX と外部 API の拡張
  3. 03サーバー・クライアント用フラグメント
  4. 04サーバー側でのシークレット管理
  5. 05リゾルバー構成と実装の確認

活用シーン

  1. 01商品クエリへの独自フィールド追加
  2. 02レビューや外部在庫データの連携
  3. 03サーバーフラグメントによる初期表示データ取得
  4. 04クライアント側の危険な API 呼び出しの確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/vtex/skills --skill faststore-data-fetching

インストール後、Claude Code で/faststore-data-fetchingを入力して読み込まれたか確認します。

詳細

FastStore データレイヤーと API 統合

このスキルを使用する場面

次の場合にこのスキルを使用します。

  • 商品データを取得する場合、既存のクエリにフィールドを追加する場合、またはサードパーティ API を統合する場合。
  • 標準の FastStore コンポーネントにデフォルトで表示される情報以外のデータが必要な場合。
  • src/graphql/vtex/ または src/graphql/thirdParty/ に API 拡張機能を作成する場合。
  • 定義済みのクエリに新しいフィールドを含めるため、src/fragments/ に GraphQL フラグメントを追加する場合。
  • VTEX REST API または外部サービスを呼び出すサーバーサイドリゾルバーを記述する場合。

次の場合、このスキルは使用しないでください。

  • クライアントサイドの状態管理(カート、セッション、検索)の場合は、faststore-state-management スキルを使用します。
  • ビジュアルテーマ設定の場合は、faststore-theming スキルを使用します。
  • コンポーネントの置き換えや props の上書きの場合は、faststore-overrides スキルを使用します。

判断ルール

  • カタログデータ(商品、コレクション、検索結果、価格)には、必ず FastStore GraphQL API を使用します。クライアントサイドコードから REST を直接呼び出してはいけません。
  • デフォルトでは公開されていない VTEX プラットフォームデータ(カスタム商品フィールド、分割払いの詳細など)にアクセスする場合は、VTEX API 拡張機能(src/graphql/vtex/)を使用します。
  • 外部データソース(レビュー、評価、外部在庫など)を統合する場合は、サードパーティ API 拡張機能(src/graphql/thirdParty/)を使用します。
  • ページの読み込み時に必要なデータ(SSR)には、サーバーフラグメント(src/fragments/ServerProduct.ts)を使用します。
  • 初回レンダリング後に読み込めるデータには、クライアントフラグメント(src/fragments/ClientProduct.ts)を使用します。
  • API キーとシークレットはサーバーサイドリゾルバー内だけに保管します。クライアントサイドコードや NEXT_PUBLIC_ 環境変数に含めてはいけません。
  • カスタム Next.js API ルート(pages/api/)は作成しないでください。代わりに API 拡張機能システムを使用します。

厳守事項

制約:カタログデータには GraphQL レイヤーを使用する

カタログデータ(商品、コレクション、検索結果、価格)の取得には、必ず FastStore GraphQL API を使用してください。クライアントサイドコードから VTEX Catalog API(/api/catalog/、/api/catalog_system/)を直接呼び出してはいけません。

この制約が重要な理由 FastStore API は認証、キャッシュ、リクエストのバッチ処理、データの正規化を処理します。REST を直接呼び出すと、こうした最適化をすべて迂回し、VTEX ドメインの構造をブラウザーにさらしてしまいます。また、CORS の問題を引き起こし、データ取得ロジックが重複し、GraphQL が提供する型安全性も利用できません。GraphQL リゾルバー内でサーバーサイドから VTEX API を REST 呼び出しすることは問題ありません。API 拡張機能は、まさにそのためのものです。

検出方法 クライアントサイドコード(コンポーネント、フック、useEffect)内に fetch('https://{account}.vtexcommercestable.com.br/api/catalog') または fetch('https://{account}.myvtex.com/api/catalog') がある場合 → GraphQL レイヤーを迂回していることを警告します。src/graphql/ 配下のリゾルバーのファイル内にある場合 → 問題ありません(これは API 拡張機能のパターンです)。src/components/ または src/pages/ 配下のファイルに VTEX API のパスを指定した axios または fetch がある場合 → 作業を中断し、GraphQL API を使用するようにリファクタリングしてください。

正しい例

// src/graphql/vtex/resolvers/product.ts
// Server-side resolver — REST calls to VTEX APIs are correct here
import type { Resolver } from '@faststore/api'

const productResolver: Record<string, Resolver> = {
  StoreProduct: {
    customAttribute: async (root, _args, context) => {
      // Server-side: safe to call VTEX REST APIs in resolvers
      const response = await context.clients.commerce.catalog.getProduct(
        root.productID
      )
      return response.customAttribute
    },
  },
}

export default productResolver

誤った例

// src/components/ProductCustomData.tsx
// WRONG: Direct REST call to VTEX Catalog API from a client component
import React, { useEffect, useState } from 'react'

interface ProductCustomDataProps {
  productId: string
}

export default function ProductCustomData({ productId }: ProductCustomDataProps) {
  const [data, setData] = useState(null)

  useEffect(() => {
    // WRONG: Direct REST call from the browser
    // This exposes the VTEX domain, bypasses caching, and creates CORS issues.
    fetch(`https://mystore.vtexcommercestable.com.br/api/catalog/pvt/product/${productId}`)
      .then((res) => res.json())
      .then(setData)
  }, [productId])

  return <div>{data?.Name}</div>
}

制約:クライアント側コードで API キーを絶対に公開しない

VTEX API キー(VTEX_APP_KEY、VTEX_APP_TOKEN)やその他のシークレット認証情報を、クライアント側コード、NEXT_PUBLIC_ で始まる環境変数、またはブラウザーにバンドルされるファイルに含めてはいけません。

重要な理由 クライアント側コードに含まれる API キーは、ページのソースやネットワークリクエストを調べる誰もが確認できます。VTEX API キーを使うと、カタログ管理、注文処理、アカウント管理にアクセスできます。キーが漏えいすると、商品を変更したり、顧客データにアクセスしたり、ストアの運営を妨害したりするおそれがあります。これは重大なセキュリティ脆弱性です。

検出方法 src/components/、src/pages/ 配下のファイル、またはブラウザーで実行されるファイルに VTEX_APP_KEY、VTEX_APP_TOKEN、X-VTEX-API-AppKey、X-VTEX-API-AppToken があれば、直ちに停止してください。これは重大なセキュリティ問題です。.env ファイルに NEXT_PUBLIC_VTEX_APP_KEY または NEXT_PUBLIC_VTEX_APP_TOKEN があれば、直ちに停止してください。NEXT_PUBLIC_ プレフィックスを付けると、これらの値はブラウザーバンドルから参照できるようになります。

正しい例

// src/graphql/vtex/resolvers/installments.ts
// API keys are used ONLY in server-side resolvers, accessed via context
import type { Resolver } from '@faststore/api'

const installmentResolver: Record<string, Resolver> = {
  StoreProduct: {
    availableInstallments: async (root, _args, context) => {
      // context.clients handles authentication automatically
      // No API keys are hardcoded or exposed
      const product = await context.clients.commerce.catalog.getProduct(
        root.productID
      )

      const installments = product.items?.[0]?.sellers?.[0]?.commertialOffer?.Installments || []

      return installments.map((inst: any) => ({
        count: inst.NumberOfInstallments,
        value: inst.Value,
        totalValue: inst.TotalValuePlusInterestRate,
        interestRate: inst.InterestRate,
      }))
    },
  },
}

export default installmentResolver

誤った例

// src/components/ProductInstallments.tsx
// CRITICAL SECURITY ISSUE: API keys exposed in client-side code
import React, { useEffect, useState } from 'react'

export default function ProductInstallments({ productId }: { productId: string }) {
  const [installments, setInstallments] = useState([])

  useEffect(() => {
    fetch(`https://mystore.vtexcommercestable.com.br/api/catalog/pvt/product/${productId}`, {
      headers: {
        // CRITICAL: These keys are now visible to EVERY visitor of your site.
        // Anyone can extract them from the browser's network tab.
        'X-VTEX-API-AppKey': 'vtexappkey-mystore-ABCDEF',
        'X-VTEX-API-AppToken': 'very-secret-token-12345',
      },
    })
      .then((res) => res.json())
      .then((data) => setInstallments(data.Installments))
  }, [productId])

  return <div>{installments.length} installments available</div>
}

制約:API 拡張のディレクトリ構成に従う

API 拡張ファイルは正しいディレクトリ構成に配置してください。VTEX API 拡張は src/graphql/vtex/、サードパーティ API 拡張は src/graphql/thirdParty/ に配置します。それぞれに typeDefs/ と resolvers/ サブディレクトリが必要です。

重要な理由 FastStore のビルドシステムは、これらの特定のディレクトリから API 拡張を検出してコンパイルします。別の場所に配置されたファイルは GraphQL スキーマに含まれず、リゾルバーも実行されません。ビルド時にエラーは発生せず、拡張フィールドが存在しないままとなるため、コンポーネントがそれらをクエリしようとすると実行時に GraphQL エラーが発生します。

検出方法 src/graphql/vtex/ または src/graphql/thirdParty/ の外に GraphQL 型定義(.graphql ファイル)やリゾルバーファイルがあれば、ビルドシステムで検出されないことを警告してください。typeDefs/ または resolvers/ サブディレクトリがない場合は、構成が正しくないことを警告してください。

正しい例

# src/graphql/vtex/typeDefs/product.graphql
type StoreProduct {
  availableInstallments: [Installment]
}

type Installment {
  count: Int
  value: Float
  totalValue: Float
  interestRate: Float
}
// src/graphql/vtex/resolvers/product.ts
import type { Resolver } from '@faststore/api'

const productResolver: Record<string, Resolver> = {
  StoreProduct: {
    availableInstallments: async (root, _args, context) => {
      const product = await context.clients.commerce.catalog.getProduct(
        root.productID
      )
      const installments =
        product.items?.[0]?.sellers?.[0]?.commertialOffer?.Installments || []

      return installments.map((inst: any) => ({
        count: inst.NumberOfInstallments,
        value: inst.Value,
        totalValue: inst.TotalValuePlusInterestRate,
        interestRate: inst.InterestRate,
      }))
    },
  },
}

export default productResolver
// src/graphql/vtex/resolvers/index.ts
import { default as StoreProductResolver } from './product'

const resolvers = {
  ...StoreProductResolver,
}

export default resolvers

誤った例

// WRONG: Resolver placed in src/api/ instead of src/graphql/vtex/resolvers/
// src/api/resolvers/product.ts
// This file will NOT be discovered by FastStore's build system.
// The GraphQL schema will NOT include the extended fields.
// Components querying these fields will get runtime errors.

const productResolver = {
  StoreProduct: {
    availableInstallments: async (root: any) => {
      return []
    },
  },
}

export default productResolver

推奨パターン

API 拡張の推奨ファイル構成:

src/
├── graphql/
│   ├── vtex/
│   │   ├── typeDefs/
│   │   │   └── product.graphql
│   │   └── resolvers/
│   │       ├── product.ts
│   │       └── index.ts
│   └── thirdParty/
│       ├── typeDefs/
│       │   └── extra.graphql
│       └── resolvers/
│           ├── reviews.ts
│           └── index.ts
└── fragments/
    ├── ServerProduct.ts    ← server-side fragment (SSR)
    └── ClientProduct.ts    ← client-side fragment (post-render)

最小限の API 拡張 — StoreProduct にフィールドを追加する例:

# src/graphql/vtex/typeDefs/product.graphql
type StoreProduct {
  availableInstallments: [Installment]
}

type Installment {
  count: Int!
  value: Float!
  totalValue: Float!
  interestRate: Float!
}
// src/graphql/vtex/resolvers/product.ts
import type { Resolver } from '@faststore/api'

const productResolver: Record<string, Resolver> = {
  StoreProduct: {
    availableInstallments: async (root, _args, context) => {
      const product = await context.clients.commerce.catalog.getProduct(
        root.productID
      )
      const installments =
        product.items?.[0]?.sellers?.[0]?.commertialOffer?.Installments || []

      return installments.map((inst: any) => ({
        count: inst.NumberOfInstallments,
        value: inst.Value,
        totalValue: inst.TotalValuePlusInterestRate,
        interestRate: inst.InterestRate,
      }))
    },
  },
}

export default productResolver

フラグメントを使ってクエリに新しいフィールドを含める例:

// src/fragments/ServerProduct.ts
import { gql } from '@faststore/core/api'

export const fragment = gql(`
  fragment ServerProduct on Query {
    product(locator: $locator) {
      availableInstallments {
        count
        value
        totalValue
        interestRate
      }
    }
  }
`)

サードパーティ API 拡張(例:商品レビュー):

// src/graphql/thirdParty/resolvers/reviews.ts
import type { Resolver } from '@faststore/api'

const REVIEWS_API_KEY = process.env.REVIEWS_API_KEY // Server-only env var (no NEXT_PUBLIC_ prefix)

const reviewsResolver: Record<string, Resolver> = {
  StoreProduct: {
    reviews: async (root) => {
      const response = await fetch(
        `https://api.reviews-service.com/products/${root.productID}/reviews`,
        {
          headers: {
            Authorization: `Bearer ${REVIEWS_API_KEY}`,
          },
        }
      )
      const data = await response.json()
      return {
        averageRating: data.average_rating,
        totalReviews: data.total_count,
        reviews: data.reviews.slice(0, 5).map((r: any) => ({
          author: r.author_name,
          rating: r.rating,
          text: r.review_text,
          date: r.created_at,
        })),
      }
    },
  },
}

export default reviewsResolver

よくある失敗例

  • React コンポーネントから VTEX Catalog API に直接 REST 呼び出しを行う — CORS の問題が発生し、キャッシュを迂回し、VTEX アカウントの構成をブラウザーに公開します。
  • API キー(VTEX_APP_KEY、VTEX_APP_TOKEN)をクライアント側コードや NEXT_PUBLIC_ 環境変数で公開する — 重大なセキュリティ脆弱性です。
  • リゾルバーや型定義を src/graphql/vtex/ または src/graphql/thirdParty/ の外に配置する — ビルドシステムで検出されません。
  • API 拡張システムを使わずにカスタム Next.js API ルート(pages/api/)を作成する — キャッシュ、型安全性、リクエストのバッチ処理を迂回します。
  • すべてのリゾルバーを再エクスポートするリゾルバーインデックスファイル(src/graphql/vtex/resolvers/index.ts)を作成し忘れる。

レビューチェックリスト

  • カタログデータはすべて FastStore GraphQL API 経由で取得していますか(コンポーネントから直接 REST 呼び出しをしていませんか)?
  • API 拡張ファイルは、適切な typeDefs/ と resolvers/ サブディレクトリを設けて src/graphql/vtex/ または src/graphql/thirdParty/ に配置されていますか?
  • リゾルバーインデックスファイルは、すべてのリゾルバーを再エクスポートしていますか?
  • API キーとシークレットはサーバー側のリゾルバーでのみ使用されていますか(NEXT_PUBLIC_ プレフィックスなし)?
  • 定義済みクエリに新しいフィールドを含めるため、src/fragments/ にフラグメントを作成していますか?
  • API 拡張に置き換えられるカスタム Next.js API ルートはありませんか?

参考資料

コメント