faststore-data-fetching について
FastStore プロジェクト向けのコーディングスキルで、カタログデータの取得と GraphQL API 拡張を案内します。VTEX や外部データの扱いに加え、サーバー側での認証情報管理と所定の拡張構成を重視します。
主な機能
- 01FastStore GraphQL によるカタログ取得
- 02VTEX と外部 API の拡張
- 03サーバー・クライアント用フラグメント
- 04サーバー側でのシークレット管理
- 05リゾルバー構成と実装の確認
活用シーン
- 01商品クエリへの独自フィールド追加
- 02レビューや外部在庫データの連携
- 03サーバーフラグメントによる初期表示データ取得
- 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 ルートはありませんか?
参考資料
- FastStore API の概要 — GraphQL API とその機能の紹介
- API 拡張機能の概要 — カスタムデータで FastStore API を拡張するためのガイド
- VTEX API スキーマの拡張 — VTEX プラットフォームのデータを GraphQL スキーマに追加する手順
- サードパーティ API スキーマの拡張 — 外部データソースの統合
- フラグメントを使用したクエリの拡張 — フラグメントを使用して定義済みクエリにフィールドを追加する方法
- カスタムコンポーネントでの API 拡張機能の利用 — React コンポーネントで拡張データを使用する方法
- GraphQL スキーマオブジェクト — すべてのネイティブ GraphQL 型(StoreProduct、StoreOffer など)のリファレンス
- GraphQL クエリのリファレンス — FastStore API で利用可能なすべての定義済みクエリ
- API 拡張機能のトラブルシューティング — API 拡張機能でよくある問題とその解決策
faststore-state-management— SDK フックを使用したクライアント側の状態管理に関する関連スキル