关于 faststore-data-fetching
面向 FastStore 项目的编码技能,指导目录数据获取与 GraphQL API 扩展,涵盖 VTEX 和第三方数据,并强调凭据应在服务端处理及扩展目录规范。
主要功能
- 01通过 FastStore GraphQL 获取目录数据
- 02扩展 VTEX 和第三方 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 API。
- 访问 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/)的 REST 接口。
原因 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 密钥
严禁在客户端代码、以 NEXT_PUBLIC_ 为前缀的环境变量或任何会被打包到浏览器中的文件中包含 VTEX API 密钥(VTEX_APP_KEY、VTEX_APP_TOKEN)或任何机密凭据。
重要原因 客户端代码中的 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 schema,解析器也不会执行。构建时不会报错——扩展字段只会不存在,导致组件尝试查询这些字段时出现运行时 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 账户结构。
- 在客户端代码或
NEXT_PUBLIC_环境变量中暴露 API 密钥(VTEX_APP_KEY、VTEX_APP_TOKEN)——这是严重的安全漏洞。 - 将解析器或类型定义放在
src/graphql/vtex/或src/graphql/thirdParty/之外——构建系统不会发现这些文件。 - 不使用 API 扩展系统,而是创建自定义 Next.js API 路由(
pages/api/)——这会绕过缓存、类型安全和请求批处理。 - 忘记创建重新导出所有解析器的解析器索引文件(
src/graphql/vtex/resolvers/index.ts)。
审查清单
- 所有目录数据是否都通过 FastStore GraphQL API 获取(而不是在组件中直接调用 REST 接口)?
- API 扩展文件是否位于
src/graphql/vtex/或src/graphql/thirdParty/中,并包含正确的typeDefs/和resolvers/子目录? - 解析器索引文件是否重新导出了所有解析器?
- 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 钩子进行客户端状态管理的相关技能