14k

AI SDK Integration

Use json-render with the Vercel AI SDK for seamless streaming. json-render supports two modes: Standalone (standalone UI) and Inline (UI embedded in conversation). See Generation Modes for a detailed comparison.

Installation#

npm install ai @ai-sdk/react

Standalone Mode#

In standalone mode, the AI outputs only JSONL patches. The entire response is a UI spec with no prose. This is the default mode and is ideal for playgrounds, builders, and dashboard generators.

API Route#

// app/api/generate/route.ts
import { streamText } from "ai";
import { catalog } from "@/lib/catalog";

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

  const systemPrompt = catalog.prompt();

  // Optionally include current UI state for context
  const contextPrompt = currentTree
    ? `\n\nCurrent UI state:\n${JSON.stringify(currentTree, null, 2)}`
    : "";

  const result = streamText({
    model: yourModel,
    system: systemPrompt + contextPrompt,
    prompt,
  });

  return result.toTextStreamResponse();
}

Client#

Use useUIStream on the client to compile the JSONL stream into a spec:

"use client";

import { useUIStream, Renderer } from "@json-render/react";

function GenerativeUI() {
  const { spec, isStreaming, error, send } = useUIStream({
    api: "/api/generate",
  });

  return (
    <div>
      <button
        onClick={() => send("Create a dashboard with metrics")}
        disabled={isStreaming}
      >
        {isStreaming ? "Generating..." : "Generate"}
      </button>

      {error && <p className="text-red-500">{error.message}</p>}

      <Renderer spec={spec} registry={registry} loading={isStreaming} />
    </div>
  );
}

Inline Mode#

In inline mode, the AI responds conversationally and includes JSONL patches inline. Text-only replies are allowed when no UI is needed. This is ideal for chatbots, copilots, and educational assistants.

API Route#

Use pipeJsonRender to separate text from JSONL patches in the stream. Patches are emitted as data parts that the client can pick up.

// app/api/chat/route.ts
import { streamText } from "ai";
import { pipeJsonRender } from "@json-render/core";
import {
  createUIMessageStream,
  createUIMessageStreamResponse,
} from "ai";
import { catalog } from "@/lib/catalog";

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

  const result = streamText({
    model: yourModel,
    system: catalog.prompt({ mode: "inline" }),
    messages,
  });

  const stream = createUIMessageStream({
    execute: async ({ writer }) => {
      writer.merge(pipeJsonRender(result.toUIMessageStream()));
    },
  });

  return createUIMessageStreamResponse({ stream });
}

Client#

Use useChat from the AI SDK and useJsonRenderMessage from json-render to extract the spec from each message:

"use client";

import { useChat } from "@ai-sdk/react";
import { useJsonRenderMessage, Renderer } from "@json-render/react";

function Chat() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    api: "/api/chat",
  });

  return (
    <div>
      <div>
        {messages.map((msg) => (
          <ChatMessage key={msg.id} message={msg} />
        ))}
      </div>

      <form onSubmit={handleSubmit}>
        <input
          value={input}
          onChange={handleInputChange}
          placeholder="Ask something..."
        />
        <button type="submit">Send</button>
      </form>
    </div>
  );
}

function ChatMessage({ message }: { message: { parts: Array<{ type: string; text?: string; data?: unknown }> } }) {
  const { spec, text, hasSpec } = useJsonRenderMessage(message.parts);

  return (
    <div>
      {text && <p>{text}</p>}
      {hasSpec && spec && (
        <Renderer spec={spec} registry={registry} />
      )}
    </div>
  );
}

Prompt Engineering#

The catalog.prompt() method creates an optimized system prompt that:

  • Lists all available components and their props
  • Describes available actions
  • Specifies the expected output format (JSONL-only or text + JSONL depending on mode)
  • Includes examples for better generation

Custom Rules#

Pass custom rules to tailor AI behavior:

const systemPrompt = catalog.prompt({
  customRules: [
    "Always use Card components for grouping related content",
    "Prefer horizontal layouts (Row) for metrics",
    "Use consistent spacing with padding=\"md\"",
  ],
});

Inline Mode Prompt#

const inlinePrompt = catalog.prompt({ mode: "inline" });

In inline mode, the prompt instructs the AI to respond conversationally first, then include JSONL patches on their own lines when UI is needed. Text-only replies are allowed.

Which Mode?#

StandaloneInline
OutputJSONL onlyText + JSONL
Text-only repliesNoYes
System promptcatalog.prompt()catalog.prompt({ mode: "inline" })
Stream utilityuseUIStreampipeJsonRender + useJsonRenderMessage
Use casePlaygrounds, buildersChatbots, copilots

Learn more in the Generation Modes guide.

Next#