multi-surface-render

$npx mdskill add yonatangross/orchestkit/multi-surface-render

Renders multiple output formats from a single JSON spec, ideal for generating cross-platform content like PDFs and videos.

  • Helps generate content for various platforms from one component specification.
  • Integrates with json-render, React, PDF libraries, email services, and Remotion.
  • Uses registry mapping and target selection to choose appropriate renderers.
  • Delivers results via platform-specific APIs like renderToBuffer or renderToFile.

SKILL.md

.github/skills/multi-surface-renderView on GitHub ↗
---
name: multi-surface-render
description: "Multi-surface rendering with json-render — same JSON spec produces React components, PDFs, emails, Remotion videos, OG images, and more. Covers renderer target selection, registry mapping, and platform-specific APIs (renderToBuffer, renderToStream, renderToFile). Use when generating output for multiple platforms, creating PDF reports, email templates, demo videos, or social media images from a single component spec."
tags: [json-render, multi-surface, pdf, email, remotion, video, image, react, rendering]
version: 1.0.0
author: OrchestKit
user-invocable: false
disable-model-invocation: false
complexity: medium
context: inherit
persuasion-type: reference
metadata:
  category: frontend
  upstream-package: "@json-render/core"
  upstream-version-tested: "0.13.0"
---

# Multi-Surface Rendering with json-render

Define once, render everywhere. A single json-render catalog and spec can produce React web UIs, PDF reports, HTML emails, Remotion demo videos, and OG images — each surface gets its own registry that maps catalog types to platform-native components.

## Quick Reference

| Category | Rules | Impact | When to Use |
|----------|-------|--------|-------------|
| [Target Selection](#target-selection) | 1 | HIGH | Choosing which renderer for your use case |
| [React Renderer](#react-renderer) | 1 | MEDIUM | Web apps, SPAs, dashboards |
| [PDF & Email Renderer](#pdf--email-renderer) | 1 | HIGH | Reports, documents, notifications |
| [Video & Image Renderer](#video--image-renderer) | 1 | MEDIUM | Demo videos, OG images, social cards |
| [Registry Mapping](#registry-mapping) | 1 | HIGH | Platform-specific component implementations |

**Total: 5 rules across 5 categories**

## How Multi-Surface Rendering Works

1. **One catalog** — Zod-typed component definitions shared across all surfaces
2. **One spec** — flat-tree JSON/YAML describing the UI structure
3. **Many registries** — each surface maps catalog types to its own component implementations
4. **Many renderers** — each package renders the spec using its registry

The catalog is the contract. The spec is the data. The registry is the platform-specific implementation.

## Quick Start — Same Catalog, Different Renderers

### Shared Catalog (used by all surfaces)

```typescript
import { defineCatalog } from '@json-render/core'
import { z } from 'zod'

export const catalog = defineCatalog({
  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,
  },
})
```

### Render to Web (React)

```tsx
import { Renderer } from '@json-render/react'
import { catalog } from './catalog'
import { webRegistry } from './registries/web'

export const Dashboard = ({ spec }) => (
  <Renderer spec={spec} catalog={catalog} registry={webRegistry} />
)
```

### Render to PDF

```typescript
import { renderToBuffer, renderToFile } from '@json-render/react-pdf'
import { catalog } from './catalog'
import { pdfRegistry } from './registries/pdf'

// Buffer for HTTP response
const buffer = await renderToBuffer(spec, { catalog, registry: pdfRegistry })

// Direct file output
await renderToFile(spec, './output/report.pdf', { catalog, registry: pdfRegistry })
```

### Render to Email

```typescript
import { renderToHtml } from '@json-render/react-email'
import { catalog } from './catalog'
import { emailRegistry } from './registries/email'

const html = await renderToHtml(spec, { catalog, registry: emailRegistry })
await sendEmail({ to: user.email, subject: 'Weekly Report', html })
```

### Render to OG Image (Satori)

```typescript
import { renderToSvg, renderToPng } from '@json-render/image'
import { catalog } from './catalog'
import { imageRegistry } from './registries/image'

const png = await renderToPng(spec, {
  catalog,
  registry: imageRegistry,
  width: 1200,
  height: 630,
})
```

### Render to Video (Remotion)

```tsx
import { JsonRenderComposition } from '@json-render/remotion'
import { catalog } from './catalog'
import { remotionRegistry } from './registries/remotion'

export const DemoVideo = () => (
  <JsonRenderComposition
    spec={spec}
    catalog={catalog}
    registry={remotionRegistry}
    fps={30}
    durationInFrames={150}
  />
)
```

## Decision Matrix — When to Use Each Target

| Target | Package | When to Use | Output |
|--------|---------|-------------|--------|
| React | `@json-render/react` | Web apps, SPAs | JSX |
| Vue | `@json-render/vue` | Vue projects | Vue components |
| Svelte | `@json-render/svelte` | Svelte projects | Svelte components |
| React Native | `@json-render/react-native` | Mobile apps (25+ components) | Native views |
| PDF | `@json-render/react-pdf` | Reports, documents | PDF buffer/file |
| Email | `@json-render/react-email` | Notifications, digests | HTML string |
| Remotion | `@json-render/remotion` | Demo videos, marketing | MP4/WebM |
| Image | `@json-render/image` | OG images, social cards | SVG/PNG (Satori) |
| YAML | `@json-render/yaml` | Token optimization | YAML string |
| MCP | `@json-render/mcp` | Claude/Cursor conversations | Sandboxed iframe |
| 3D | `@json-render/react-three-fiber` | 3D scenes (19 components) | Three.js canvas |
| Codegen | `@json-render/codegen` | Source code from specs | TypeScript/JSX |

Load `rules/target-selection.md` for detailed selection criteria and trade-offs.

## PDF Renderer — Reports and Documents

The `@json-render/react-pdf` package renders specs to PDF using react-pdf under the hood. Three output modes: buffer, file, and stream.

```typescript
import { renderToBuffer, renderToFile, renderToStream } from '@json-render/react-pdf'

// In-memory buffer (for HTTP responses, S3 upload)
const buffer = await renderToBuffer(spec, { catalog, registry: pdfRegistry })
res.setHeader('Content-Type', 'application/pdf')
res.send(buffer)

// Direct file write
await renderToFile(spec, './output/report.pdf', { catalog, registry: pdfRegistry })

// Streaming (for large documents)
const stream = await renderToStream(spec, { catalog, registry: pdfRegistry })
stream.pipe(res)
```

Load `rules/pdf-email-renderer.md` for PDF registry patterns and email rendering.

## Image Renderer — OG Images and Social Cards

The `@json-render/image` package uses Satori to convert specs to SVG, then optionally to PNG. Designed for server-side generation of social media images.

```typescript
import { renderToSvg, renderToPng } from '@json-render/image'

// SVG output (smaller, scalable)
const svg = await renderToSvg(spec, {
  catalog,
  registry: imageRegistry,
  width: 1200,
  height: 630,
})

// PNG output (universal compatibility)
const png = await renderToPng(spec, {
  catalog,
  registry: imageRegistry,
  width: 1200,
  height: 630,
})
```

Load `rules/video-image-renderer.md` for Satori constraints and Remotion composition patterns.

## Registry Mapping — Same Catalog, Platform-Specific Components

Each surface needs its own registry. The registry maps catalog types to platform-specific component implementations while the catalog and spec stay identical.

```typescript
// 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>
  ),
}
```

Load `rules/registry-mapping.md` for registry creation patterns and type safety.

## Rule Details

### Target Selection

Decision criteria for choosing the right renderer target.

| Rule | File | Key Pattern |
|------|------|-------------|
| Target Selection | `rules/target-selection.md` | Use case mapping, output format constraints |

### React Renderer

Web rendering with the `<Renderer>` component.

| Rule | File | Key Pattern |
|------|------|-------------|
| React Renderer | `rules/react-renderer.md` | `<Renderer>` component, streaming, error boundaries |

### PDF & Email Renderer

Server-side rendering to PDF buffers/files and HTML email strings.

| Rule | File | Key Pattern |
|------|------|-------------|
| PDF & Email | `rules/pdf-email-renderer.md` | renderToBuffer, renderToFile, renderToHtml |

### Video & Image Renderer

Remotion compositions and Satori image generation.

| Rule | File | Key Pattern |
|------|------|-------------|
| Video & Image | `rules/video-image-renderer.md` | JsonRenderComposition, renderToPng, renderToSvg |

### Registry Mapping

Creating platform-specific registries for a shared catalog.

| Rule | File | Key Pattern |
|------|------|-------------|
| Registry Mapping | `rules/registry-mapping.md` | Per-platform registries, type-safe mapping |

## Key Decisions

| Decision | Recommendation |
|----------|----------------|
| PDF library | Use `@json-render/react-pdf` (react-pdf), not Puppeteer screenshots |
| Email rendering | Use `@json-render/react-email` (react-email), not MJML or custom HTML |
| OG images | Use `@json-render/image` (Satori), not Puppeteer or canvas |
| Video | Use `@json-render/remotion` (Remotion), not FFmpeg scripts |
| Registry per platform | Always separate registries; never one registry for all surfaces |
| Catalog sharing | One catalog definition shared via import across all registries |

## Common Mistakes

1. Building separate component trees for each surface — defeats the purpose; share the catalog and spec
2. Using Puppeteer to screenshot React for PDF generation — slow, fragile; use native react-pdf rendering
3. One giant registry covering all platforms — impossible since PDF uses `<View>`/`<Text>`, web uses `<div>`/`<span>`
4. Forgetting Satori limitations — no CSS grid, limited flexbox; design image registries with these constraints
5. Duplicating catalog definitions per surface — one catalog, many registries; the catalog is the contract

## Related Skills

- `ork:json-render-catalog` — Catalog definition patterns with Zod, shadcn components
- `ork:demo-producer` — Video production pipeline using Remotion
- `ork:presentation-builder` — Slide deck generation
- `ork:mcp-visual-output` — Rendering specs in Claude/Cursor via MCP

More from yonatangross/orchestkit

SkillDescription
agent-orchestrationAgent orchestration patterns for agentic loops, multi-agent coordination, alternative frameworks, and multi-scenario workflows. Use when building autonomous agent loops, coordinating multiple agents, evaluating CrewAI/AutoGen/Swarm, or orchestrating complex multi-step scenarios.
ai-ui-generationAI-assisted UI generation patterns for json-render, v0, Bolt, and Cursor workflows. Covers prompt engineering for component generation, review checklists for AI-generated code, design token injection, refactoring for design system conformance, and CI gates for quality assurance. Use when generating UI components with AI tools, rendering multi-surface MCP visual output, reviewing AI-generated code, or integrating AI output into design systems.
analyticsQuery cross-project usage analytics. Use when reviewing agent, skill, hook, or team performance across OrchestKit projects. Also replay sessions, estimate costs, and view model delegation trends.
animation-motion-designAnimation and motion design patterns using Motion library (formerly Framer Motion) and View Transitions API. Use when implementing component animations, page transitions, micro-interactions, gesture-driven UIs, or ensuring motion accessibility with prefers-reduced-motion.
architecture-patternsArchitecture validation and patterns for clean architecture, backend structure enforcement, project structure validation, test standards, and context-aware sizing. Use when designing system boundaries, enforcing layered architecture, validating project structure, defining test standards, or choosing the right architecture tier for project scope.
ascii-visualizerASCII diagram patterns for architecture, workflows, file trees, and data visualizations. Use when creating terminal-rendered diagrams, box-drawing layouts, progress bars, swimlanes, or blast radius visualizations.
assessAssesses and rates quality 0-10 with pros/cons analysis. Use when evaluating code, designs, or approaches.
async-jobsAsync job processing patterns for background tasks, Celery workflows, task scheduling, retry strategies, and distributed task execution. Use when implementing background job processing, task queues, or scheduled task systems.
audit-fullFull-codebase audit using 1M context window. Security, architecture, and dependency analysis in a single pass. Use when you need whole-project analysis.
audit-skillsAudits all OrchestKit skills for quality, completeness, and compliance with authoring standards. Use when checking skill health, before releases, or after bulk skill edits to surface SKILL.md files that are too long, have missing frontmatter, lack rules/references, or are unregistered in manifests.