vtex-io-react-apps について
React builderでVTEX IOのフロントエンドアプリを開発するためのスキルです。Store Frameworkブロック、Site Editorスキーマ、スタイリング、データ取得に加え、コンポーネント構成の確認方法を扱います。
主な機能
- 01interfaces.jsonによるブロック対応付け
- 02contentSchemas.jsonでのプロパティ公開
- 03css-handlesによるストアフロントのスタイリング
- 04管理画面でのVTEX Styleguide利用
- 05GraphQLデータ取得と国際化の指針
- 06コンポーネントのエクスポート確認
活用シーン
- 01カスタムStore Frameworkストアフロントブロックの作成
- 02Site Editorで編集可能なコンポーネントプロパティの設定
- 03VTEX Admin管理画面の開発
- 04ブロック登録とコンポーネントエクスポートの確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vtex/ skills --skill vtex-io-react-appsインストール後、Claude Code で/vtex-io-react-appsを入力して読み込まれたか確認します。
詳細
フロントエンド React コンポーネントとフック
このスキルを使う場面
react ビルダーを使って VTEX IO のフロントエンドアプリを構築する場合に、このスキルを使います。Store Framework とテーマブロックとして連携する React コンポーネントの作成、interfaces.json の設定、Site Editor 用の contentSchemas.json の設定、スタイリングパターンの適用が対象です。
- カスタムストアフロントコンポーネント(商品表示、フォーム、バナー)の作成
- VTEX Styleguide を使った管理パネルのインターフェース構築
- コンポーネントの Store Framework ブロックとしての登録
contentSchemas.jsonを通じた Site Editor へのコンポーネントプロパティの公開- 安全なストアフロントのスタイリングのための
css-handlesの適用
次の用途には、このスキルを使わないでください。
- バックエンドサービスの実装(代わりに
vtex-io-service-appsを使ってください) - GraphQL スキーマとリゾルバーの開発(代わりに
vtex-io-graphql-apiを使ってください) - Manifest とビルダーの設定(代わりに
vtex-io-app-structureを使ってください)
判断ルール
- ストアフロントに表示される要素はすべて ブロック です。ブロックはテーマ JSON で宣言され、インターフェース を通じて React コンポーネントに対応付けられます。
/storeのinterfaces.jsonは、ブロック名を React コンポーネントファイルに対応付けます。"component"は/react内のファイル名(拡張子なし)、"allowed"は子ブロックの一覧、"composition"は子要素の動作("children"または"blocks")を制御します。- エクスポートする各コンポーネントには、再エクスポートするルートレベルのファイルが
/reactに必須です。ビルダーは"component": "ProductReviews"をreact/ProductReviews.tsxとして解決します。 - ストアフロント コンポーネントのスタイリングには
vtex.css-handlesを使ってください(インラインスタイルやグローバル CSS は使わないでください)。 - 管理画面 コンポーネントには、公式 VTEX Admin コンポーネントライブラリである
vtex.styleguideを使ってください。サードパーティの UI ライブラリは使わないでください。 - コンポーネントのプロパティを Site Editor で編集できるようにするには、
/storeのcontentSchemas.jsonを使います(JSON Schema 形式)。マーチャントによる編集内容は、宣言元アプリの MAJOR バージョン を含むキーの下にvtex.pages-graphqlによって保存されます([email protected]:template)。宣言元アプリのメジャーバージョンを上げると、[email protected]のupdateThemeIdsミューテーションで新しいメジャーバージョンに移行するまで、リゾルバーから編集内容が見えなくなります。詳しくはvtex-io-storefront-theme-versioningを参照してください。 - i18n には
react-intlとmessagesビルダーを使い、ユーザーに表示する文字列をハードコードしないでください。 - データは GraphQL クエリ(
react-apolloのuseQuery)で取得し、ブラウザーから直接 API を呼び出さないでください。
アーキテクチャ:
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
厳守事項
制約:すべてのストアフロントブロックにインターフェースを宣言する
Store Framework ブロックとして使用できるようにする React コンポーネントにはすべて、store/interfaces.json に対応するエントリーが必須です。インターフェース宣言がないと、そのブロックはテーマ JSON ファイルから参照できません。
これが重要な理由
store ビルダーは interfaces.json を通じてブロック名を React コンポーネントに解決します。コンポーネントにインターフェースがないと、Store Framework から認識されず、ストアフロントにレンダリングされません。
検出方法
/react 内の React コンポーネントをストアフロントで使う予定なのに、store/interfaces.json に一致するエントリーがない場合は、開発者に警告してください。コンポーネントはコンパイルされますが、レンダリングされません。
正しい例
{
"product-reviews": {
"component": "ProductReviews",
"composition": "children",
"allowed": ["product-review-item"]
},
"product-review-item": {
"component": "ReviewItem"
}
}
// react/ProductReviews.tsx
import ProductReviews from './components/ProductReviews'
export default ProductReviews
誤った例
// 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
制約:管理画面の UI には VTEX Styleguide を使う
管理パネルのコンポーネント(admin ビルダーを使うアプリ)では、UI 要素に VTEX Styleguide(vtex.styleguide)を必ず使ってください。管理画面のアプリで Material UI、Chakra UI、Ant Design などのサードパーティ UI ライブラリを使ってはいけません。
これが重要な理由
VTEX Admin では、Styleguide によって一貫したデザイン言語が適用されます。サードパーティの UI ライブラリを使うと、見た目に一貫性がなくなり、Admin のグローバル CSS と競合する可能性があるほか、不要なバンドルサイズの増加につながります。Styleguide 以外の UI を使用したアプリは、VTEX App Store に提出しても審査に通りません。
検出方法
管理画面のアプリで @material-ui、@chakra-ui/react、@chakra-ui、antd、または @ant-design からのインポートが見つかった場合は、代わりに vtex.styleguide を使うよう開発者に警告してください。
正しい例
// 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
誤った例
// 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>
)
}
制約:react/ 直下からのコンポーネントのエクスポート
すべての Store Framework ブロックコンポーネントには、interfaces.json の component の値と一致するルートレベルのエクスポートファイルが /react ディレクトリに必要です。実際の実装はサブディレクトリに配置してもかまいませんが、ルートファイルは存在しなければなりません。
重要な理由
react ビルダーは、/react のルートにあるファイルを探してコンポーネントを解決します。interfaces.json で "component": "ProductReviews" が宣言されている場合、ビルダーは react/ProductReviews.tsx を探します。このルートエクスポートファイルがないと、コンポーネントが見つからず、ブロックのレンダリングに失敗します。
検出
interfaces.json で参照されているコンポーネント名に対応するファイルが /react のルートにない場合は、処理を停止してエクスポートファイルを作成してください。
正しい例
// react/ProductReviews.tsx — root-level export file
import ProductReviews from './components/ProductReviews/index'
export default ProductReviews
// 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 ProductReviews
誤った例
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"
推奨パターン
React コンポーネントをサブディレクトリ内に作成します。
// 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
ルートエクスポートファイル:
// react/ProductReviews.tsx
import ProductReviews from './components/ProductReviews'
export default ProductReviews
ブロックインターフェース:
{
"product-reviews": {
"component": "ProductReviews",
"composition": "children",
"allowed": ["product-review-form"],
"render": "client"
}
}
Site Editor スキーマ:
{
"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 テーマでコンポーネントを使用する方法:
{
"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
}
}
}
よくある失敗パターン
- 管理アプリでサードパーティ製 UI ライブラリをインポートする:
@material-ui/core、@chakra-ui/react、antdを使用すると、VTEX Admin のグローバル CSS と競合し、表示に一貫性がなくなり、App Store の審査にも通りません。代わりにvtex.styleguideを使用してください。 - React コンポーネントから API を直接呼び出す:
fetch()やaxiosを使用すると、認証トークンがクライアントに公開され、CORS の制限も回避されます。react-apolloのuseQueryを使い、サーバー側で解決される GraphQL クエリを使用してください。 - i18n 対応のないハードコード文字列:文字列がハードコードされたコンポーネントは、1 つの言語でしか動作しません。国際化には
messagesビルダーとreact-intlを使用してください。 - ルートレベルのエクスポートファイルがない:
interfaces.jsonで"component": "ProductReviews"が参照されているのにreact/ProductReviews.tsxが存在しない場合、ブロックのレンダリングはエラーを表示せずに失敗します。 - コンテンツを保持するコンポーネントアプリのメジャーバージョンを上げる:
store/contentSchemas.jsonを含むアプリでvtex release majorを実行すると、そのアプリが宣言したブロックに対してこれまで保存されたすべての Site Editor の編集内容は、[email protected]のupdateThemeIdsミューテーションを使って新しいメジャーバージョンへ移行するまで、リゾルバーから見えなくなります。可能な限りpatchまたはminorを使用し、メジャーバージョンの更新が避けられない場合はvtex-io-storefront-theme-versioningに従ってください。
レビューチェックリスト
- すべてのストアフロントブロックに、
store/interfaces.jsonの対応するエントリーがありますか? - すべての
interfaces.jsonコンポーネントに、/reactのルートレベルのエクスポートファイルがありますか? - 管理アプリは
vtex.styleguideを使用していますか(サードパーティ製 UI ライブラリを使用していませんか)? - ストアフロントコンポーネントは、スタイル設定に
css-handlesを使用していますか? - データは API の直接呼び出しではなく、GraphQL(
useQuery)経由で取得していますか? - ユーザーに表示される文字列は、
react-intlとmessagesビルダーを使用していますか? - Site Editor で編集可能な props に対して
contentSchemas.jsonが定義されていますか? - アプリに
store/contentSchemas.jsonが含まれる場合、予定しているメジャーバージョン更新が加盟店に与える影響を確認しましたか?
関連スキル
vtex-io-storefront-theme-versioning— アプリにstore/contentSchemas.jsonが含まれ、バージョン変更時に Site Editor のコンテンツを保持または移行する必要がある場合に使用します。vtex-io-storefront-theme-app— コンシューマーテーマでこれらのブロックをページに組み込む方法についての質問に使用します。
参考資料
- カスタムストアフロントコンポーネントの開発 — Store Framework コンポーネントの構築ガイド
- インターフェース — インターフェースがブロックを React コンポーネントに対応付ける方法
- React Builder — React Builder の設定とディレクトリ構成
- Site Editor でカスタムコンポーネントを利用可能にする — contentSchemas.json と Site Editor の統合
- Store Framework — ブロックベースのストアフロントシステムの概要
- コンポーネントの使用 — テーマでネイティブコンポーネントとカスタムコンポーネントを使用する方法
- VTEX Styleguide — VTEX Admin UI 向けの公式コンポーネントライブラリ