---
title: "Generative UI × microCMS で新しい概念の検証をしてみた"
canonical_url: "https://y-brew.vercel.app/tech/2x-4acpe29q"
markdown_url: "https://y-brew.vercel.app/tech/2x-4acpe29q/markdown"
blocks_url: "https://y-brew.vercel.app/tech/2x-4acpe29q/blocks"
published: "2026-03-29"
updated: "2026-03-30"
last_reviewed: "2026-03-29"
tags: []
intent: []
evergreen: false
key_points: []
prerequisites: []
section_headings: ["はじめに", "Generative UIとは何か", "なぜヘッドレスCMSと相性がいいのか", "1. データのフィールドが明確で、コンポーネントにマッピングしやすい", "2. 問い×立場×データの組み合わせが膨大", "3. 運用者が「気づいていない課題」を発見できる", "実証: microCMS × Generative UI", "アーキテクチャ", "サーバー側: APIルート", "クライアント側: 描画", "デモの流れ", "インタラクティブUI", "CatalogでUIの「振る舞い」も制約する", "デモ: 下書き一覧 → 詳細 → 公開", "デモ: サイト閲覧者向けの探索", "Catalogの設計", "最初の試み：ビルトインコンポーネントだけで始める", "ドメイン特化のカスタムコンポーネントを追加する", "Zodスキーマによるコンポーネント登録", "Catalogは「足し算」で進化する", "「Generate then Render」", "検証で見えた発見", "1. 「想定していなかった切り口」が質問から生まれる", "2. 同じデータでも「誰のために」でUIが変わる", "3. 会話で深掘りできる（＝画面遷移の設計が不要）", "4. 「見る」だけでなく「操作」もできる", "APIが豊かになるほど価値が増す", "MCP Apps との関係", "まとめ"]
---

# Generative UI × microCMS で新しい概念の検証をしてみた

## TL;DR
json-render の Generative UI を microCMS の構造化データと組み合わせて検証し、相性のよさと Catalog による制約設計、運用 UI への展開可能性を整理した実践メモ。

## Retrieval Notes
- Prefer this Markdown URL when you need the full article body in a compact text format.
- Prefer the block JSON at https://y-brew.vercel.app/tech/2x-4acpe29q/blocks when you need article structure instead of prose.
- Cite the canonical HTML page at https://y-brew.vercel.app/tech/2x-4acpe29q when linking to the source.
- Treat updated and last_reviewed as freshness signals.

## Section Guide
- はじめに
- Generative UIとは何か
- なぜヘッドレスCMSと相性がいいのか
  - 1. データのフィールドが明確で、コンポーネントにマッピングしやすい
  - 2. 問い×立場×データの組み合わせが膨大
  - 3. 運用者が「気づいていない課題」を発見できる
- 実証: microCMS × Generative UI
  - アーキテクチャ
  - サーバー側: APIルート
  - クライアント側: 描画
  - デモの流れ
- インタラクティブUI
  - CatalogでUIの「振る舞い」も制約する
  - デモ: 下書き一覧 → 詳細 → 公開
  - デモ: サイト閲覧者向けの探索
- Catalogの設計
  - 最初の試み：ビルトインコンポーネントだけで始める
  - ドメイン特化のカスタムコンポーネントを追加する
  - Zodスキーマによるコンポーネント登録
  - Catalogは「足し算」で進化する
- 「Generate then Render」
- 検証で見えた発見
  - 1. 「想定していなかった切り口」が質問から生まれる
  - 2. 同じデータでも「誰のために」でUIが変わる
  - 3. 会話で深掘りできる（＝画面遷移の設計が不要）
  - 4. 「見る」だけでなく「操作」もできる
- APIが豊かになるほど価値が増す
- MCP Apps との関係
- まとめ

## Article

## はじめに

ヘッドレスCMSを使っていると、コンテンツを活用するために画面を作る作業がセットでついてきます。一覧画面、詳細画面、ダッシュボード。見せたい切り口が増えれば、そのぶん画面も増えていく。と思っていました。

そんな中、[**json-render**](https://json-render.dev/)というフレームワークを見かけました。Vercel Labsが公開した「Generative UI」のためのフレームワークで、AIがUIの構造をJSONとして動的に生成し、あらかじめ定義されたコンポーネントで描画する仕組みです。

面白そうだなと思い、microCMSの構造化データと組み合わせたらどうなるか試してみました。この記事ではその検証の過程と気づきを整理してみました。

## Generative UIとは何か

Generative UIとは、事前に画面を設計・実装するのではなく、**AIがユーザーの問いに応じてUIの構造そのものを動的に生成する**アプローチのことを指すようです。

従来のUI開発とGenerative UIの違いを整理すると、以下のように整理できると思います。

**従来のUI開発**

-   開発者がUIを事前に設計・実装
-   データは固定のテンプレートに流し込まれる
-   新しい切り口が必要になるたびに、画面を追加開発
-   「見たいもの」が事前に決まっていることが前提

**Generative UI**

-   AIが問いの意図を理解し、その場でUI構造（JSON）を生成
-   同じデータでも、質問が変われば表示形式が変わる
-   使えるコンポーネントはCatalogで制約されており、AIが勝手に未知のUIを作ることはない
-   「何が見たいかわからない」状態でも、聞くことで発見が生まれる

## なぜヘッドレスCMSと相性がいいのか

Generative UIはデータさえあれば成り立つ仕組みなので、ヘッドレスCMSに限った話ではありません。ただ、実際にmicroCMSで試してみて相性が良いと感じた点がいくつかあったので、整理してみます。

### 1\. データのフィールドが明確で、コンポーネントにマッピングしやすい

microCMSに格納されたデータは、`title`、`category`、`publishedAt`のようにフィールド名と型が明確に定義されています。AIがUI構造を生成するとき、このフィールド名を見て「`title`はContentCardのタイトルに、`category`はバッジ表示に」と自然にマッピングできます。これがプレーンテキストやHTMLのような非構造データだと、まずデータの構造を推測するところから始まるので、ひと手間増える印象です。

ただ、これはヘッドレスCMSに限った話ではなく、構造化されたデータ全般に言えることだと思います。RDBや他のAPIでも同様のことはできるはずです。ヘッドレスCMSの場合は、フィールド名が人間にとっても読みやすい名前（`記事タイトル`、`カテゴリ`など）で設計されていることが多く、AIがUIにマッピングする際に追加の説明が不要な点は利点だと感じました。

### 2\. 問い×立場×データの組み合わせが膨大

microCMSに格納されるコンテンツは、記事・タグ・著者・カテゴリと多岐にわたりますが、同じデータでも**「誰が」「何のために」見るか**で最適なUIが変わります。そしてその組み合わせは、サイト閲覧者・CMS運用者の両面で膨大になると感じました。

**サイト閲覧者の例**

-   旅行ガイドサイトでは、スポット情報がmicroCMSに構造化されていて、「子連れで楽しめる沖縄のスポットは？」「雨の日でも行ける場所に絞りたい」「この3つのスポットを比較したい」と、閲覧者の状況ごとに欲しい切り口が変わります
-   数百店舗を展開するような多拠点ビジネスでは、店舗やキャンペーンの情報をmicroCMSで一元管理しているケースがあります。「近くの店舗のキャンペーン詳細を見たい」ならカード形式の一覧が合いそうですし、「全店舗のキャンペーン実施状況を俯瞰したい」ならテーブル形式の集計表の方が見やすいかもしれません
-   コーポレートサイトでは、求職者が見たい採用情報と、投資家が見たいIR情報と、記者が見たいプレスリリースが、同じmicroCMSから配信されていることがあります

**CMS運用者の例**

-   コンテンツ編集者は「自分が担当している下書き記事の状況」を把握したい
-   編集長は「チーム全体の公開ペース」や「放置されている下書き」を俯瞰したい
-   マーケティング担当者は「カテゴリ別の記事数の偏り」や「今月の公開コンテンツ数」を確認したい
-   多拠点の場合、本部は全店舗のコンテンツ更新状況を横断的に見たいが、各店舗担当者は自分の店舗分だけ見たい

もちろんこれまでも、必要な画面はそのつど作ってきました。ただ、「カテゴリ別の偏りをちょっと確認したい」「先月と比較してみたい」のような、わざわざ画面を作るほどではないけれど、ふと知りたくなる切り口は意外と多いのかなと感じたりしました。

### 3\. 運用者が「気づいていない課題」を発見できる

CMS管理画面でもステータスによるフィルタリングは可能ですが、たとえば「下書きのまま1ヶ月以上放置されている記事を、担当者ごとにまとめて見たい」「特定カテゴリの記事が最近減っているのはなぜか」のような、複数の条件を組み合わせた問いかけに対応する画面を事前に用意するのは難しいと思います。Generative UIなら、聞くだけでそうした切り口のUIが生まれるかもしれません。

もう一つ付け加えると、microCMSにはManagement APIがあり、フィールド名・型・選択肢といったスキーマ情報をプログラムから取得できます。今回の検証では、このスキーマ情報をAIのコンテキストに含めることで、データの構造を踏まえたUI生成ができました。

ただ正直なところ、スキーマ情報が取れるという点だけならRDBでも同じことは可能です。ヘッドレスCMSならではの利点があるとすれば、コンテンツ管理用に設計されたフィールド（リッチエディタ、画像、コンテンツ参照など）が、UIコンポーネントとの対応を取りやすいことや、非エンジニアがコンテンツを更新できる運用面との組み合わせかなと思います。

## 実証: microCMS × Generative UI

今回の検証では、json-renderフレームワークを使って、Next.js上でGenerative UIの概念実証を試みました。

### アーキテクチャ

全体の構造は比較的シンプルです。

1.  ユーザーが自然言語で質問を入力
2.  サーバー側でmicroCMSの全コンテンツ+スキーマ定義をAI（Gemini 2.5 Flash）のコンテキストに含める。モデルの選定はコスト重視で、ストリーミングでUIを逐次描画するためレスポンス速度とのバランスも考慮した
3.  json-renderのCatalogがAIに「使えるコンポーネント一覧」を提示
4.  AIがテキスト回答とともに、UIの構造をJSONとしてストリーミング生成
5.  クライアント側でjson-renderのRendererがJSONをReactコンポーネントに変換・描画

個人的に重要だと感じたのは、**Catalogがコンポーネントの制約として機能する**点です。AIは登録されたStatCard、DataGrid、ContentCardなどの中からしかUIを構築できない。これにより「AIが暴走して意味不明なUIを生成する」リスクを抑えられているように思います。

### サーバー側: APIルート

APIルートの実装は、CatalogからAI向けプロンプトを生成し、ストリーミングでレスポンスを返すだけです。

```typescript
import { streamText } from "ai";
import { google } from "@ai-sdk/google";
import { generatePreviewCatalog } from "@microcms-json-render/catalog-adapter";
import { loadApiSchema, getContentList } from "@/lib/microcms";

export async function POST(req: Request) {
  const { messages } = await req.json();

  // microCMSから全コンテンツとスキーマを取得
  const allContents = { blogs: getContentList("blogs"), tags: getContentList("tags") };
  const allSchemas = { blogs: loadApiSchema("blogs") };

  // microCMSのスキーマ定義から、json-renderのCatalogを生成
  // Catalogには「AIが使えるコンポーネント一覧」と「各propsの型定義」が含まれる
  const blogsSchema = allSchemas["blogs"];
  const catalog = generatePreviewCatalog({
    apiFields: blogsSchema.apiFields,
    customFields: blogsSchema.customFields ?? [],
  });

  const systemPrompt = catalog.prompt({
    mode: "inline",        // テキスト+JSON混在ストリーミング
    customRules: [
      "ユーザーの質問にテキストで答えつつ、適切な場合はリッチUIを生成してください",
      `全コンテンツ: ${JSON.stringify(allContents)}`,
      `スキーマ定義: ${JSON.stringify(allSchemas)}`,
    ],
  });

  // Vercel AI SDKでストリーミング生成
  const result = streamText({
    model: google("gemini-2.5-flash"),
    system: systemPrompt,
    messages,
  });

  return result.toTextStreamResponse();
}
```

`catalog.prompt({ mode: "inline" })`の1行で、Catalogに登録された全コンポーネントのprops定義、使用例、アクション定義が自動的にシステムプロンプトに変換されます。AIはこのプロンプトに従って、テキストとJSON Specを混在させたストリームを出力します。

### クライアント側: 描画

クライアント側では`useChatUI`フックでメッセージを管理し、`Renderer`でJSON SpecをReactコンポーネントに変換します。

```typescript
import { useChatUI, Renderer, StateProvider, ActionProvider } from "@json-render/react";
import { componentRegistry } from "@/lib/registry";

export function ChatPageClient() {
  const { messages, isStreaming, send } = useChatUI({ api: "/api/chat" });

  return (
    <div>
      {messages.map((msg) => (
        <div key={msg.id}>
          {/* テキスト部分 */}
          {msg.text && <p>{msg.text}</p>}

          {/* AI生成のUI部分 */}
          {msg.spec && (
            <StateProvider initialState={msg.spec.state}>
              <ActionProvider handlers={actionHandlers}>
                <Renderer
                  spec={msg.spec}
                  registry={componentRegistry}
                  loading={isStreaming}
                />
              </ActionProvider>
            </StateProvider>
          )}
        </div>
      ))}
    </div>
  );
}
```

`useChatUI`が返す各メッセージには`text`（テキスト回答）と`spec`（UI構造のJSON）が含まれています。`Renderer`は`spec`を`componentRegistry`に登録されたReactコンポーネントにマッピングして描画します。ストリーミング中は`loading`プロパティで部分的な描画が行われます。

### デモの流れ

以下は実際のデモ画面です。

![Field Notes — 初期画面](https://images.microcms-assets.io/assets/60dd058c671d490e94ef51fbf40d022c/70349df181c44850bcdb4b3cc7b1dd25/new-chat-empty.png)

例えば、まず「コンテンツの全体状況を教えて」と問いかけます。

![全体概況のリッチUI](https://images.microcms-assets.io/assets/60dd058c671d490e94ef51fbf40d022c/1babaf0c0f63407992037f727f6c28a9/demo-rich-overview-figure.png)

AIが記事数、公開/下書きの内訳をStatCardで示しつつ、カテゴリ別の集計、よく使われるタグ、最新記事一覧までまとめて返してきました。この画面は誰も事前に設計していません。AIがデータとコンポーネントの組み合わせから、その場で構成したものです。

次に「カテゴリごとの記事数を見せて」と切り口を変えてみます。

![カテゴリ別の記事数](https://images.microcms-assets.io/assets/60dd058c671d490e94ef51fbf40d022c/bdad4e05f3404160babf714ad57c1881/demo-category.png)

同じデータに対して、今度はStatCardだけでカテゴリ別の件数が生成されました。質問の意図が変われば、UIの構造も変わるという感じのデモでした。

## インタラクティブUI

Generative UIは「表示」だけでなく、操作ももちろん可能です。

**AIが生成したUI上でユーザーが操作を行い、その結果がCMSに反映される**フローを試しに実装してみました。

### CatalogでUIの「振る舞い」も制約する

json-renderのCatalogにはコンポーネント定義だけでなく、**アクション定義**も含めることができます。これにより、AIが生成するUIに「ボタンを押したら何が起きるか」という振る舞いのレイヤーを追加できるようになります。

今回試しに実装してみたアクションは2つです。

-   **view\_detail** — ContentCardをクリックすると、その記事の詳細情報を表示する。AIに対して「この記事の詳細を見せて」というフォローアップ質問を自動送信する
-   **publish\_content** — 「公開する」ボタンをクリックすると、microCMSのAPIを通じて記事のステータスを実際に「公開」に変更する

これらのアクションもCatalogの一部としてZodスキーマで定義されています。AIは「何でもできるボタン」を生成できるわけではなく、Catalogに登録されたアクションの中からしか選べません。制約と自由度のバランスが、ここでもうまく効いているように感じました。

クライアント側では、`ActionProvider`にアクションハンドラーを登録します。AIが生成したUI上のボタンやカードがクリックされると、対応するハンドラーが実行されます。

```
// クライアント側のアクションハンドラー
const actionHandlers = {
  // カードクリック → 詳細表示（AIにフォローアップ質問を送信）
  view_detail: async (params) => {
    const title = params.title || "この記事";
    await send(`「${title}」の詳細を見せて。公開ボタンも付けて。`);
  },

  // 公開ボタンクリック → microCMS APIで実際に公開
  publish_content: async (params) => {
    const res = await fetch("/api/publish", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        endpoint: params.endpoint,
        contentId: params.contentId,
      }),
    });
    const data = await res.json();
    if (data.ok) {
      await send(`「${params.contentId}」を公開しました。最新の全体状況を見せて。`);
    }
  },
};
```

`view_detail`のハンドラーは個人的に面白いと感じた部分です。カードがクリックされると、**AIに対して新しい質問を自動送信する**ことで詳細画面を「生成」しています。画面遷移ではなく、会話の継続として機能する感じです。

サーバー側の`/api/publish`ルートは、microCMS JS SDKを使って記事のステータスを変更します。

```typescript
export async function POST(req: Request) {
  const { endpoint, contentId } = await req.json();

  // microCMS SDKでコンテンツのステータスを公開に変更
  await client.update({
    endpoint,
    contentId,
    content: {},
    options: { status: "publish" },
  });

  return Response.json({ ok: true });
}
```

### デモ: 下書き一覧 → 詳細 → 公開

「下書きのまま放置されている記事はある？」と聞くと、AIが下書き記事をContentCardで一覧表示してくれました。各カードにはview\_detailアクションが設定されており、クリック可能になっています。

![下書き記事の一覧](https://images.microcms-assets.io/assets/60dd058c671d490e94ef51fbf40d022c/43d962dd4d594be89cca26a867f0741e/demo-drafts-list.png)

カードをクリック（または「〇〇の詳細を見せて。公開ボタンも付けて。」と質問）すると、記事の詳細情報がContentCardで表示され、その下に「公開する」ボタンが配置されます。

![記事詳細と公開ボタン](https://images.microcms-assets.io/assets/60dd058c671d490e94ef51fbf40d022c/3ff39ceb6597440c83745f7128a9f178/demo-detail-publish.png)

「公開する」ボタンをクリックすると、サーバー側の`/api/publish`エンドポイントがmicroCMSのAPIを呼び出し、記事のステータスを実際に公開に変更します。公開完了後は「公開しました。最新の全体状況を見せて。」というフォローアップが自動送信され、更新後の状態がリアルタイムで反映されます。

この一連のフローで面白いと感じたのは、**一覧画面も詳細画面も公開確認画面も、事前にルーティングやページ遷移として設計していない**という点です。すべてが会話の文脈の中で動的に生成されています。

### デモ: サイト閲覧者向けの探索

ここまでのデモはCMS運用者寄りでしたが、同じGenerative UIの発想はサイト閲覧者側でもわかりやすく効きます。旅行スポットの構造化データを例に、問い方によってUI構造がどう変わるかを試してみました。

たとえば「子連れOKを予算別に、雨の日代替も」と聞くと、AIは条件をタブと予算別Sectionに分解し、それぞれにContentCardを並べる構成を選びました。1枚の一覧に押し込むのではなく、家族旅行の検討フローに合わせて情報を整理してくれます。

![家族旅行プランのタブUI](https://images.microcms-assets.io/assets/60dd058c671d490e94ef51fbf40d022c/8456eebe22fd401a896f11afe05a4ea6/demo-spots-family.png)

「文化・歴史スポットを比較して」と切り替えると、今度は京都と鎌倉を左右に並べた比較UIになりました。同じスポットデータでも、探索ではなく比較が主題になると、AIが左右分割のレイアウトを選ぶのがわかります。

![京都と鎌倉の比較UI](https://images.microcms-assets.io/assets/60dd058c671d490e94ef51fbf40d022c/06854d91c45b441dae122071424a22bf/demo-spots-kyoto-kamakura.png)

さらに「カテゴリ別・料金帯別に一覧、子連れ・雨の日も」と条件を増やすと、アコーディオンとカテゴリグルーピングを組み合わせた俯瞰UIが生成されました。条件が多くなるほど、単純なカード列やテーブルではなく、情報を折りたためる構造が効いてきます。

![旅行スポットの俯瞰UI](https://images.microcms-assets.io/assets/60dd058c671d490e94ef51fbf40d022c/4bdb3c0ae6894794b4daff2f5aaceeb5/demo-spots-matrix.png)

ここで伝わるのは、Generative UIの価値が単に「カードかテーブルか」を切り替えることではなく、**複雑な問いに対して、AIがその意図に合ったUI構造そのものを選べる**ことだという点です。運用者向けのダッシュボード的な体験だけでなく、サイト閲覧者向けの探索体験にも広げられると感じました。

## Catalogの設計

Generative UIにおいて、Catalogの設計がかなり重要な要素だと感じました。AIが使えるコンポーネントの一覧と、各コンポーネントが受け取れるpropsを定義する。AIはこの中からしかUIを構築できません。

この「制約」をどう設計するかが、想像以上に出力品質に影響していました。

### 最初の試み：ビルトインコンポーネントだけで始める

json-renderには`@json-render/shadcn`というパッケージがあり、shadcn/uiベースの汎用プリビルトコンポーネントが用意されている。Card、Table、Badge、Button、Selectなど、UIの基本パーツが一通り揃っています。最初はこれだけで始めてみました。「汎用コンポーネントが揃っているなら、AIがよしなに選んでくれるだろう」と考えです。

結果は期待通りにはいきませんでした。特定の質問をしたときに、AIの出力がいまいちになることがありました。たとえばCMSのコンテンツ一覧を表示してほしいときに、汎用的なCardやTableはあっても、「記事プレビュー」に適したコンポーネントがないなどです。AIは手持ちのコンポーネントでなんとかしようとするのですが、結果として中途半端なUIになったという感じです。

### ドメイン特化のカスタムコンポーネントを追加する

そこで、microCMSのデータ構造に合わせたカスタムコンポーネントをCatalogに追加してみました。「このCMSに格納されているデータを、どんな切り口で見せたいか」を考えて設計した8種です。

-   **StatCard** — KPI数値の表示（記事数、公開/下書きの内訳などに使う）
-   **DataGrid** — テーブル形式のデータ表示（カテゴリ別集計などに使う）
-   **ContentCard** — 記事プレビューカード（タイトル、カテゴリ、日付をまとめて表示）
-   **StatusBadge** — ステータス表示（公開/下書き/レビュー中などの状態を色で区別）
-   **Callout** — 重要情報の強調（「注意が必要な記事がある」などの警告表示）
-   **Section** — セクション区切り（レポート形式の出力で構造を整理）
-   **TagCloud** — タグ一覧表示（コンテンツの分類を俯瞰する）
-   **MetricRow** — 指標の横並び表示（複数のKPIを一行で比較する）

実装としては、shadcnの汎用コンポーネントを残したまま、その上にカスタムコンポーネントを追加しています。

```typescript
return defineCatalog(schema, {
  components: {
    ...shadcnComponentDefinitions,  // 汎用コンポーネント（Card, Table等）
    ...chatComponents,              // CMS特化の8種を追加
  },
});
```

ただし、コンポーネントを追加しただけでは不十分でした。AIは依然として汎用のCardやTableを使おうとすることがありました。そこで、プロンプトのcustomRulesでカスタムコンポーネントの**優先使用を指示**しました。

```typescript
// customRulesの一部
"カスタムコンポーネント（StatCard, DataGrid, ContentCard, Callout, Section,
  TagCloud, MetricRow, StatusBadge）を標準コンポーネント（Card, Table, Badge等）
  よりも優先して使ってください"
```

つまり、Catalogの構成は「汎用コンポーネントを削除する」のではなく、**「ドメイン特化のコンポーネントを追加し、そちらを優先させる」**というアプローチです。汎用コンポーネントはフォールバックとして残しつつ、CMSデータに最適化されたコンポーネントがあればそちらが使われるようになります。

この「追加＋優先指示」の組み合わせにより、AIの出力品質はかなり改善されたと感じました。ContentCardが追加される前は汎用Cardで表現していた記事一覧が、タイトル・カテゴリ・ステータス・タグ・日付を適切にレイアウトした専用カードで表示されるようになりました。

### Zodスキーマによるコンポーネント登録

Catalogに登録する各コンポーネントは、Zodスキーマでpropsの型を厳密に定義しています。これは単なる型安全の話だけではなく、AIにとっての「設計図」としても機能しているように思います。

たとえばContentCardの定義は以下のようになります。

```typescript
ContentCard: {
  props: z.object({
    title: z.string().describe("記事タイトル"),
    description: z.string().nullable().describe("記事の概要や説明文"),
    category: z.string().nullable().describe("カテゴリ名"),
    status: z.enum(["公開", "下書き"]).nullable().describe("公開ステータス"),
    date: z.string().nullable().describe("日付文字列"),
    tags: z.array(z.string()).nullable().describe("タグ名の配列"),
  }),
  description: "記事プレビュー用カード。複数記事を表示するときはStackの中に個別に直接並べる。",
}
```

`.describe()`で各propsに説明を付けています。`catalog.prompt()`を呼ぶと、このZodスキーマから自動的にAI向けのシステムプロンプトが生成されます。手書きのドキュメントと違い、コードとドキュメントが乖離する心配がありません。

このZodスキーマに対応する描画側のReactコンポーネントは以下のようになる。Catalogの定義が「AIへの設計図」だとすると、こちらは「ブラウザでの実体」にあたります。

```typescript
function ContentCard({ props, emit, on }) {
  const isClickable = on && on("press")?.bound;

  return (
    <div
      style={{ cursor: isClickable ? "pointer" : "default" }}
      onClick={() => isClickable && emit("press")}
    >
      <h3>{props.title}</h3>
      {props.status && <StatusBadge status={props.status} />}
      {props.description && <p>{props.description}</p>}
      {props.tags?.map(tag => <span>#{tag}</span>)}
      {props.date && <span>{props.date}</span>}
    </div>
  );
}
```

`on("press")?.bound`で、AIがこのカードにアクションを設定したかどうかを判定しています。設定されていればクリック可能になり、`emit("press")`でアクションが発火します。**同じContentCardコンポーネントが、AIの判断次第でクリック可能にもなり、静的な表示にもなる**という柔軟さがあるように思います。

加えて、アクション定義もZodスキーマで記述します。

```typescript
actions: {
  view_detail: {
    params: z.object({
      contentId: z.string().describe("microCMSのコンテンツID"),
      title: z.string().describe("記事タイトル"),
    }),
    description: "記事の詳細を表示する。ContentCardのon.pressに設定する。",
  },
  publish_content: {
    params: z.object({
      contentId: z.string().describe("公開するコンテンツのID"),
      endpoint: z.string().describe("APIエンドポイント名"),
    }),
    description: "下書き記事をmicroCMSで公開する。",
  },
}
```

コンポーネントの見た目だけでなく、振る舞い（アクション）もスキーマとして定義することで、**AIが生成できるUIの全体像がCatalogという単一の場所に集約されます**。これがGenerative UIにおける安全性と柔軟性の両立の鍵になっているのではないかと感じています。

### Catalogは「足し算」で進化する

カスタムコンポーネントは最初から8種が揃っていたわけではありません。検証を進める中で「もう少し見せ方が欲しい」と感じる場面があり、その都度1つずつ追加していきました。

たとえば、最初はCalloutがありませんでした。ですが、「マーケ会議用にサマリーを」と依頼したときに、重要ポイントを強調する手段がなく、テキストだけの出力になりました。Calloutを追加したことで、AIは自然に重要な情報をCalloutで囲むようになりました。

ここでの気づきは、**Catalogの設計は一度きりではなく、使いながら育てていくもの**ということでした。新しい見せ方が必要になったら、コンポーネントを1つ追加し、必要に応じてプロンプトの優先ルールも更新する。どの質問にどのコンポーネントを使うかの判断はAIに委ねられるので、ルーティングや画面遷移の設計は不要です。

これは従来のUI開発とはかなり異なるアプローチだと感じました。従来なら「この画面にはこのコンポーネントを配置する」と設計しますが、Generative UIでは「このコンポーネントが存在する」だけでよく、配置はAIが質問の意図に応じて決めてくれます。

## 「Generate then Render」

検証を進める中で、ある概念的な類似に気づきました。CloudflareがDynamic Workersとして発表した「AIがその場でWorkerのコードを生成し、V8 Isolateで実行する」という[アーキテクチャ](https://blog.cloudflare.com/dynamic-workers/)と、json-renderの「AIがその場でJSON Specを生成し、Catalogで制約されたコンポーネントで描画する」という構造が、根本的に何か同じパターンに見えました。

両者に共通するのは**「AIがその場で生成したものが、安全な制約の下でそのまま実行・描画される」**という点です。従来は「人間が作る→デプロイする→ユーザーが使う」というサイクルが前提だったが、このパラダイムでは**「AIが生成する→即座に実行される」**という流れになる感じかと思っています。間にデプロイのステップがありません。

ただし「何でも生成できる」わけではありません。両者とも安全性のための制約機構を持っています。

-   **Cloudflare Dynamic Workers** — V8 Isolateがサンドボックスとして機能し、生成されたコードの実行範囲を制限する
-   **json-render Catalog** — 登録されたコンポーネントとアクションのみが使用可能で、AIは制約の外のUIを生成できない

サンドボックスの形態は異なるが、**「AIの創造性を活かしつつ、安全な枠の中に収める」**という設計思想は同じです。これを仮に「Generate then Render」と勝手に呼ぶなら、今後さまざまな領域に広がっていく可能性がありそうと思いました。（自分のその概念に頭をスイッチしないとなんか理解が進まない感じもありました）

APIが公開するデータが増えれば増えるほど、CMSの運用面でのアクション（公開・非公開・ステータス変更など）が提供されるほど、Generative UIでできることも広がっていくのではないかと思います。

## 検証で見えた発見

### 1\. 「想定していなかった切り口」が質問から生まれる

「下書きのまま放置されている記事を、カテゴリごとにまとめて見せて」 -> この質問に対して、AIはContentCardを使って下書き記事をカテゴリ別にグルーピングし、StatusBadgeで状態を可視化しました。

単純な下書きフィルタリングはCMS管理画面でもできますが、「カテゴリごとに整理して」「期間で絞り込んで」のような複合的な切り口を組み合わせた画面は、事前に用意されていないことが多いです。Generative UIでは、**こうした細かい切り口の画面を事前に設計しなくてよい**のかもしれません。問えば生まれます。という感じです。

### 2\. 同じデータでも「誰のために」でUIが変わる

「マーケ会議用にコンテンツ状況をサマリーにして」と依頼すると、Calloutで重要ポイントを強調し、Sectionで構造化された報告書形式のUIが生成されました。同じデータでも、エンジニア向けと、マーケ向けでは最適なUIが異なります。従来なら役割ごとに別画面を開発する必要がありましたが、Generative UIなら問い方を変えるだけでよくなります。

### 3\. 会話で深掘りできる（＝画面遷移の設計が不要）

「その中で一番注意が必要なものの詳細を見せて」 -> 前の回答を踏まえた追加質問で、自然に情報を深掘りできます。固定画面では一覧→詳細の遷移ルートを事前に設計する必要がありますが、Generative UIでは会話の文脈で自動的にUIが構成されます。

### 4\. 「見る」だけでなく「操作」もできる

今回の検証で追加したインタラクティブフローにより、もう一つの気づきがありました。Generative UIは情報の表示だけでなく、**CMSのコンテンツに対する実際の操作**も可能にします。カードをクリックして詳細を見る、ボタンを押して記事を公開する。これらの操作はすべてCatalogのアクション定義によって制約されており、AIが不正な操作を生成することはないです。

「Generative UIはダッシュボードの代替」という見方を超えて、**CMS管理画面の一部機能すら対話的に提供できる**可能性があるのではないかと感じています。

## APIが豊かになるほど価値が増す

今回の検証では、microCMSのコンテンツAPIとスキーマ情報のみを使用しました。もしCMS側がより多くのデータを公開してくれれば、Generative UIでできることも広がりそうです。

-   **リビジョン履歴** — diff表示、変更追跡、「誰がいつ何を変えた？」
-   **操作ログ** — チームの活動分析、「今月一番編集したのは誰？」
-   **APIコール統計** — 人気コンテンツ分析、「最も読まれている記事は？」
-   **公開スケジュール** — タイムライン表示、「来週公開予定の記事は？」

つまり、**Generative UIの価値 = 利用可能なコンポーネント × アクセスできるデータの幅 × 実行可能なアクション**という関係があります。CMS開発側にとって、API公開とアクション提供の新たなインセンティブとなりうる視点なのかもしれません。

## MCP Apps との関係

ここまでの検証はNext.jsアプリ上で完結していますが、もう一つ気になっていたのが[MCP Apps](https://modelcontextprotocol.io/docs/extensions/apps)との関係です。MCP AppsもAIチャット内にインタラクティブなUIを埋め込む仕組みで、一見するとjson-renderと似た領域に見えます。

ただ、実際に両方を調べてみると、レイヤーが違うということがわかります。

MCP Appsは「どこにUIを表示するか」のプロトコル。Claude、ChatGPT、VS Code、Cursor等のホスト内に、サンドボックス化されたiframeとしてUIを安全に表示する仕組み。UIの中身そのもの（HTMLに何を書くか）には関与しない。

json-renderは「UIの中身をどう生成するか」のフレームワーク。AIがUIの構造をJSONとして生成し、Catalogで制約されたコンポーネントで描画する。どこに表示するか（ブラウザ単体か、チャット内か）には関与しない。

つまり、MCP Appsが「箱」だとすると、json-renderは「箱の中身の生成方法」にあたります。MCP Appsだけでも動的なUIは作れますが、json-renderのCatalogのような制約・生成の仕組みは別途自分で用意する必要があります。json-renderを組み合わせることで、その枠の中にAIが動的生成したUIが表示されるようになります。

実際に、json-renderには[@json-render/mcp](https://json-render.dev/docs/api/mcp)というパッケージが用意されていて、json-renderのCatalogをMCPツールとして公開し、生成されたUIをMCP Appsのiframe内で描画する連携がサポートされています。

今回の検証はNext.jsアプリ上のチャットUIで行いましたが、@json-render/mcpを使えば同じCatalogとコンポーネントをClaude DesktopやChatGPTの会話内でも動かせると思います。

独自のフロントエンドを持たなくても、既存のAIチャットクライアントがそのままGenerative UIのホストになるということです。ヘッドレスCMSにとっての「フロントエンドを作る必要がある」という前提が、もう一段揺らぐかもしれません。

## まとめ

Generative UIは「UIを事前に作る」という前提そのものを問い直すきっかけになるのではないかと感じています。ヘッドレスCMSの構造化データと組み合わせることで、以下のような体験が実現できそうです。

-   質問するだけで、データに最適なUIがその場で構築される
-   事前に想定していなかった切り口の画面が、問いから生まれる
-   役割・文脈に応じて、同じデータが異なるUIで表示される
-   Catalogによる制約で、AIの生成物は予測可能かつ安全
-   AIが生成したUI上から、CMSコンテンツへの操作も実行できる

「Generate then Render」 -> AIがその場で生成し、安全な制約の下で即座に描画・実行する。これは、Cloudflare Dynamic Workersのようなサーバーサイドの文脈でも、json-renderのようなフロントエンドの文脈でも、同じ構造で現れていそうです。

「どんな画面が必要かわからない」「全パターンのUIを作るリソースがない」 -> ヘッドレスCMSの運用で感じるこの種の摩擦は、もしかすると**「画面を事前に作る」という暗黙の前提そのもの**に起因しているのかもしれません。

今回の検証はまだまだ入り口に過ぎず、Catalogの設計、データの渡し方、アクションの範囲と権限制御、そして自分が勝手に言っている「Generate then Render」が他の領域にどう広がるか。深掘りしたいテーマはまだたくさんありますが、「フロントを作るのが当たり前」だった自分の前提が揺らいだという体験はとても面白かったです。
