storefront-builder
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseSaleor Storefront Playbook
Saleor 店面开发实践手册
This skill owns Saleor data contracts and UX/data-layer behaviour. It does not own framework scaffolding, CSS setup, or env-loading specifics — the agent discovers those from the local project.
Parse to determine which step to run.
$ARGUMENTS本Skill负责Saleor的数据契约以及UX/数据层行为。它不负责框架脚手架、CSS配置或环境加载的具体细节——Agent会从本地项目中自动识别这些内容。
解析来确定要执行的步骤。
$ARGUMENTSStep routing
步骤路由
Read the first word of as the step number and jump to that section. Execute only that step, then stop and wait for the user to ask for the next one. Never chain steps automatically.
$ARGUMENTS- → Step 1: Project Bootstrap
1 - → Step 2: Design & Aesthetic
2 - → Step 3: Catalog — Product List + PDP
3
If no step is provided or the step is unrecognized, print:
Saleor Storefront Builder
Usage: /storefront-builder <step>
Steps:
1 Bootstrap — wire GraphQL client, codegen, Saleor API connection
2 Design & aesthetic — color palette, typography, accent color
3 Catalog — product list page + product detail page with variant selection
Example: /storefront-builder 1读取的第一个单词作为步骤编号,并跳转到对应章节。仅执行指定步骤,完成后停止并等待用户请求下一步。禁止自动执行后续步骤。
$ARGUMENTS- → 步骤1:项目初始化
1 - → 步骤2:设计与视觉风格
2 - → 步骤3:商品目录——产品列表页 + 产品详情页
3
如果未提供步骤或步骤编号不被识别,输出以下内容:
Saleor Storefront Builder
使用方法: /storefront-builder <step>
步骤:
1 初始化 — 连接GraphQL客户端、代码生成工具、Saleor API
2 设计与视觉风格 — 调色板、排版、强调色
3 商品目录 — 产品列表页 + 带变体选择的产品详情页
示例: /storefront-builder 1Step 1: Project Bootstrap
步骤1:项目初始化
Connect an existing project to Saleor's GraphQL API with correct client separation and codegen.
将现有项目连接到Saleor的GraphQL API,配置正确的客户端分离和代码生成工具。
0. Saleor instance check
0. Saleor实例检查
Ask the user:
"Do you have a Saleor instance ready?
- No — create one at https://cloud.saleor.io/ (free tier available), then come back with the API URL.
- Yes — paste your storefront/API URL and we'll get started."
Wait for the user's response before continuing. If they don't have an instance yet, stop here and let them set one up. If they provide a URL, note it for use in step 6.
询问用户:
"你是否已准备好Saleor实例?
- 否 — 前往https://cloud.saleor.io/创建(提供免费套餐),然后返回并提供API地址。
- 是 — 粘贴你的店面/API地址,我们将开始配置。"
等待用户回复后再继续。如果用户还没有实例,在此处停止,让用户先完成实例创建。如果用户提供了地址,记录下来以便在步骤6中使用。
1. Inspect the project
1. 检查项目
Read and any framework config files present (, , , , , etc.) to understand:
package.jsonnuxt.config.tsnext.config.*svelte.config.jsremix.config.jsvite.config.*- Framework and version
- Package manager in use (check for lockfiles: ,
pnpm-lock.yaml,yarn.lock)package-lock.json - Existing GraphQL setup (if any)
- Import alias conventions (e.g. ,
@/,~/)# - Source directory layout (,
src/, flat root)app/
Do not ask about any of the above — derive it from the project. Only ask if something cannot be determined and is needed to proceed.
读取和所有存在的框架配置文件(、、、、等),以了解:
package.jsonnuxt.config.tsnext.config.*svelte.config.jsremix.config.jsvite.config.*- 使用的框架及版本
- 使用的包管理器(通过锁文件判断:、
pnpm-lock.yaml、yarn.lock)package-lock.json - 已有的GraphQL配置(如果存在)
- 导入别名约定(如、
@/、~/)# - 源码目录结构(、
src/、根目录平铺)app/
无需询问上述信息——直接从项目中推导。仅当无法确定且该信息是继续操作的必要条件时,才向用户询问。
2. Create AGENTS.md
2. 创建AGENTS.md
If does not already exist at the repo root, create it now. This wires Saleor-specific rules into the AI harness for all future interactions in this repo.
AGENTS.mdCheck for installed skills:
bash
ls .agent-skills/saleor-storefront/AGENTS.md 2>/dev/null && echo "STOREFRONT" || echo ""
ls .agent-skills/saleor-configurator/AGENTS.md 2>/dev/null && echo "CONFIGURATOR" || echo ""Write , including only the references for skills that are present:
AGENTS.md@markdown
undefined如果仓库根目录下不存在,立即创建。该文件会将Saleor专属规则集成到AI工具中,用于本仓库未来的所有交互。
AGENTS.md检查已安装的Skill:
bash
ls .agent-skills/saleor-storefront/AGENTS.md 2>/dev/null && echo "STOREFRONT" || echo ""
ls .agent-skills/saleor-configurator/AGENTS.md 2>/dev/null && echo "CONFIGURATOR" || echo ""编写,仅包含已存在的Skill的引用:
AGENTS.md@markdown
undefinedSaleor Storefront
Saleor Storefront
This is a Saleor-powered storefront.
这是一个基于Saleor的店面项目。
Workflow
工作流程
When running , execute only the requested step, then stop and wait for the user to ask for the next one. Never chain steps automatically.
/storefront-builder运行时,仅执行请求的步骤,完成后停止并等待用户请求下一步。禁止自动执行后续步骤。
/storefront-builderSaleor rules
Saleor规则
<!-- include if .agent-skills/saleor-storefront/ exists -->
@.agent-skills/saleor-storefront/AGENTS.md
<!-- include if .agent-skills/saleor-configurator/ exists -->
@.agent-skills/saleor-configurator/AGENTS.md
If `AGENTS.md` already exists, skip this step entirely — do not overwrite it.<!-- 如果.agent-skills/saleor-storefront/存在则包含 -->
@.agent-skills/saleor-storefront/AGENTS.md
<!-- 如果.agent-skills/saleor-configurator/存在则包含 -->
@.agent-skills/saleor-configurator/AGENTS.md
如果`AGENTS.md`已存在,完全跳过此步骤——不要覆盖现有文件。3. Install GraphQL dependencies
3. 安装GraphQL依赖
Using the package manager detected in step 1:
graphql-request graphql
@graphql-codegen/cli @graphql-codegen/client-preset (dev)使用步骤1中检测到的包管理器安装以下依赖:
graphql-request graphql
@graphql-codegen/cli @graphql-codegen/client-preset (开发依赖)4. Create codegen config
4. 创建代码生成配置
Write a codegen config file at the project root (filename: or based on project conventions). Key values to set:
codegen.tscodegen.js- schema: Saleor GraphQL API URL, read from the env variable the project uses (or if none is established)
SALEOR_API_URL - documents: glob pointing to the project's GraphQL files directory, following local conventions
- generates: use the preset with
clientgqlTagName: "graphql"
Add a script to .
codegenpackage.json在项目根目录下编写代码生成配置文件(根据项目约定选择文件名:或)。需要设置的关键值:
codegen.tscodegen.js- schema: Saleor GraphQL API地址,从项目使用的环境变量中读取(如果未建立相关变量,则使用)
SALEOR_API_URL - documents: 符合本地约定的、指向项目GraphQL文件目录的glob路径
- generates: 使用预设,并设置
clientgqlTagName: "graphql"
在中添加脚本。
package.jsoncodegen5. Create Saleor API clients
5. 创建Saleor API客户端
Two-client pattern — this is a Saleor correctness rule, not optional:
Write a client module in the location that matches the project's library/util conventions. Export two clients:
saleorClient — anonymous, no auth headers — safe for RSC, SSG, public product queries
saleorAuthClient — server-only, reads app token from env — NEVER use in browser bundlesWhy two clients matter: passing an app token on public/cached queries leaks privileged access and can expose customer data. Anonymous queries must stay anonymous.
The auth client should only include the header when the token env var is set (guard with a conditional so the module doesn't throw on front-end environments where the var is absent).
Authorization双客户端模式——这是Saleor的正确性规则,不可省略:
在符合项目工具类/库约定的位置编写客户端模块。导出两个客户端:
saleorClient — 匿名客户端,无认证头 — 适用于RSC、SSG、公开产品查询
saleorAuthClient — 仅服务器端使用,从环境变量读取应用令牌 — 绝不能在浏览器包中使用双客户端的重要性: 在公开/缓存查询中传递应用令牌会泄露特权访问权限,可能暴露客户数据。匿名查询必须保持匿名。
仅当令牌环境变量存在时,认证客户端才应包含头(使用条件判断,避免在未设置该变量的前端环境中抛出错误)。
Authorization6. Configure environment
6. 配置环境变量
Determine the env variable naming convention from the project (e.g. Next.js uses for browser-accessible vars, Nuxt uses , etc.).
NEXT_PUBLIC_*NUXT_PUBLIC_*Required variables:
- — Saleor GraphQL endpoint
[PUBLIC_PREFIX]_SALEOR_API_URL - — default channel slug
[PUBLIC_PREFIX]_SALEOR_CHANNEL - (no public prefix — server-side only)
SALEOR_APP_TOKEN
Write or update the project's env file (, , etc.) with placeholder values and comments. Ask the user if they have a Saleor API URL and channel slug to fill in.
.env.local.envTip — inspecting an existing store with Configurator If you have access to an existing Saleor instance and are unsure what channels, categories, or products are configured, use the Configurator CLI:bashexport SALEOR_URL=https://your-store.saleor.cloud/graphql/ export SALEOR_TOKEN=YOUR_TOKEN pnpm dlx @saleor/configurator introspectRead the resultingto find exact channel slugs, published products, and category structure — use these values directly in env and queries.config.yml
从项目中推导环境变量命名约定(例如Next.js使用表示可浏览器访问的变量,Nuxt使用等)。
NEXT_PUBLIC_*NUXT_PUBLIC_*必填变量:
- — Saleor GraphQL端点
[PUBLIC_PREFIX]_SALEOR_API_URL - — 默认渠道别名
[PUBLIC_PREFIX]_SALEOR_CHANNEL - (无公共前缀——仅服务器端使用)
SALEOR_APP_TOKEN
在项目的环境文件(、等)中写入或更新占位符值及注释。询问用户是否有Saleor API地址和渠道别名来填充这些值。
.env.local.env提示——使用Configurator检查现有店面 如果你有权访问现有Saleor实例,但不确定已配置的渠道、分类或商品,可以使用Configurator CLI:bashexport SALEOR_URL=https://your-store.saleor.cloud/graphql/ export SALEOR_TOKEN=YOUR_TOKEN pnpm dlx @saleor/configurator introspect查看生成的文件,获取准确的渠道别名、已发布产品和分类结构——直接将这些值用于环境变量和查询。config.yml
7. Verify
7. 验证配置
If the API URL is configured, run codegen to confirm the schema is reachable:
bash
[package-manager] codegen 2>&1 | head -20If it fails with a network error, help troubleshoot (wrong URL, missing auth, etc.).
如果已配置API地址,运行代码生成工具以确认架构可访问:
bash
[包管理器] codegen 2>&1 | head -20如果因网络错误失败,帮助排查问题(地址错误、缺少认证等)。
8. Summary
8. 总结
[✓/–] AGENTS.md: [created / already existed]
✓ Framework: [detected framework]
✓ Package manager: [pm]
✓ Deps: graphql-request, @graphql-codegen/cli, @graphql-codegen/client-preset
✓ Clients: [path] (public + authenticated)
✓ Codegen: codegen.ts
[✓/⚠] API URL: [set / not set]
[✓/⚠] Channel: [slug / placeholder]
Next: /storefront-builder 2After printing the summary, stop. Do not proceed to Step 2 unless the user explicitly asks.
[✓/–] AGENTS.md: [已创建 / 已存在]
✓ 框架: [检测到的框架]
✓ 包管理器: [包管理器名称]
✓ 依赖: graphql-request, @graphql-codegen/cli, @graphql-codegen/client-preset
✓ 客户端: [路径](公开 + 认证)
✓ 代码生成配置: codegen.ts
[✓/⚠] API地址: [已设置 / 未设置]
[✓/⚠] 渠道: [别名 / 占位符]
下一步: /storefront-builder 2输出总结后停止。除非用户明确请求,否则不要执行步骤2。
Step 2: Design & Aesthetic
步骤2:设计与视觉风格
Define the visual identity of the storefront before writing any UI code. The output of this step is a theme module and design tokens that all future steps will import. The exact file paths and token format follow the project's existing conventions.
在编写任何UI代码之前,定义店面的视觉标识。本步骤的输出是一个主题模块和设计令牌,供后续所有步骤导入。文件路径和令牌格式遵循项目的现有约定。
1. Inspect the project's styling setup
1. 检查项目的样式配置
Read the project to determine:
- CSS framework in use (Tailwind, CSS Modules, styled-components, UnoCSS, vanilla CSS, etc.)
- Existing design token conventions (CSS custom properties, a file, Tailwind config, etc.)
theme.* - Where shared styles live
Do not assume Tailwind or any specific CSS approach — derive it from the project.
读取项目以确定:
- 使用的CSS框架(Tailwind、CSS Modules、styled-components、UnoCSS、原生CSS等)
- 已有的设计令牌约定(CSS自定义属性、文件、Tailwind配置等)
theme.* - 共享样式的存放位置
不要默认使用Tailwind或任何特定CSS方案——直接从项目中推导。
2. Ask about the aesthetic
2. 询问视觉风格偏好
Ask the user three questions in one message — conversational, not a form:
"Let's define the look of your storefront. A few quick questions:
- Do you have any references? (a brand, a URL, a screenshot — or skip)
- What's the general vibe? Some starting points if helpful: minimalist light, dark luxury, bold & colorful, soft & warm, classic editorial — or describe it in your own words.
- Any accent color in mind? This goes on buttons and links. A hex, a color name, or leave it to me."
If the user gives very little, ask one follow-up before proceeding.
在一条消息中向用户提出三个问题——采用对话式语气,而非表单式:
"让我们定义你的店面外观。几个简单的问题:
- 你是否有参考示例?(品牌、网址、截图——或跳过)
- 整体风格倾向?提供一些参考方向:极简浅色、深色奢华、大胆多彩、柔和温暖、经典排版风——或用你自己的语言描述。
- 是否有指定的强调色?将用于按钮和链接。可以是十六进制值、颜色名称,或由我推荐。"
如果用户提供的信息很少,最多追问一次后再继续。
3. Decide on tokens
3. 确定设计令牌值
Determine values for: background, surface, border, text primary, text secondary, accent, accent-hover, border radius, heading font, body font.
确定以下值:背景色、表面色、边框色、主文本色、次要文本色、强调色、强调色悬停态、边框圆角、标题字体、正文字体。
4. Write theme tokens
4. 编写主题令牌
Write a theme module in a location consistent with the project's conventions. Include a comment block capturing:
- Style preset name
- Reference (if any)
- Accent rationale
- Typography choice
Wire the tokens into the project's styling system following local conventions:
- Tailwind: extend with the token values
tailwind.config.* - CSS custom properties: write to the project's global CSS file
- Other: follow what's already in use
Update the global/base CSS to apply background and text defaults.
在符合项目约定的位置编写主题模块。包含注释块,记录:
- 样式预设名称
- 参考示例(如果有)
- 强调色选择理由
- 排版选择
按照本地约定将令牌集成到项目的样式系统中:
- Tailwind: 在中扩展令牌值
tailwind.config.* - CSS自定义属性: 写入项目的全局CSS文件
- 其他方案: 遵循项目已有的配置方式
更新全局/基础CSS以应用背景色和文本默认值。
5. Summary
5. 总结
✓ Style: [preset name]
✓ Accent: [color]
✓ Typography: [font choice]
✓ Theme tokens: [path]
✓ Styling system updated: [tailwind.config / globals.css / etc.]
Next: /storefront-builder 3After printing the summary, stop. Do not proceed to Step 3 unless the user explicitly asks.
✓ 风格: [预设名称]
✓ 强调色: [颜色]
✓ 排版: [字体选择]
✓ 主题令牌: [路径]
✓ 样式系统已更新: [tailwind.config / globals.css / 等]
下一步: /storefront-builder 3输出总结后停止。除非用户明确请求,否则不要执行步骤3。
Step 3: Catalog — Product List + PDP
步骤3:商品目录——产品列表页 + 产品详情页
Build a product listing page and product detail page with variant selection.
构建产品列表页和带变体选择的产品详情页。
Prerequisites check
前置检查
Verify the Saleor client module exists (search for it based on what was set up in Step 1). If missing, tell the user to run first.
/storefront-builder 1Check for a channel slug in the project's env file. If missing and not passed as argument, ask:
"What's your Saleor channel slug? (Saleor Dashboard → Channels, or press Enter for 'default-channel')"
Inspect the framework and routing conventions from the project to determine where to write pages and how data-fetching works (server components, , loaders, functions, , etc.).
getStaticPropsloadasyncData验证Saleor客户端模块是否存在(根据步骤1中创建的内容进行搜索)。如果不存在,告知用户先运行。
/storefront-builder 1检查项目环境文件中是否存在渠道别名。如果不存在且未作为参数传递,询问:
"你的Saleor渠道别名是什么?(可在Saleor后台 → 渠道中查看,或按Enter使用'default-channel')"
从项目中检测框架和路由约定,以确定页面的编写位置和数据获取方式(服务器组件、、loaders、函数、等)。
getStaticPropsloadasyncData1. GraphQL queries — Saleor data contracts
1. GraphQL查询——Saleor数据契约
Write a file in the project's GraphQL documents directory.
products.graphql在项目的GraphQL文档目录中编写文件。
products.graphqlProductCard fragment
ProductCard 片段
Required fields for a product listing surface:
graphql
fragment ProductCard on Product {
id
name
slug
thumbnail {
url
alt
}
pricing {
priceRange {
start {
gross {
amount
currency
}
}
}
}
category {
name
slug
}
}Why these fields:
- is nullable — always guard with a fallback image or placeholder
thumbnail - is nullable — guard before rendering price
pricing.priceRange.start - is nullable — guard before rendering category label
category
产品列表页所需的字段:
graphql
fragment ProductCard on Product {
id
name
slug
thumbnail {
url
alt
}
pricing {
priceRange {
start {
gross {
amount
currency
}
}
}
}
category {
name
slug
}
}选择这些字段的原因:
- 可为空——必须始终设置备用图片或占位符
thumbnail - 可为空——渲染价格前需进行判断
pricing.priceRange.start - 可为空——渲染分类标签前需进行判断
category
ProductDetails fragment
ProductDetails 片段
Required fields for a PDP surface:
graphql
fragment ProductDetails on Product {
id
name
slug
description
thumbnail {
url
alt
}
media {
url
alt
type
}
pricing {
priceRange {
start {
gross {
amount
currency
}
}
}
}
category {
name
slug
}
variants {
id
name
sku
pricing {
price {
gross {
amount
currency
}
}
priceUndiscounted {
gross {
amount
currency
}
}
}
selectionAttributes: attributes(variantSelection: VARIANT_SELECTION) {
attribute {
name
slug
}
values {
name
slug
}
}
quantityAvailable
}
}Why these fields:
- array preferred over
mediaon PDP — usethumbnailas fallback whenthumbnailis emptymedia - is nullable — guard before accessing
variants.pricingamount - is nullable for anonymous users — treat
quantityAvailableas in-stock (behave as if 1 available)null - uses
selectionAttributesfilter — returns only variant-differentiating attributes (size, color, etc.), not product-level attributesvariantSelection: VARIANT_SELECTION
产品详情页所需的字段:
graphql
fragment ProductDetails on Product {
id
name
slug
description
thumbnail {
url
alt
}
media {
url
alt
type
}
pricing {
priceRange {
start {
gross {
amount
currency
}
}
}
}
category {
name
slug
}
variants {
id
name
sku
pricing {
price {
gross {
amount
currency
}
}
priceUndiscounted {
gross {
amount
currency
}
}
}
selectionAttributes: attributes(variantSelection: VARIANT_SELECTION) {
attribute {
name
slug
}
values {
name
slug
}
}
quantityAvailable
}
}选择这些字段的原因:
- 产品详情页优先使用数组而非
media——当thumbnail为空时,使用media作为备用thumbnail - 可为空——访问
variants.pricing前需进行判断amount - 匿名用户的可为空——将
quantityAvailable视为有库存(按1件可用处理)null - 使用
selectionAttributes过滤器——仅返回区分变体的属性(尺寸、颜色等),不返回产品级属性variantSelection: VARIANT_SELECTION
Queries
查询语句
graphql
query ProductList($channel: String!, $first: Int = 20, $after: String) {
products(channel: $channel, first: $first, after: $after) {
edges {
node {
...ProductCard
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
query ProductBySlug($slug: String!, $channel: String!) {
product(slug: $slug, channel: $channel) {
...ProductDetails
}
}Channel is always required — queries without return no pricing or availability data.
channelRun codegen after writing the queries.
graphql
query ProductList($channel: String!, $first: Int = 20, $after: String) {
products(channel: $channel, first: $first, after: $after) {
edges {
node {
...ProductCard
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
query ProductBySlug($slug: String!, $channel: String!) {
product(slug: $slug, channel: $channel) {
...ProductDetails
}
}渠道参数是必填项——不带的查询不会返回定价或库存数据。
channel编写查询后运行代码生成工具。
2. Saleor data handling rules
2. Saleor数据处理规则
Apply these rules when implementing the pages and components:
实现页面和组件时,需遵循以下规则:
Description parsing
描述解析
Saleor stores as EditorJS JSON. Never render it raw. Parse safely:
descriptiontypescript
function extractDescriptionText(description: unknown): string {
try {
const parsed = typeof description === "string" ? JSON.parse(description) : description;
return parsed?.blocks
?.map((b: { data?: { text?: string } }) => b.data?.text ?? "")
.filter(Boolean)
.join(" ") ?? "";
} catch {
return "";
}
}Saleor以EditorJS JSON格式存储。绝不能直接渲染原始内容。需安全解析:
descriptiontypescript
function extractDescriptionText(description: unknown): string {
try {
const parsed = typeof description === "string" ? JSON.parse(description) : description;
return parsed?.blocks
?.map((b: { data?: { text?: string } }) => b.data?.text ?? "")
.filter(Boolean)
.join(" ") ?? "";
} catch {
return "";
}
}Price formatting
价格格式化
Always use with the currency from the response — never hardcode currency symbols:
Intl.NumberFormattypescript
function formatPrice(amount: number, currency: string) {
return new Intl.NumberFormat(undefined, { style: "currency", currency }).format(amount);
}Use locale to respect the user's browser locale (or pass a locale if the project has a locale system).
undefined始终使用并传入响应中的货币代码——绝不能硬编码货币符号:
Intl.NumberFormattypescript
function formatPrice(amount: number, currency: string) {
return new Intl.NumberFormat(undefined, { style: "currency", currency }).format(amount);
}使用 locale以尊重用户的浏览器区域设置(如果项目有区域设置系统,也可传入指定locale)。
undefinedImage handling
图片处理
- On PDP: prefer over
product.media[0]; fall back tothumbnailifthumbnailis emptymedia - Always guard for missing images — render a neutral placeholder, not a broken tag
<img> - Use as the alt text fallback
alt ?? product.name
- 产品详情页:优先使用而非
product.media[0];如果thumbnail为空,回退到mediathumbnail - 始终处理图片缺失的情况——渲染中性占位符,而非损坏的标签
<img> - 使用作为alt文本的备用值
alt ?? product.name
Inventory / availability semantics
库存/可用性规则
- → treat as available (anonymous users don't see inventory)
quantityAvailable === null - → out of stock — disable selection and show visual indicator (strikethrough or muted)
quantityAvailable === 0 - → in stock
quantityAvailable > 0
- → 视为有库存(匿名用户无法获取库存数量)
quantityAvailable === null - → 缺货——禁用选择并显示视觉标识(删除线或灰化)
quantityAvailable === 0 - → 有库存
quantityAvailable > 0
Variant selection UX
变体选择UX
- Show all variants; disable (not hide) out-of-stock ones — visibility helps users understand what exists
- Use to label variants (e.g. "Size: M", "Color: Red") when attributes are present
selectionAttributes - If a product has only one variant and no selection attributes, skip the selector and go straight to Add to Cart
- The selected variant's overrides the product-level
pricing.price— update the displayed price on selectionpricing.priceRange
- 显示所有变体;禁用(而非隐藏)缺货变体——可见性有助于用户了解产品的所有选项
- 如果存在属性,使用为变体添加标签(如"尺寸: M"、"颜色: 红")
selectionAttributes - 如果产品只有一个变体且无选择属性,跳过选择器,直接显示加入购物车按钮
- 选中变体的会覆盖产品级的
pricing.price——选择变体时更新显示的价格pricing.priceRange
Empty / error states
空状态/错误状态
- Product list with no results: show a clear message with troubleshooting hint (wrong channel slug or products not published)
- Product not found (null from ): use the framework's not-found/404 mechanism
ProductBySlug - Pricing missing: omit price entirely rather than showing $0 or NaN
- 产品列表无结果:显示清晰的提示信息及排查建议(渠道别名错误或产品未在该渠道发布)
- 产品未找到(返回null):使用框架的404/未找到机制
ProductBySlug - 定价缺失:完全不显示价格,而非显示$0或NaN
3. Write shared navigation
3. 编写共享导航组件
Write a nav/header component in the project's component directory following local naming conventions. The nav should use the theme tokens established in Step 2 (or sensible neutral defaults if Step 2 was skipped).
Wire the nav into the root layout / app shell following framework conventions detected from the project.
在符合本地命名约定的项目组件目录中编写导航/头部组件。导航应使用步骤2中建立的主题令牌(如果跳过了步骤2,则使用合理的中性默认值)。
按照检测到的框架约定,将导航组件集成到根布局/应用外壳中。
4. Write product list page
4. 编写产品列表页
Write the product list page at the path that fits the project's routing conventions (e.g. , , , , ).
app/page.tsxpages/index.tsxpages/index.vuesrc/routes/+page.svelteapp/routes/_index.tsxData-fetching pattern: use whatever the framework provides (async server component, /ISR, function, , Remix loader). For SSG-capable frameworks, set a reasonable revalidation interval (e.g. 60s).
getStaticPropsloadasyncDataApply all data handling rules from section 2: guard nullables, format prices correctly, show empty state.
在符合项目路由约定的路径下编写产品列表页(如、、、、)。
app/page.tsxpages/index.tsxpages/index.vuesrc/routes/+page.svelteapp/routes/_index.tsx数据获取模式:使用框架提供的方式(异步服务器组件、/ISR、函数、、Remix loader)。对于支持SSG的框架,设置合理的重新验证间隔(如60秒)。
getStaticPropsloadasyncData应用第2节中的所有数据处理规则:处理空值、正确格式化价格、显示空状态。
5. Write PDP
5. 编写产品详情页
Write the PDP at the path that fits routing conventions (e.g. , , , ).
app/p/[slug]/page.tsxpages/p/[slug].tsxpages/p/[slug].vuesrc/routes/p/[slug]/+page.svelteApply all data handling rules from section 2.
在符合路由约定的路径下编写产品详情页(如、、、)。
app/p/[slug]/page.tsxpages/p/[slug].tsxpages/p/[slug].vuesrc/routes/p/[slug]/+page.svelte应用第2节中的所有数据处理规则。
6. Write VariantSelector component
6. 编写VariantSelector组件
Write a component in the project's component directory. It must be client-interactive (use whatever interactivity primitive the framework provides — React state, Vue , Svelte store, etc.).
VariantSelectorrefBehaviour:
- Shows all variants; disables out-of-stock ones (do not hide them)
- Highlights selected variant
- Updates displayed price when a variant is selected (variant takes precedence)
pricing.price - Add to Cart button is disabled until a variant is selected (when selection is required)
- Single-variant / no-attribute products: skip selector, show Add to Cart directly
- Add to Cart is non-functional at this step — placeholder only, note this clearly in a comment
在项目的组件目录中编写组件。该组件必须支持客户端交互(使用框架提供的交互原语——React状态、Vue 、Svelte store等)。
VariantSelectorref行为要求:
- 显示所有变体;禁用(而非隐藏)缺货变体
- 高亮选中的变体
- 选择变体时更新显示的价格(变体的优先)
pricing.price - 当需要选择变体时,加入购物车按钮在选中变体前保持禁用状态
- 单变体/无属性产品:跳过选择器,直接显示加入购物车按钮
- 加入购物车按钮在本步骤中仅为占位符,无实际功能——需在注释中明确说明
7. Run and verify
7. 运行与验证
Start the dev server using the project's dev command. Direct the user to the product list and a PDP URL to confirm data loads correctly.
Common issues:
- Empty list: wrong channel slug or products not published in that channel — suggest running to inspect the store
configurator introspect - Codegen errors: API URL not set or unreachable
- Product not found on every slug: channel mismatch or product unpublished in that channel
使用项目的开发命令启动开发服务器。引导用户访问产品列表页和产品详情页URL,确认数据加载正常。
常见问题:
- 空列表: 渠道别名错误或产品未在该渠道发布——建议运行检查店面配置
configurator introspect - 代码生成错误: API地址未设置或无法访问
- 所有别名都找不到产品: 渠道不匹配或产品未在该渠道发布
Summary
总结
✓ GraphQL queries: [path]/products.graphql
✓ Types generated
✓ Navigation: [path] (wired into root layout)
✓ Product list: [route]
✓ Product detail: [route]
✓ VariantSelector: [path]
Note: "Add to Cart" is present but non-functional — checkout is not covered by this skill
This is the last step currently available in this skill.After printing the summary, stop.
✓ GraphQL查询: [路径]/products.graphql
✓ 类型已生成
✓ 导航组件: [路径](已集成到根布局)
✓ 产品列表页: [路由]
✓ 产品详情页: [路由]
✓ VariantSelector: [路径]
注意: "加入购物车"按钮已存在但无实际功能——本Skill不包含结账流程
这是本Skill当前提供的最后一个步骤。输出总结后停止。
Saleor correctness rules (always apply)
Saleor正确性规则(始终适用)
These rules apply across all steps and any future storefront work:
- Always pass — every product/pricing/availability query requires it; omitting it returns no data
channel - Parse safely — it is EditorJS JSON, not plain text or HTML
description - Never expose to the browser — use the two-client pattern; the auth client is server-side only
SALEOR_APP_TOKEN - null = available — anonymous users don't receive inventory counts; null means "don't block purchase"
quantityAvailable - is nullable at every level — guard
pricing,pricing,pricing.price, andpricing.priceRangebefore accessinggrossamount - Use for prices — never hardcode currency symbols or assume locale
Intl.NumberFormat - PDP media priority: →
media[0]→ placeholderthumbnail - Disable, don't hide, out-of-stock variants — hiding them confuses users about what the product offers
这些规则适用于所有步骤及未来的所有店面开发工作:
- 始终传递参数——每个产品/定价/库存查询都需要该参数;省略该参数将返回空数据
channel - 安全解析——它是EditorJS JSON格式,而非纯文本或HTML
description - 绝不能向浏览器暴露——使用双客户端模式;认证客户端仅在服务器端使用
SALEOR_APP_TOKEN - 为null = 有库存——匿名用户无法获取库存数量;null表示"不阻止购买"
quantityAvailable - 在每个层级都可为空——访问
pricing前,需对amount、pricing、pricing.price和pricing.priceRange进行判断gross - 使用格式化价格——绝不能硬编码货币符号或假设区域设置
Intl.NumberFormat - 产品详情页媒体优先级: →
media[0]→ 占位符thumbnail - 禁用而非隐藏缺货变体——隐藏变体会让用户对产品的可选选项产生困惑