multi-surface-render について
json-render の利用ガイドで、共通のカタログと UI 仕様を定義し、Web、モバイル、ターミナル、文書、メール、画像、動画などに描画する方法を説明します。共通の UI データから複数の出力面を作る開発者向けです。
主な機能
- 01共通カタログと UI 仕様
- 02プラットフォーム別レンダラーレジストリ
- 03React、Vue、Svelte、モバイルに対応
- 04PDF、メール、画像、動画を出力
- 05ターミナルと Next.js の描画
- 06描画先の選択ガイド
活用シーン
- 01同じ UI 仕様を Web と PDF で再利用
- 02共通 UI データから HTML メールを生成
- 03ソーシャル画像やデモ動画を作成
- 04仕様からコマンドライン UI を描画
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ yonatangross/ orchestkit --skill multi-surface-renderインストール後、Claude Code で/multi-surface-renderを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
test-cases.jsonreferences/ork-delta.mdupstream-email.mdupstream-image.mdupstream-pdf.mdupstream-remotion.mdrules/_sections.md_template.mdpdf-email-renderer.mdreact-renderer.mdregistry-mapping.mdtarget-selection.mdvideo-image-renderer.md詳細
上流バージョンの参照:@json-render/core 0.21.0(2026-09-30)。
json-render によるマルチサーフェスレンダリング
一度定義すれば、どこでもレンダリングできます。単一の json-render カタログと仕様から、React の Web UI、PDF レポート、HTML メール、Remotion のデモ動画、OG 画像を生成できます。それぞれのサーフェスには、カタログの型をプラットフォームネイティブのコンポーネントに対応付ける独自のレジストリがあります。
クイックリファレンス
| カテゴリ | ルール | 影響度 | 使用する場面 |
|---|---|---|---|
| ターゲットの選択 | 1 | 高 | ユースケースに合ったレンダラーを選ぶ |
| React レンダラー | 1 | 中 | Web アプリ、SPA、ダッシュボード |
| PDF・メールレンダラー | 1 | 高 | レポート、ドキュメント、通知 |
| 動画・画像レンダラー | 1 | 中 | デモ動画、OG 画像、ソーシャルカード |
| レジストリのマッピング | 1 | 高 | プラットフォーム固有のコンポーネント実装 |
合計:5 カテゴリにわたる 5 つのルール
マルチサーフェスレンダリングの仕組み
- 1 つのカタログ — すべてのサーフェスで共有される、Zod 型付きコンポーネント定義
- 1 つの仕様 — UI 構造を記述するフラットツリー形式の JSON/YAML
- 複数のレジストリ — 各サーフェスが、カタログの型を独自のコンポーネント実装に対応付けます
- 複数のレンダラー — 各パッケージが、レジストリを使って仕様をレンダリングします
カタログは契約です。仕様はデータです。レジストリはプラットフォーム固有の実装です。
クイックスタート — 同じカタログ、異なるレンダラー
共有カタログ(すべてのサーフェスで使用)
import { defineCatalog } from '@json-render/core'
import { schema } from '@json-render/react/schema'
import { z } from 'zod'
export const catalog = defineCatalog(schema, {
components: {
Heading: {
props: z.object({
text: z.string(),
level: z.enum(['h1', 'h2', 'h3']),
}),
children: false,
},
Paragraph: {
props: z.object({ text: z.string() }),
children: false,
},
StatCard: {
props: z.object({
label: z.string(),
value: z.string(),
trend: z.enum(['up', 'down', 'flat']).optional(),
}),
children: false,
},
},
})
Web にレンダリング(React)
import { Renderer } from '@json-render/react'
import { webRegistry } from './registries/web'
// webRegistry comes from `defineRegistry(catalog, { components })`.
// RendererProps is { spec, registry, loading?, fallback? } — no catalog prop.
export const Dashboard = ({ spec }) => (
<Renderer spec={spec} registry={webRegistry} />
)
PDF にレンダリング
import { renderToBuffer, renderToFile } from '@json-render/react-pdf'
import { pdfRegistry } from './registries/pdf'
// Buffer for HTTP response. PDF options are { registry?, state?, handlers? }.
// includeStandard is an EMAIL option, not a PDF one (see references/upstream-pdf.md).
const buffer = await renderToBuffer(spec, { registry: pdfRegistry })
// Direct file output — renderToFile(spec, filePath, options?)
await renderToFile(spec, './output/report.pdf', { registry: pdfRegistry })
メールにレンダリング
import { renderToHtml } from '@json-render/react-email'
import { emailRegistry } from './registries/email'
const html = await renderToHtml(spec, { registry: emailRegistry })
await sendEmail({ to: user.email, subject: 'Weekly Report', html })
OG 画像にレンダリング(Satori)
import { renderToSvg, renderToPng } from '@json-render/image'
import { imageRegistry } from './registries/image'
const png = await renderToPng(spec, {
registry: imageRegistry,
width: 1200,
height: 630,
})
動画にレンダリング(Remotion)
// Verified 2026-07-31 against @json-render/[email protected]: the export is
// `Renderer` and its props are { spec, components }. fps and durationInFrames
// belong on Remotion's own Composition, not on this renderer.
import { Renderer } from '@json-render/remotion'
import { remotionComponents } from './registries/remotion'
export const DemoVideo = () => (
<Renderer spec={spec} components={remotionComponents} />
)
ターミナルにレンダリング(Ink、0.15+)
import { render } from 'ink'
import { Renderer } from '@json-render/ink'
import { catalog } from './catalog'
import { inkRegistry } from './registries/ink'
render(<Renderer spec={spec} catalog={catalog} registry={inkRegistry} />)
/ork:* CLI ダッシュボードや、ストリーミング形式のエージェントチャットインターフェースに便利です。Box、Text、Spinner、Table、Markdown、Progress など、20 種類以上の Ink ネイティブコンポーネントが付属します。
Next.js アプリにレンダリング(0.16+)
// createNextApp lives on the /server subpath, not the package root.
import { createNextApp } from '@json-render/next/server'
const { getPageData, generateMetadata, generateStaticParams } = createNextApp({
spec, // NextAppSpec: routes keyed by Next.js URL patterns
loaders: { getPost }, // server-side data loaders referenced by route.loader
})
プロジェクトをディスク上に生成するわけではありません。createNextApp は、キャッチオールルートから再エクスポートするサーバーサイドの構成要素を返します。ページ自体は PageRenderer を介してレンダリングされます。
// app/[[...slug]]/page.tsx
export { generateMetadata, generateStaticParams }
export default async function Page({ params }) {
const data = await getPageData(params)
if (!data) notFound()
return <PageRenderer {...data} registry={webRegistry} />
}
仕様が記述するのはルートツリー(ページ、レイアウト、メタデータ、ローディング状態、エラー状態)であり、コンポーネントツリーだけではありません。
各ターゲットの選択基準
| ターゲット | パッケージ | 使用する場面 | 出力 |
|---|---|---|---|
| React | @json-render/react | Web アプリ、SPA | JSX |
| Next.js | @json-render/next (0.16+) | ルート、レイアウト、SSR、メタデータを含むフルアプリ | Next.js アプリ |
| Vue | @json-render/vue | Vue プロジェクト | Vue コンポーネント |
| Svelte | @json-render/svelte | Svelte プロジェクト | Svelte コンポーネント |
| Svelte+shadcn | @json-render/shadcn-svelte (0.16+) | 36 個のコンポーネントを備えた Svelte 5 カタログ | Svelte + Tailwind |
| React Native | @json-render/react-native | モバイルアプリ(25 個以上のコンポーネント) | ネイティブビュー |
| Terminal | @json-render/ink (0.15+) | CLI UI、TUI、ストリーミングチャット | Ink(ターミナル) |
@json-render/react-pdf | レポート、ドキュメント | PDF バッファー/ファイル | |
@json-render/react-email | 通知、ダイジェスト | HTML 文字列 | |
| Remotion | @json-render/remotion | デモ動画、マーケティング | MP4/WebM |
| Image | @json-render/image | OG 画像、ソーシャルカード | SVG/PNG(Satori) |
| YAML | @json-render/yaml (0.14+) | トークン最適化、ストリーミングパーサー | YAML 文字列 |
| MCP | @json-render/mcp | Claude/Cursor/ChatGPT の会話 | サンドボックス化された iframe |
| 3D | @json-render/react-three-fiber | 3D シーン(19 個のコンポーネント、2026-07-31 に検証済み。コンポーネント一覧は上流にあります) | Three.js キャンバス |
| コード生成 | @json-render/codegen | 仕様からのソースコード生成 | TypeScript/JSX |
すべての @json-render/* レンダラーは 0.20.0(@json-render/core)に対して検証済みです。
0.19.0 から 0.20.0 へのエクスポート対象は、削除なしで 84 個から 87 個に増えたため、ここで説明するすべての
API は引き続き利用できます。0.20.0 では、名前付きスロット(UIElement の slots?: Record<string, string[]>。カタログでは slots: ["default", "header", ...] を宣言)、{"$item": "employees"} 形式の
項目相対 repeat.statePath を使ったネストされた繰り返し、および項目スコープの表示制御が追加されました。同じ要素に
repeat と visible: {"$item": ...} を指定すると、エラーになるのではなく項目がフィルタリングされます。このスキルで使われるルールには影響しない破壊的変更が 1 つあります。
ActionExecutionContext.executeAction は、単なるアクション名ではなく ActionBinding を受け取るようになりました。これは手書きのカスタムレンダラーブリッジにのみ影響します。
詳しい選択基準とトレードオフについては、rules/target-selection.md を読み込んでください。
アップストリームの対象範囲(繰り返さないでください)
このスキルは @json-render/* をラップします。ベンダーのドキュメントは取得して使用するため、ここでは繰り返しません。ここに残すのは、プロジェクト固有の差分である references/ork-delta.md と5つのルールです。
| トピック | 出典 |
|---|---|
レンダラーの完全なシグネチャとオプションオブジェクト(renderToBuffer / renderToFile / renderToStream、renderToHtml / renderToPlainText、renderToSvg / renderToPng、Remotion のエクスポート) | references/upstream-pdf.md、upstream-email.md、upstream-image.md、upstream-remotion.md(ベンダー提供の原文そのまま。bash scripts/sync-vercel-skills.sh で再同期してください) |
対象ごとの標準コンポーネント一覧(Document、Page、Table、メールの Section / Row / Column、Remotion のトランジションとエフェクト) | 同じ4つのベンダー提供 references/upstream-*.md ファイル |
<Renderer> の props、defineRegistry、useUIStream | https://github.com/vercel-labs/json-render/tree/main/packages/react。0.19 の props 形状に関する修正(catalog prop なし、トップレベルの onError なし)はプロジェクト固有の知見であり、rules/react-renderer.md に記載されています |
| メールクライアントの制約:600px コンテナー、テーブルレイアウト、インラインスタイル、絶対 URL の画像 | references/upstream-email.md(「Email Best Practices」) |
| Satori の CSS サポートマトリクス | https://github.com/vercel/satori。このスキルで画像レジストリを設計する際に使用するサポート範囲は、`rules/video-image-renderer.md` に記載されています |
| react-pdf のスタイルプロパティのサポート(フレックスボックスのプロパティ群、グリッド非対応) | https://react-pdf.org/styling |
| Remotion のレンダリングコストとクラウドレンダリング | https://www.remotion.dev/docs/lambda |
| パッケージごとの機能と出力のマトリクス | プロジェクト固有の対象選定は上記の Decision Matrix と rules/target-selection.md に記載されています。パッケージごとの詳細は https://github.com/vercel-labs/json-render を参照してください |
レンダラーのコードを書く前に references/ork-delta.md を読んでください。API の変更に関するルール、Remotion と PDF のレイテンシ予算、PDF / React Native のレジストリレイアウト上限が記載されています。
PDF Renderer — レポートとドキュメント
@json-render/react-pdf パッケージは、内部で react-pdf を使用して仕様を PDF にレンダリングします。出力モードはバッファー、ファイル、ストリームの3種類です。
import { renderToBuffer, renderToFile, renderToStream } from '@json-render/react-pdf'
// In-memory buffer (for HTTP responses, S3 upload)
// PDF options are { registry?, state?, handlers? }, no catalog field
const buffer = await renderToBuffer(spec, { registry: pdfRegistry })
res.setHeader('Content-Type', 'application/pdf')
res.send(buffer)
// Direct file write — renderToFile(spec, filePath, options?)
await renderToFile(spec, './output/report.pdf', { registry: pdfRegistry })
// Streaming (for large documents)
const stream = await renderToStream(spec, { registry: pdfRegistry })
stream.pipe(res)
PDF レジストリのパターンとメールのレンダリングについては、rules/pdf-email-renderer.md を読み込んでください。
Image Renderer — OG 画像とソーシャルカード
@json-render/image パッケージは Satori を使用して仕様を SVG に変換し、必要に応じて PNG に変換します。ソーシャルメディア画像のサーバーサイド生成用に設計されています。
import { renderToSvg, renderToPng } from '@json-render/image'
// SVG output (smaller, scalable)
const svg = await renderToSvg(spec, {
registry: imageRegistry,
width: 1200,
height: 630,
})
// PNG output (universal compatibility)
const png = await renderToPng(spec, {
registry: imageRegistry,
width: 1200,
height: 630,
})
Satori の制約と Remotion のコンポジションパターンについては、rules/video-image-renderer.md を読み込んでください。
レジストリのマッピング — 同じカタログ、プラットフォーム固有のコンポーネント
各サーフェスにはそれぞれ独自のレジストリが必要です。レジストリは、カタログと仕様を同じままにして、カタログの型をプラットフォーム固有のコンポーネント実装にマッピングします。
// Web registry — uses HTML elements
const webRegistry = {
Heading: ({ text, level }) => {
const Tag = level // h1, h2, h3
return <Tag className="font-bold">{text}</Tag>
},
StatCard: ({ label, value, trend }) => (
<div className="rounded border p-4">
<span className="text-sm text-gray-500">{label}</span>
<strong className="text-2xl">{value}</strong>
</div>
),
}
// PDF registry — uses react-pdf primitives
import { Text, View } from '@react-pdf/renderer'
const pdfRegistry = {
Heading: ({ text, level }) => (
<Text style={{ fontSize: level === 'h1' ? 24 : level === 'h2' ? 18 : 14 }}>
{text}
</Text>
),
StatCard: ({ label, value }) => (
<View style={{ border: '1pt solid #ccc', padding: 8 }}>
<Text style={{ fontSize: 10, color: '#666' }}>{label}</Text>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>{value}</Text>
</View>
),
}
レジストリの作成パターンと型安全性については、rules/registry-mapping.md を読み込んでください。
ルールの詳細
対象の選択
適切なレンダラーの対象を選ぶための判断基準です。
| ルール | ファイル | 主なパターン |
|---|---|---|
| 対象の選択 | rules/target-selection.md | ユースケースのマッピング、出力形式の制約 |
React Renderer
<Renderer> コンポーネントを使用した Web レンダリングです。
| ルール | ファイル | 主なパターン |
|---|---|---|
| React Renderer | rules/react-renderer.md | <Renderer> コンポーネント、ストリーミング、エラーバウンダリー |
PDF & Email Renderer
PDF バッファー/ファイルと HTML メール文字列へのサーバーサイドレンダリングです。
| ルール | ファイル | 主なパターン |
|---|---|---|
| PDF & Email | rules/pdf-email-renderer.md | renderToBuffer、renderToFile、renderToHtml |
Video & Image Renderer
Remotion のコンポジションと Satori による画像生成です。
| ルール | ファイル | 主なパターン |
|---|---|---|
| Video & Image | rules/video-image-renderer.md | Renderer(Remotion)、renderToPng、renderToSvg |
レジストリのマッピング
共有カタログ用のプラットフォーム固有のレジストリを作成します。
| ルール | ファイル | キーパターン |
|---|---|---|
| レジストリのマッピング | rules/registry-mapping.md | プラットフォームごとのレジストリ、型安全なマッピング |
主な決定事項
| 決定事項 | 推奨事項 |
|---|---|
| PDF ライブラリ | Puppeteer のスクリーンショットではなく、@json-render/react-pdf(react-pdf)を使用します |
| メールのレンダリング | MJML やカスタム HTML ではなく、@json-render/react-email(react-email)を使用します |
| OG 画像 | Puppeteer や canvas ではなく、@json-render/image(Satori)を使用します |
| 動画 | FFmpeg スクリプトではなく、@json-render/remotion(Remotion)を使用します |
| プラットフォームごとのレジストリ | レジストリは必ず分けます。すべてのサーフェスに1つのレジストリを使ってはいけません |
| カタログの共有 | すべてのレジストリでインポートを通じて共有するカタログ定義を1つにします |
よくある間違い
- サーフェスごとに個別のコンポーネントツリーを構築する — 目的に反します。カタログと仕様を共有します
- PDF 生成で React のスクリーンショットを撮るために Puppeteer を使う — 遅く、壊れやすいため、ネイティブの react-pdf レンダリングを使います
- すべてのプラットフォームを網羅する巨大なレジストリを1つ作る — PDF では
<View>/<Text>を使い、Web では<div>/<span>を使うため、不可能です - Satori の制限を忘れる — CSS grid は使えず、flexbox にも制限があります。これらの制約を考慮して画像レジストリを設計します
- サーフェスごとにカタログ定義を重複させる — カタログは1つ、レジストリは複数です。カタログが契約となります
関連スキル
ork:json-render-catalog— Zod と shadcn コンポーネントを使ったカタログ定義のパターンork:demo-producer— Remotion を使った動画制作パイプラインork:mcp-visual-output— MCP 経由で Claude/Cursor にレンダリング仕様を出力する方法