vtex-io-react-apps
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFrontend React Components & Hooks
前端React组件与Hooks
When this skill applies
本技能的适用场景
Use this skill when building VTEX IO frontend apps using the builder — creating React components that integrate with Store Framework as theme blocks, configuring , setting up for Site Editor, and applying styling patterns.
reactinterfaces.jsoncontentSchemas.json- Creating custom storefront components (product displays, forms, banners)
- Building admin panel interfaces with VTEX Styleguide
- Registering components as Store Framework blocks
- Exposing component props in Site Editor via
contentSchemas.json - Applying for safe storefront styling
css-handles
Do not use this skill for:
- Backend service implementation (use instead)
vtex-io-service-apps - GraphQL schema and resolver development (use instead)
vtex-io-graphql-api - Manifest and builder configuration (use instead)
vtex-io-app-structure
当你使用构建器开发VTEX IO前端应用时,请使用本技能——包括创建与Store Framework集成作为主题区块的React组件、配置、为Site Editor设置,以及应用样式规范。
reactinterfaces.jsoncontentSchemas.json- 创建自定义店铺前端组件(商品展示、表单、横幅)
- 使用VTEX Styleguide构建管理面板界面
- 将组件注册为Store Framework区块
- 通过在Site Editor中暴露组件属性
contentSchemas.json - 应用实现安全的店铺前端样式
css-handles
请勿在以下场景使用本技能:
- 后端服务实现(请改用)
vtex-io-service-apps - GraphQL schema与解析器开发(请改用)
vtex-io-graphql-api - 清单与构建器配置(请改用)
vtex-io-app-structure
Decision rules
决策规则
- Every visible storefront element is a block. Blocks are declared in theme JSON and map to React components via interfaces.
- (in
interfaces.json) maps block names to React component files:/storeis the file name in"component"(without extension),/reactlists child blocks,"allowed"controls how children work ("composition"or"children")."blocks" - Each exported component MUST have a root-level file in that re-exports it. The builder resolves
/reactto"component": "ProductReviews".react/ProductReviews.tsx - For storefront components, use for styling (not inline styles, not global CSS).
vtex.css-handles - For admin components, use — the official VTEX Admin component library. No third-party UI libraries.
vtex.styleguide - Use in
contentSchemas.jsonto make component props editable in Site Editor (JSON Schema format)./store - Use and the
react-intlbuilder for i18n — never hardcode user-facing strings.messages - Fetch data via GraphQL queries (from
useQuery), never via direct API calls from the browser.react-apollo
Architecture:
text
Store Theme (JSON blocks)
└── declares "product-reviews" block with props
│
▼
interfaces.json → maps "product-reviews" to "ProductReviews" component
│
▼
react/ProductReviews.tsx → React component renders
│
├── useCssHandles() → CSS classes for styling
├── useQuery() → GraphQL data fetching
└── useProduct() / useOrderForm() → Store Framework context hooks- 所有可见的店铺前端元素都是一个区块。区块在主题JSON中声明,并通过接口映射到React组件。
- 目录下的
/store将区块名称映射到React组件文件:interfaces.json对应"component"目录下的文件名(不含扩展名),/react列出允许的子区块,"allowed"控制子元素的工作方式("composition"或"children")。"blocks" - 每个导出的组件必须在目录下有一个根级文件重新导出它。构建器会将
/react解析为"component": "ProductReviews"。react/ProductReviews.tsx - 对于店铺前端组件,请使用进行样式处理(不要使用内联样式或全局CSS)。
vtex.css-handles - 对于管理端组件,请使用——官方VTEX管理端组件库。禁止使用第三方UI库。
vtex.styleguide - 使用目录下的
/store使组件属性可在Site Editor中编辑(采用JSON Schema格式)。contentSchemas.json - 使用和
react-intl构建器实现国际化——切勿硬编码面向用户的字符串。messages - 通过GraphQL查询(来自的
react-apollo)获取数据,切勿从浏览器直接调用API。useQuery
架构:
text
Store Theme (JSON blocks)
└── declares "product-reviews" block with props
│
▼
interfaces.json → maps "product-reviews" to "ProductReviews" component
│
▼
react/ProductReviews.tsx → React component renders
│
├── useCssHandles() → CSS classes for styling
├── useQuery() → GraphQL data fetching
└── useProduct() / useOrderForm() → Store Framework context hooksHard constraints
硬性约束
Constraint: Declare Interfaces for All Storefront Blocks
约束:为所有店铺前端区块声明接口
Every React component that should be usable as a Store Framework block MUST have a corresponding entry in . Without the interface declaration, the block cannot be referenced in theme JSON files.
store/interfaces.jsonWhy this matters
The store builder resolves block names to React components through . If a component has no interface, it is invisible to Store Framework and will not render on the storefront.
interfaces.jsonDetection
If a React component in is intended for storefront use but has no matching entry in , warn the developer. The component will compile but never render.
/reactstore/interfaces.jsonCorrect
json
{
"product-reviews": {
"component": "ProductReviews",
"composition": "children",
"allowed": ["product-review-item"]
},
"product-review-item": {
"component": "ReviewItem"
}
}tsx
// react/ProductReviews.tsx
import ProductReviews from './components/ProductReviews'
export default ProductReviewsWrong
tsx
// react/ProductReviews.tsx exists but NO store/interfaces.json entry
// The component compiles fine but cannot be used in any theme.
// Adding <product-reviews /> in a theme JSON will produce:
// "Block 'product-reviews' not found"
import ProductReviews from './components/ProductReviews'
export default ProductReviews每个要作为Store Framework区块使用的React组件,必须在中有对应的条目。没有接口声明的话,该区块无法在主题JSON文件中被引用。
store/interfaces.json重要性
店铺构建器通过将区块名称解析为React组件。如果组件没有对应的接口,Store Framework将无法识别它,也不会在店铺前端渲染。
interfaces.json检测方式
如果目录下的React组件用于店铺前端,但在中没有匹配的条目,请提醒开发者。该组件会正常编译,但永远不会渲染。
/reactstore/interfaces.json正确示例
json
{
"product-reviews": {
"component": "ProductReviews",
"composition": "children",
"allowed": ["product-review-item"]
},
"product-review-item": {
"component": "ReviewItem"
}
}tsx
// react/ProductReviews.tsx
import ProductReviews from './components/ProductReviews'
export default ProductReviews错误示例
tsx
// react/ProductReviews.tsx存在,但没有对应的store/interfaces.json条目
// 组件会正常编译,但无法在任何主题中使用。
// 在主题JSON中添加<product-reviews />会报错:
// "Block 'product-reviews' not found"
import ProductReviews from './components/ProductReviews'
export default ProductReviewsConstraint: Use VTEX Styleguide for Admin UIs
约束:管理端UI必须使用VTEX Styleguide
Admin panel components (apps using the builder) MUST use VTEX Styleguide () for UI elements. You MUST NOT use third-party UI libraries like Material UI, Chakra UI, or Ant Design in admin apps.
adminvtex.styleguideWhy this matters
VTEX Admin has a consistent design language enforced by Styleguide. Third-party UI libraries produce inconsistent visuals, may conflict with the Admin's global CSS, and add unnecessary bundle size. Apps submitted to the VTEX App Store with non-Styleguide admin UIs will fail review.
Detection
If you see imports from , , , , or in an admin app, warn the developer to use instead.
@material-ui@chakra-ui/react@chakra-uiantd@ant-designvtex.styleguideCorrect
tsx
// react/admin/ReviewModeration.tsx
import React, { useState } from 'react'
import {
Layout,
PageHeader,
Table,
Button,
Tag,
Modal,
Input,
} from 'vtex.styleguide'
interface Review {
id: string
author: string
rating: number
text: string
status: 'pending' | 'approved' | 'rejected'
}
function ReviewModeration() {
const [reviews, setReviews] = useState<Review[]>([])
const [modalOpen, setModalOpen] = useState(false)
const tableSchema = {
properties: {
author: { title: 'Author', width: 200 },
rating: { title: 'Rating', width: 100 },
text: { title: 'Review Text' },
status: {
title: 'Status',
width: 150,
cellRenderer: ({ cellData }: { cellData: string }) => (
<Tag type={cellData === 'approved' ? 'success' : 'error'}>
{cellData}
</Tag>
),
},
},
}
return (
<Layout fullWidth pageHeader={<PageHeader title="Review Moderation" />}>
<Table
items={reviews}
schema={tableSchema}
density="medium"
/>
</Layout>
)
}
export default ReviewModerationWrong
tsx
// react/admin/ReviewModeration.tsx
import React from 'react'
import { DataGrid } from '@material-ui/data-grid'
import { Button } from '@material-ui/core'
// Material UI components will look inconsistent in the VTEX Admin,
// conflict with global styles, and inflate bundle size.
// This app will fail VTEX App Store review.
function ReviewModeration() {
return (
<div>
<DataGrid rows={[]} columns={[]} />
<Button variant="contained" color="primary">Approve</Button>
</div>
)
}管理面板组件(使用构建器的应用)必须使用VTEX Styleguide()作为UI元素。禁止在管理端应用中使用Material UI、Chakra UI或Ant Design等第三方UI库。
adminvtex.styleguide重要性
VTEX管理端有由Styleguide强制执行的统一设计语言。第三方UI库会导致视觉不一致,可能与管理端的全局CSS冲突,还会增加不必要的包体积。使用非Styleguide管理端UI的应用在提交到VTEX应用商店时会审核不通过。
检测方式
如果在管理端应用中看到、、、或的导入,请提醒开发者改用。
@material-ui@chakra-ui/react@chakra-uiantd@ant-designvtex.styleguide正确示例
tsx
// react/admin/ReviewModeration.tsx
import React, { useState } from 'react'
import {
Layout,
PageHeader,
Table,
Button,
Tag,
Modal,
Input,
} from 'vtex.styleguide'
interface Review {
id: string
author: string
rating: number
text: string
status: 'pending' | 'approved' | 'rejected'
}
function ReviewModeration() {
const [reviews, setReviews] = useState<Review[]>([])
const [modalOpen, setModalOpen] = useState(false)
const tableSchema = {
properties: {
author: { title: 'Author', width: 200 },
rating: { title: 'Rating', width: 100 },
text: { title: 'Review Text' },
status: {
title: 'Status',
width: 150,
cellRenderer: ({ cellData }: { cellData: string }) => (
<Tag type={cellData === 'approved' ? 'success' : 'error'}>
{cellData}
</Tag>
),
},
},
}
return (
<Layout fullWidth pageHeader={<PageHeader title="Review Moderation" />}>
<Table
items={reviews}
schema={tableSchema}
density="medium"
/>
</Layout>
)
}
export default ReviewModeration错误示例
tsx
// react/admin/ReviewModeration.tsx
import React from 'react'
import { DataGrid } from '@material-ui/data-grid'
import { Button } from '@material-ui/core'
// Material UI组件在VTEX管理端中视觉效果不一致,
// 会与全局样式冲突,还会增大包体积。
// 该应用在VTEX应用商店审核中会不通过。
function ReviewModeration() {
return (
<div>
<DataGrid rows={[]} columns={[]} />
<Button variant="contained" color="primary">Approve</Button>
</div>
)
}Constraint: Export Components from react/ Root Level
约束:从react/根目录导出组件
Every Store Framework block component MUST have a root-level export file in the directory that matches the value in . The actual implementation can live in subdirectories, but the root file must exist.
/reactcomponentinterfaces.jsonWhy this matters
The react builder resolves components by looking for files at the root of . If declares , the builder looks for . Without this root export file, the component will not be found and the block will fail to render.
/reactinterfaces.json"component": "ProductReviews"react/ProductReviews.tsxDetection
If references a component name that does not have a matching file at the root of , STOP and create the export file.
interfaces.json/reactCorrect
tsx
// react/ProductReviews.tsx — root-level export file
import ProductReviews from './components/ProductReviews/index'
export default ProductReviewstsx
// react/components/ProductReviews/index.tsx — actual implementation
import React from 'react'
import { useCssHandles } from 'vtex.css-handles'
const CSS_HANDLES = ['container', 'title', 'list'] as const
interface Props {
title: string
maxReviews: number
}
function ProductReviews({ title, maxReviews }: Props) {
const handles = useCssHandles(CSS_HANDLES)
return (
<div className={handles.container}>
<h2 className={handles.title}>{title}</h2>
{/* ... */}
</div>
)
}
export default ProductReviewsWrong
text
react/components/ProductReviews/index.tsx exists but
react/ProductReviews.tsx does NOT exist.
The builder cannot find the component.
Error: "Could not find component ProductReviews"每个Store Framework区块组件必须在目录下有一个根级导出文件,且文件名与中的值匹配。实际实现代码可以放在子目录中,但根级导出文件必须存在。
/reactinterfaces.jsoncomponent重要性
React构建器通过查找根目录下的文件来解析组件。如果声明了,构建器会查找。没有这个根级导出文件的话,组件会无法被找到,区块渲染会失败。
/reactinterfaces.json"component": "ProductReviews"react/ProductReviews.tsx检测方式
如果引用的组件名称在根目录下没有匹配的文件,请立即创建导出文件。
interfaces.json/react正确示例
tsx
// react/ProductReviews.tsx — 根级导出文件
import ProductReviews from './components/ProductReviews/index'
export default ProductReviewstsx
// react/components/ProductReviews/index.tsx — 实际实现代码
import React from 'react'
import { useCssHandles } from 'vtex.css-handles'
const CSS_HANDLES = ['container', 'title', 'list'] as const
interface Props {
title: string
maxReviews: number
}
function ProductReviews({ title, maxReviews }: Props) {
const handles = useCssHandles(CSS_HANDLES)
return (
<div className={handles.container}>
<h2 className={handles.title}>{title}</h2>
{/* ... */}
</div>
)
}
export default ProductReviews错误示例
text
react/components/ProductReviews/index.tsx存在,但
react/ProductReviews.tsx不存在。
构建器无法找到该组件。
错误提示:"Could not find component ProductReviews"Preferred pattern
推荐模式
Create the React component inside a subdirectory:
tsx
// react/components/ProductReviews/index.tsx
import React, { useMemo } from 'react'
import { useQuery } from 'react-apollo'
import { useProduct } from 'vtex.product-context'
import { useCssHandles } from 'vtex.css-handles'
import GET_REVIEWS from '../../graphql/getReviews.graphql'
import ReviewItem from './ReviewItem'
const CSS_HANDLES = [
'reviewsContainer',
'reviewsTitle',
'reviewsList',
'averageRating',
'emptyState',
] as const
interface Props {
title?: string
showAverage?: boolean
maxReviews?: number
}
function ProductReviews({
title = 'Customer Reviews',
showAverage = true,
maxReviews = 10,
}: Props) {
const handles = useCssHandles(CSS_HANDLES)
const productContext = useProduct()
const productId = productContext?.product?.productId
const { data, loading, error } = useQuery(GET_REVIEWS, {
variables: { productId, limit: maxReviews },
skip: !productId,
})
const averageRating = useMemo(() => {
if (!data?.reviews?.length) return 0
const sum = data.reviews.reduce(
(acc: number, review: { rating: number }) => acc + review.rating,
0
)
return (sum / data.reviews.length).toFixed(1)
}, [data])
if (loading) return <div className={handles.reviewsContainer}>Loading...</div>
if (error) return null
return (
<div className={handles.reviewsContainer}>
<h2 className={handles.reviewsTitle}>{title}</h2>
{showAverage && data?.reviews?.length > 0 && (
<div className={handles.averageRating}>
Average: {averageRating} / 5
</div>
)}
{data?.reviews?.length === 0 ? (
<p className={handles.emptyState}>No reviews yet.</p>
) : (
<ul className={handles.reviewsList}>
{data.reviews.map((review: { id: string; author: string; rating: number; text: string }) => (
<ReviewItem key={review.id} review={review} />
))}
</ul>
)}
</div>
)
}
export default ProductReviewsRoot export file:
tsx
// react/ProductReviews.tsx
import ProductReviews from './components/ProductReviews'
export default ProductReviewsBlock interface:
json
{
"product-reviews": {
"component": "ProductReviews",
"composition": "children",
"allowed": ["product-review-form"],
"render": "client"
}
}Site Editor schema:
json
{
"definitions": {
"ProductReviews": {
"type": "object",
"properties": {
"title": {
"type": "string",
"title": "Section Title",
"description": "Title displayed above the reviews list",
"default": "Customer Reviews"
},
"showAverage": {
"type": "boolean",
"title": "Show average rating",
"default": true
},
"maxReviews": {
"type": "number",
"title": "Maximum reviews",
"default": 10,
"enum": [5, 10, 20, 50]
}
}
}
}
}Using the component in a Store Framework theme:
json
{
"store.product": {
"children": [
"product-images",
"product-name",
"product-price",
"buy-button",
"product-reviews"
]
},
"product-reviews": {
"props": {
"title": "What Our Customers Say",
"showAverage": true,
"maxReviews": 20
}
}
}在子目录中创建React组件:
tsx
// react/components/ProductReviews/index.tsx
import React, { useMemo } from 'react'
import { useQuery } from 'react-apollo'
import { useProduct } from 'vtex.product-context'
import { useCssHandles } from 'vtex.css-handles'
import GET_REVIEWS from '../../graphql/getReviews.graphql'
import ReviewItem from './ReviewItem'
const CSS_HANDLES = [
'reviewsContainer',
'reviewsTitle',
'reviewsList',
'averageRating',
'emptyState',
] as const
interface Props {
title?: string
showAverage?: boolean
maxReviews?: number
}
function ProductReviews({
title = 'Customer Reviews',
showAverage = true,
maxReviews = 10,
}: Props) {
const handles = useCssHandles(CSS_HANDLES)
const productContext = useProduct()
const productId = productContext?.product?.productId
const { data, loading, error } = useQuery(GET_REVIEWS, {
variables: { productId, limit: maxReviews },
skip: !productId,
})
const averageRating = useMemo(() => {
if (!data?.reviews?.length) return 0
const sum = data.reviews.reduce(
(acc: number, review: { rating: number }) => acc + review.rating,
0
)
return (sum / data.reviews.length).toFixed(1)
}, [data])
if (loading) return <div className={handles.reviewsContainer}>Loading...</div>
if (error) return null
return (
<div className={handles.reviewsContainer}>
<h2 className={handles.reviewsTitle}>{title}</h2>
{showAverage && data?.reviews?.length > 0 && (
<div className={handles.averageRating}>
Average: {averageRating} / 5
</div>
)}
{data?.reviews?.length === 0 ? (
<p className={handles.emptyState}>No reviews yet.</p>
) : (
<ul className={handles.reviewsList}>
{data.reviews.map((review: { id: string; author: string; rating: number; text: string }) => (
<ReviewItem key={review.id} review={review} />
))}
</ul>
)}
</div>
)
}
export default ProductReviews根级导出文件:
tsx
// react/ProductReviews.tsx
import ProductReviews from './components/ProductReviews'
export default ProductReviews区块接口:
json
{
"product-reviews": {
"component": "ProductReviews",
"composition": "children",
"allowed": ["product-review-form"],
"render": "client"
}
}Site Editor schema:
json
{
"definitions": {
"ProductReviews": {
"type": "object",
"properties": {
"title": {
"type": "string",
"title": "Section Title",
"description": "Title displayed above the reviews list",
"default": "Customer Reviews"
},
"showAverage": {
"type": "boolean",
"title": "Show average rating",
"default": true
},
"maxReviews": {
"type": "number",
"title": "Maximum reviews",
"default": 10,
"enum": [5, 10, 20, 50]
}
}
}
}
}在Store Framework主题中使用组件:
json
{
"store.product": {
"children": [
"product-images",
"product-name",
"product-price",
"buy-button",
"product-reviews"
]
},
"product-reviews": {
"props": {
"title": "What Our Customers Say",
"showAverage": true,
"maxReviews": 20
}
}
}Common failure modes
常见失败模式
- Importing third-party UI libraries for admin apps: Using ,
@material-ui/core, or@chakra-ui/reactconflicts with VTEX Admin's global CSS, produces inconsistent visuals, and will fail App Store review. Useantdinstead.vtex.styleguide - Directly calling APIs from React components: Using or
fetch()exposes authentication tokens to the client and bypasses CORS restrictions. Use GraphQL queries that resolve server-side viaaxiosfromuseQuery.react-apollo - Hardcoded strings without i18n: Components with hardcoded strings only work in one language. Use the builder and
messagesfor internationalization.react-intl - Missing root-level export file: If references
interfaces.jsonbut"component": "ProductReviews"doesn't exist, the block silently fails to render.react/ProductReviews.tsx
- 管理端应用导入第三方UI库:使用、
@material-ui/core或@chakra-ui/react会与VTEX管理端的全局CSS冲突,导致视觉效果不一致,且在应用商店审核中不通过。请改用antd。vtex.styleguide - 从React组件直接调用API:使用或
fetch()会将认证令牌暴露给客户端,还会绕过CORS限制。请使用通过服务器端解析的GraphQL查询(来自axios的react-apollo)。useQuery - 未做国际化的硬编码字符串:含有硬编码字符串的组件只能在一种语言环境下使用。请使用构建器和
messages实现国际化。react-intl - 缺少根级导出文件:如果引用了
interfaces.json但"component": "ProductReviews"不存在,区块会静默渲染失败。react/ProductReviews.tsx
Review checklist
审核清单
- Does every storefront block have a matching entry in ?
store/interfaces.json - Does every component have a root-level export file in
interfaces.json?/react - Are admin apps using (no third-party UI libraries)?
vtex.styleguide - Are storefront components using for styling?
css-handles - Is data fetched via GraphQL (), not direct API calls?
useQuery - Are user-facing strings using and the
react-intlbuilder?messages - Is defined for Site Editor-editable props?
contentSchemas.json
- 每个店铺前端区块在中都有对应的条目吗?
store/interfaces.json - 每个中的组件在
interfaces.json目录下都有根级导出文件吗?/react - 管理端应用是否使用了(未使用第三方UI库)?
vtex.styleguide - 店铺前端组件是否使用了进行样式处理?
css-handles - 是否通过GraphQL()获取数据,而非直接调用API?
useQuery - 面向用户的字符串是否使用了和
react-intl构建器?messages - 是否为可在Site Editor中编辑的属性定义了?
contentSchemas.json
Reference
参考资料
- Developing Custom Storefront Components — Guide for building Store Framework components
- Interfaces — How interfaces map blocks to React components
- React Builder — React builder configuration and directory structure
- Making a Custom Component Available in Site Editor — contentSchemas.json and Site Editor integration
- Store Framework — Overview of the block-based storefront system
- Using Components — How to use native and custom components in themes
- VTEX Styleguide — Official component library for VTEX Admin UIs
- Developing Custom Storefront Components — 构建Store Framework组件的指南
- Interfaces — 接口如何将区块映射到React组件
- React Builder — React构建器配置与目录结构
- Making a Custom Component Available in Site Editor — contentSchemas.json与Site Editor集成
- Store Framework — 基于区块的店铺前端系统概述
- Using Components — 如何在主题中使用原生和自定义组件
- VTEX Styleguide — VTEX管理端官方组件库