关于 multi-surface-render
这是一份 json-render 使用指南,介绍如何定义一套目录和界面规范,并将其渲染到网页、移动端、终端、文档、邮件、图片、视频等平台。适合需要用共享界面数据生成多种输出的开发者。
主要功能
- 01共享目录与界面规范
- 02按平台配置渲染器注册表
- 03支持 React、Vue、Svelte 和移动端
- 04输出 PDF、邮件、图片和视频
- 05支持终端与 Next.js 渲染
- 06提供渲染目标选择指引
适用场景
- 01将同一界面规范用于网页和 PDF
- 02根据共享界面数据生成 HTML 邮件
- 03生成社交图片或演示视频
- 04根据规范渲染命令行界面
安装
复制下面的命令,在终端中运行。
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 条规则
多端渲染的工作方式
- 一个目录 — 跨所有端共享的 Zod 类型组件定义
- 一个规范 — 描述 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:* 命令行仪表板和流式 Agent 聊天界面——内置 20 多个 Ink 原生组件(Box、Text、Spinner、Table、Markdown、Progress 等)。
渲染为 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 个以上组件) | 原生视图 |
| 终端 | @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 |
| 图像 | @json-render/image | OG 图像、社交卡片 | SVG/PNG(Satori) |
| YAML | @json-render/yaml (0.14+) | Token 优化、流式解析器 | 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", ...] 声明),通过相对于条目的
repeat.statePath(格式为 {"$item": "employees"})实现嵌套重复,以及条目范围内的可见性,
因此在同一元素上组合 repeat 和 visible: {"$item": ...} 会筛选条目,而不会导致失败。有一项
破坏性变更,本技能中的规则均未使用:ActionExecutionContext.executeAction 现在接收
ActionBinding,而非裸动作名称,这只会影响手写的自定义渲染器桥接。
加载 rules/target-selection.md,了解详细的选择标准和权衡。
上游覆盖范围(无需重述)
此技能封装了 @json-render/*。供应商文档通过获取引入,而非在此重复。此处保留的内容
是本项目的增补:references/ork-delta.md 以及五条规则。
| 主题 | 来源 |
|---|---|
完整的渲染器签名和选项对象(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 转场和效果) | 同样的四个引入的 references/upstream-*.md 文件 |
<Renderer> 属性、defineRegistry、useUIStream | https://github.com/vercel-labs/json-render/tree/main/packages/react。0.19 属性形状修正(没有 catalog 属性,也没有顶层 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 样式属性支持(flexbox 集合,不支持 grid) | https://react-pdf.org/styling |
| Remotion 渲染成本和云端渲染 | https://www.remotion.dev/docs/lambda |
| 各软件包的功能与输出矩阵 | 本项目的目标选择保留在上方的决策矩阵和 rules/target-selection.md 中;各软件包详情见 https://github.com/vercel-labs/json-render |
编写渲染器代码前,请阅读 references/ork-delta.md:其中包含 API 漂移规则、Remotion 和 PDF 延迟预算,以及 PDF / React Native 注册表的布局上限。
PDF 渲染器 — 报告和文档
@json-render/react-pdf 软件包在底层使用 react-pdf 将规格渲染为 PDF。提供三种输出模式:缓冲区、文件和流。
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)
加载 rules/pdf-email-renderer.md,了解 PDF 注册表模式和电子邮件渲染。
图片渲染器 — 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,
})
加载 rules/video-image-renderer.md,了解 Satori 限制和 Remotion 合成模式。
注册表映射 — 同一目录,不同平台组件
每个界面都需要自己的注册表。注册表将目录类型映射到特定平台的组件实现,同时目录和规格保持一致。
// 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> 组件进行 Web 渲染。
| 规则 | 文件 | 关键模式 |
|---|---|---|
| React 渲染器 | rules/react-renderer.md | <Renderer> 组件、流式传输、错误边界 |
PDF 和电子邮件渲染器
在服务端渲染为 PDF 缓冲区 / 文件和 HTML 电子邮件字符串。
| 规则 | 文件 | 关键模式 |
|---|---|---|
| PDF 和电子邮件 | rules/pdf-email-renderer.md | renderToBuffer、renderToFile、renderToHtml |
视频和图片渲染器
Remotion 合成和 Satori 图片生成。
| 规则 | 文件 | 关键模式 |
|---|---|---|
| 视频和图片 | rules/video-image-renderer.md | 渲染器(Remotion)、renderToPng、renderToSvg |
Registry Mapping
为共享目录创建平台专属注册表。
| Rule | File | Key Pattern |
|---|---|---|
| 注册表映射 | rules/registry-mapping.md | 每个平台的注册表、类型安全映射 |
关键决策
| Decision | Recommendation |
|---|---|
| PDF 库 | 使用 @json-render/react-pdf(react-pdf),而不是 Puppeteer 截图 |
| 邮件渲染 | 使用 @json-render/react-email(react-email),而不是 MJML 或自定义 HTML |
| OG 图片 | 使用 @json-render/image(Satori),而不是 Puppeteer 或 canvas |
| 视频 | 使用 @json-render/remotion(Remotion),而不是 FFmpeg 脚本 |
| 每个平台一个注册表 | 始终使用独立的注册表;切勿为所有界面共用一个注册表 |
| 目录共享 | 通过导入,在所有注册表之间共享同一个目录定义 |
常见错误
- 为每个界面构建单独的组件树——这违背了初衷;应共享目录和规范
- 使用 Puppeteer 截取 React 页面来生成 PDF——速度慢且脆弱;应使用原生 react-pdf 渲染
- 使用一个庞大的注册表覆盖所有平台——这不可行,因为 PDF 使用
<View>/<Text>,而 Web 使用<div>/<span> - 忽略 Satori 的限制——不支持 CSS grid,flexbox 支持有限;设计图片注册表时应考虑这些限制
- 为每个界面重复定义目录——一个目录,多个注册表;目录就是契约
相关技能
ork:json-render-catalog— 使用 Zod 和 shadcn 组件定义目录的模式ork:demo-producer— 使用 Remotion 的视频制作流水线ork:mcp-visual-output— 在 Claude/Cursor 中通过 MCP 渲染规范