Streaming
Progressively render UI as AI generates it.
SpecStream Format
json-render uses SpecStream, a JSONL-based streaming format where each line is a JSON patch operation that progressively builds your spec:
{"op":"add","path":"/root","value":"root"}
{"op":"add","path":"/elements/root","value":{"type":"Card","props":{"title":"Dashboard"},"children":["metric-1","metric-2"]}}
{"op":"add","path":"/elements/metric-1","value":{"type":"Metric","props":{"label":"Revenue"}}}
{"op":"add","path":"/elements/metric-2","value":{"type":"Metric","props":{"label":"Users"}}}Patch Operations (RFC 6902)
SpecStream uses RFC 6902 JSON Patch operations:
add— Add a value at a path (creates or replaces for objects, inserts for arrays)remove— Remove the value at a pathreplace— Replace an existing value at a pathmove— Move a value from one path to another (requiresfrom)copy— Copy a value from one path to another (requiresfrom)test— Assert that a value at a path equals the given value
Path Format
Paths follow JSON Pointer (RFC 6901) into the spec object:
/root -> Root element key (string)
/elements/card-1 -> Element with key "card-1"
/elements/card-1/props -> Props of card-1
/elements/card-1/children -> Children of card-1Server-Side Setup
Ensure your API route streams properly:
import { streamText } from 'ai';
import { catalog } from '@/lib/catalog';
export async function POST(req: Request) {
const { prompt } = await req.json();
const result = streamText({
model: 'anthropic/claude-haiku-4.5',
system: catalog.prompt(),
prompt,
});
// Return as a streaming response
return result.toTextStreamResponse();
}Low-Level SpecStream API
For custom or framework-agnostic streaming implementations, use the SpecStream compiler from @json-render/core directly:
import { createSpecStreamCompiler } from '@json-render/core';
// Create a compiler for your spec type
const compiler = createSpecStreamCompiler<MySpec>();
const decoder = new TextDecoder();
// Process streaming chunks from AI
async function processStream(reader: ReadableStreamDefaultReader<Uint8Array>) {
while (true) {
const { done, value } = await reader.read();
if (done) break;
// Decode the Uint8Array chunk to a string
const chunk = decoder.decode(value, { stream: true });
const { result, newPatches } = compiler.push(chunk);
if (newPatches.length > 0) {
// Update UI with partial result
setSpec(result);
}
}
// Get final compiled result
return compiler.getResult();
}One-Shot Compilation
For non-streaming scenarios, compile entire SpecStream at once:
import { compileSpecStream } from '@json-render/core';
const jsonl = `{"op":"add","path":"/root","value":"card-1"}
{"op":"add","path":"/elements/card-1","value":{"type":"Card","props":{"title":"Hello"},"children":[]}}`;
const spec = compileSpecStream<Spec>(jsonl);
// { root: "card-1", elements: { "card-1": { type: "Card", props: { title: "Hello" }, children: [] } } }Usage with React
@json-render/react provides the useUIStream hook, which wraps the low-level compiler in a React-friendly API with state management, error handling, and abort support.
useUIStream Hook
import { useUIStream } from '@json-render/react';
function App() {
const {
spec, // Current UI spec state
isStreaming, // True while streaming
error, // Any error that occurred
send, // Function to start generation
clear, // Function to reset spec and error
} = useUIStream({
api: '/api/generate',
onComplete: (spec) => {}, // Optional: called when streaming completes
onError: (error) => {}, // Optional: called when an error occurs
});
}Progressive Rendering
The Renderer automatically updates as the spec changes:
function App() {
const { spec, isStreaming } = useUIStream({ api: '/api/generate' });
return (
<div>
{isStreaming && <LoadingIndicator />}
<Renderer spec={spec} registry={registry} loading={isStreaming} />
</div>
);
}Aborting Streams
Calling send again automatically aborts the previous request. Use clear to reset the spec and error state:
function App() {
const { isStreaming, send, clear } = useUIStream({
api: '/api/generate',
});
return (
<div>
<button onClick={() => send('Create dashboard')}>
Generate
</button>
<button onClick={clear}>Reset</button>
</div>
);
}See the @json-render/react API reference for full useUIStream documentation.