frontic-implementation
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFrontic Implementation and Configuration
Frontic 实现与配置
Use this skill when autonomously creating or managing blocks, listings, and pages based on user requests. Prefer Frontic data sources over mocked data at all times.
当根据用户请求自主创建或管理块、列表与页面时,请使用本技能。始终优先使用Frontic数据源而非模拟数据。
Before You Start
开始之前
Use the tool to inspect the data structure of any block, listing, or page before using it. This prevents displaying non-existent content or illegible IDs to users.
fetch_api_call在使用任何块、列表或页面之前,使用工具检查其数据结构。这可以避免向用户展示不存在的内容或难以辨认的ID。
fetch_api_call1. Resource Decision Making
1. 资源决策
Before creating new blocks or listings, analyze the current Frontic configuration:
- Existing blocks that could be reused or extended
- Existing listings that match the user's requirements — verify parameter compatibility and data structure
- Available storages containing relevant data
在创建新的块或列表之前,分析当前的Frontic配置:
- 可复用或扩展的现有块
- 符合用户需求的现有列表——验证参数兼容性和数据结构
- 包含相关数据的可用存储
Reuse vs Extend vs Create
复用 vs 扩展 vs 创建
| Scenario | Action |
|---|---|
| Exact match (same user journey, same parameters) | Reuse |
| Close but not perfect fit | Extend (if appropriate) or create new |
| Different parameters or intent | Create new |
| 场景 | 操作 |
|---|---|
| 完全匹配(相同用户流程、相同参数) | 复用 |
| 接近但不完全匹配 | 扩展(若合适)或创建新资源 |
| 参数或意图不同 | 创建新资源 |
Rules (Non-Negotiable)
不可违反的规则
- Never force-fit: Do NOT use a block or listing for a use case it wasn't designed for, even as a workaround.
- Parameter compatibility is absolute: If a listing requires parameters (e.g. ) and the user's use case doesn't provide them (e.g. global search), create a new listing — never use dummy/placeholder values.
categoryId - User intent is primary: Match the user's stated intent and user journey exactly.
- Document your reasoning: Briefly explain why you reused, extended, or created a resource.
⚠️ Warning: If you find yourself using dummy/default parameters to make an existing resource work for a different use case, STOP. Create a new resource instead.
- 切勿强行适配:即使作为临时方案,也不要将块或列表用于其设计之外的场景。
- 参数兼容性是硬性要求:如果列表需要特定参数(如),但用户的使用场景无法提供(如全局搜索),请创建新列表——绝不要使用虚拟/占位符值。
categoryId - 用户意图优先:完全匹配用户声明的意图和用户流程。
- 记录决策理由:简要说明复用、扩展或创建资源的原因。
⚠️ 警告:如果你发现自己为了让现有资源适配不同场景而使用虚拟/默认参数,请立即停止。转而创建新资源。
2. Creating Blocks and Listings
2. 创建块与列表
Block Creation
块创建
- Analyze data structure: Identify the single record/entity the block represents
- Define comprehensive schema: Include all relevant e-commerce fields
- Plan for variations: Account for product types, categories, etc.
Common blocks: , , , , ,
ProductDetailCategoryDetailUserProfileOrderDetailReviewDetailBrandDetail- 分析数据结构:确定块所代表的单个记录/实体
- 定义完整的 schema:包含所有相关电商字段
- 规划变体:考虑商品类型、分类等情况
常见块:, , , , ,
ProductDetailCategoryDetailUserProfileOrderDetailReviewDetailBrandDetailListing Creation
列表创建
- Identify collection type: Products, categories, blog posts, etc.
- Identify embedded block: The listing must reference a block already created in the project
- Configure filtering: Price, category, brand, ratings, availability
- Plan sorting: Price, popularity, newest, ratings
- Enable search: Where relevant (description, title)
- Make parameters optional for global use: For listings that serve both global search and scoped contexts, configure parameters as optional with default values:
json
{
"name": "categoryId",
"dataType": "string",
"required": false,
"defaultValue": "root-id"
}This allows for global search and for scoped queries.
listing("GlobalSearch", {})listing("GlobalSearch", { categoryId: "xyz" })Common listings: , , , ,
ProductListingCategoryListingOrderListingReviewListingSearchResults- 确定集合类型:商品、分类、博客文章等
- 指定嵌入块:列表必须引用项目中已创建的块
- 配置过滤条件:价格、分类、品牌、评分、库存状态
- 规划排序规则:价格、人气、新品、评分
- 启用搜索功能:适用于相关场景(如描述、标题搜索)
- 设置全局可用的可选参数:对于同时服务于全局搜索和限定范围查询的列表,将参数配置为可选并设置默认值:
json
{
"name": "categoryId",
"dataType": "string",
"required": false,
"defaultValue": "root-id"
}这样就可以通过执行全局搜索,通过执行限定范围查询。
listing("GlobalSearch", {})listing("GlobalSearch", { categoryId: "xyz" })常见列表:, , , ,
ProductListingCategoryListingOrderListingReviewListingSearchResultsRegenerate the Client
重新生成客户端
After creating or updating a block or listing, always regenerate the Frontic Client:
bash
npx @frontic/cli@latest generate创建或更新块或列表后,请务必重新生成Frontic客户端:
bash
npx @frontic/cli@latest generate3. Frontic Client Usage
3. Frontic 客户端使用
Import the client (resolve path relative to the application root):
.frontic/typescript
import client from '../../.frontic/generated-client'导入客户端(相对于应用根目录解析路径):
.frontic/typescript
import client from '../../.frontic/generated-client'Blocks (single items by key)
块(通过键获取单个条目)
Single items are always identified by a key. Don't make up placeholder keys like 'root-category', always use actual keys from the storage associated with the block.
typescript
const data = await client.block(
'CategoryDetail',
'a3f4b5c6-d7e8-49ab-9c2d-3e4f5e6d7c8b'
)
const data = await client.block(
'ProductDetail',
'123e4567-e89b-12d3-a456-426614174000'
)单个条目始终通过键来标识。不要编造类似'root-category'的占位符键,请始终使用与块关联的存储中的实际键。
typescript
const data = await client.block(
'CategoryDetail',
'a3f4b5c6-d7e8-49ab-9c2d-3e4f5e6d7c8b'
)
const data = await client.block(
'ProductDetail',
'123e4567-e89b-12d3-a456-426614174000'
)Listings (collections with filtering, sorting, pagination)
列表(带过滤、排序、分页的集合)
Refer to and for filters and sort options.
.frontic/query-types.ts.frontic/fetch-api.d.tstypescript
const products = await client.listing('ProductList', {
categoryId: 'e72b0f5e-1c3d-4a7e-913a-2e4c95cd8f31'
})
const results = await client.listing(
'ProductList',
{ categoryId: '...' },
{
query: {
filter: [
{
type: 'and',
filter: [
{ type: 'equals', field: 'attributes.size', value: '10' },
{ type: 'range', field: 'price.amount', from: 10 }
]
},
{ type: 'range', field: 'price.amount', from: 2000, to: 10000 }
],
sort: { field: 'price.amount', order: 'asc' },
search: 'Red',
page: 1,
limit: 20
}
}
)有关过滤和排序选项,请参考和。
.frontic/query-types.ts.frontic/fetch-api.d.tstypescript
const products = await client.listing('ProductList', {
categoryId: 'e72b0f5e-1c3d-4a7e-913a-2e4c95cd8f31'
})
const results = await client.listing(
'ProductList',
{ categoryId: '...' },
{
query: {
filter: [
{
type: 'and',
filter: [
{ type: 'equals', field: 'attributes.size', value: '10' },
{ type: 'range', field: 'price.amount', from: 10 }
]
},
{ type: 'range', field: 'price.amount', from: 2000, to: 10000 }
],
sort: { field: 'price.amount', order: 'asc' },
search: 'Red',
page: 1,
limit: 20
}
}
)Pages (retrieve by route)
页面(通过路由获取)
typescript
// The route path is the full path including the domain without the protocol
const pageData = await client.page('demo-shop.com/uk/women/shoes')typescript
// 路由路径是包含域名的完整路径,不包含协议
const pageData = await client.page('demo-shop.com/uk/women/shoes')Type Reference
类型参考
Check and for blocks, listings, pages, parameters, and return types.
.frontic/generated-types.d.ts.frontic/query-types.ts有关块、列表、页面、参数和返回类型,请查看和。
.frontic/generated-types.d.ts.frontic/query-types.ts4. Page Integration and Dynamic Updates
4. 页面集成与动态更新
Use Pages for Dynamic Routing
使用页面实现动态路由
Always use pages for dynamic routing unless the user requests otherwise. Pages are containers for blocks identified by slug. Page slugs include domain, locale (if configured), and path. Blocks can have fields for route information.
pageRouteFor root-level dynamic routing, create a catch-all page that calls with the assembled slug. Handle the root case separately (e.g. with a hardcoded block).
client.page()/除非用户另有要求,否则请始终使用页面实现动态路由。页面是通过slug标识的块容器。页面slug包含域名、区域设置(若已配置)和路径。块可以包含字段以存储路由信息。
pageRoute对于根级动态路由,请创建一个捕获所有路径的页面,使用组装好的slug调用。单独处理根路径的情况(例如使用硬编码块)。
client.page()/Page Structure Hierarchy
页面结构层级
Page (e.g. CategoryDetail)
├── Block (e.g. CategoryDetailShopware)
└── Nested Listing (e.g. ProductListShopware)Page (e.g. CategoryDetail)
├── Block (e.g. CategoryDetailShopware)
└── Nested Listing (e.g. ProductListShopware)Implementation Pattern
实现模式
- Initial load: Use or
client.page()— provides full page context and embedded content.client.block() - Filtering/sorting/pagination: Use —
client.listing()andclient.page()do not accept query parameters.client.block()
typescript
// 1. Initial page load
const pageData = await client.page('domain.com/category-name')
// 2. Dynamic updates — extract params, call embedded listing directly
const categoryId = pageData.data.key
const filteredData = await client.listing('ProductListShopware', { categoryId }, {
query: { filter: [...], sort: {...}, page: 2 }
})Summary: Always start with for initial load. For dynamic updates, identify the nested listing, extract required parameters from page data, and call with query parameters. Update only the listing portion of component state.
client.page()client.listing()- 初始加载:使用或
client.page()——提供完整的页面上下文和嵌入内容。client.block() - 过滤/排序/分页:使用——
client.listing()和client.page()不接受查询参数。client.block()
typescript
// 1. 初始页面加载
const pageData = await client.page('domain.com/category-name')
// 2. 动态更新 —— 提取参数,直接调用嵌入的列表
const categoryId = pageData.data.key
const filteredData = await client.listing('ProductListShopware', { categoryId }, {
query: { filter: [...], sort: {...}, page: 2 }
})总结:初始加载时始终使用。对于动态更新,请识别嵌套列表,从页面数据中提取所需参数,并携带查询参数调用。仅更新组件状态中的列表部分。
client.page()client.listing()5. Field Configuration
5. 字段配置
Filter Field Naming with Paths
带路径的过滤字段命名
When a filter field is configured with a parameter, you must use the full dotted path in queries:
pathjson
// Configuration:
{ "name": "price", "source": "blockField", "path": "amount" }
// In queries — use "price.amount", NOT "price":
{ "type": "range", "field": "price.amount", "from": 2000, "to": 10000 }Rule: If a filter field has , always reference it as in queries.
path: "X"fieldName.X当过滤字段配置了参数时,必须在查询中使用完整的点分路径:
pathjson
// 配置:
{ "name": "price", "source": "blockField", "path": "amount" }
// 查询时 —— 使用"price.amount",而非"price":
{ "type": "range", "field": "price.amount", "from": 2000, "to": 10000 }规则:如果过滤字段包含,则在查询中必须以的形式引用它。
path: "X"fieldName.XSort Field Configuration
排序字段配置
Frontic does not support duplicate sort field names with different directions. Add each sort field once — the API automatically supports both and . Specify direction at query time:
ascdesctypescript
// One sort field configured, direction chosen per query:
sort: { field: 'price', order: 'asc' } // or 'desc'Frontic 不支持同名排序字段设置不同方向。每个排序字段只需配置一次——API会自动支持和两种方向。在查询时指定排序方向:
ascdesctypescript
// 配置一个排序字段,查询时选择方向:
sort: { field: 'price', order: 'asc' } // 或 'desc'Field Source Selection
字段源选择
Always prefer over when configuring search, filter, and sort fields:
blockFieldstorageField- Block fields are already exposed and configured
- Storage fields may not be accessible or properly typed
- Block fields maintain consistency with the nested block structure
javascript
// Prefer:
{ type: "filter", name: "price", source: "blockField" }
// Avoid:
{ type: "filter", name: "price", source: "storageField" }配置搜索、过滤和排序字段时,请始终优先选择而非:
blockFieldstorageField- 块字段已暴露并配置完成
- 存储字段可能无法访问或类型定义不当
- 块字段与嵌套块结构保持一致
javascript
// 推荐:
{ type: "filter", name: "price", source: "blockField" }
// 避免:
{ type: "filter", name: "price", source: "storageField" }Validating Field Paths
验证字段路径
Before using a filter or sort field, verify the exact name:
- Use to see configured fields
list_resources - Use to test the listing and inspect the response structure
fetch_api_call
javascript
// Test listing response reveals field structure:
// items: [{ price: { amount: 49595 } }]
// → filter field is "price.amount"在使用过滤或排序字段之前,请验证其确切名称:
- 使用查看已配置的字段
list_resources - 使用测试列表并检查响应结构
fetch_api_call
javascript
// 测试列表响应可揭示字段结构:
// items: [{ price: { amount: 49595 } }]
// → 过滤字段为"price.amount"6. Price Format
6. 价格格式
Frontic prices use an integer format with precision:
json
{ "amount": 49595, "currency": "EUR", "precision": 2, "ref": 0 }Display calculation:
javascript
const displayPrice = price.amount / Math.pow(10, price.precision)
// 49595 / 100 = 495.95Frontic的价格采用带精度的整数格式:
json
{ "amount": 49595, "currency": "EUR", "precision": 2, "ref": 0 }显示计算方式:
javascript
const displayPrice = price.amount / Math.pow(10, price.precision)
// 49595 / 100 = 495.957. Troubleshooting
7. 故障排除
Browser and Build Cache
浏览器与构建缓存
After regenerating the client, caching can cause stale code to persist.
- Clear the framework build cache (e.g. ,
.next/,.nuxt/)dist/ - Hard refresh browser: Cmd+Shift+R (Mac) / Ctrl+Shift+F5 (Windows/Linux)
- Disable cache in DevTools: Network tab → "Disable cache"
重新生成客户端后,缓存可能导致陈旧代码残留。
- 清除框架构建缓存(例如,
.next/,.nuxt/)dist/ - 强制刷新浏览器:Cmd+Shift+R(Mac)/ Ctrl+Shift+F5(Windows/Linux)
- 在开发者工具中禁用缓存:网络标签 → "禁用缓存"