mcpField
V
vtex-io-react-apps

vtex-io-react-apps

github.com/vtex開発471.6k 回インストール2 週間前に更新
リポジトリ

vtex-io-react-apps について

React builderでVTEX IOのフロントエンドアプリを開発するためのスキルです。Store Frameworkブロック、Site Editorスキーマ、スタイリング、データ取得に加え、コンポーネント構成の確認方法を扱います。

主な機能

  1. 01interfaces.jsonによるブロック対応付け
  2. 02contentSchemas.jsonでのプロパティ公開
  3. 03css-handlesによるストアフロントのスタイリング
  4. 04管理画面でのVTEX Styleguide利用
  5. 05GraphQLデータ取得と国際化の指針
  6. 06コンポーネントのエクスポート確認

活用シーン

  1. 01カスタムStore Frameworkストアフロントブロックの作成
  2. 02Site Editorで編集可能なコンポーネントプロパティの設定
  3. 03VTEX Admin管理画面の開発
  4. 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 — コンシューマーテーマでこれらのブロックをページに組み込む方法についての質問に使用します。

参考資料

コメント