Recipe

Recipe: v0-like component generator

Build a prompt-to-component pipeline that outputs production-ready Tailwind + React code, styled in Meridian violet and pink.

Overview

This recipe wires an LLM endpoint to a streaming UI that renders generated JSX in a live preview. The pipeline accepts a natural-language prompt, enforces Meridian design tokens, and returns a complete functional component. No shadcn/ui, no Radix — just Tailwind and native HTML.

Ingredients

  • Next.js 14 App Router with a server action or API route for LLM calls
  • Streaming response parser (ReadableStream + TextDecoder)
  • Sandboxed iframe or dynamic import for preview rendering
  • System prompt enforcing violet #8B5CF6, pink #F472B6, dark #0A0612

System Prompt

You are a React component generator for Meridian.
Output ONLY valid JSX with Tailwind classes.
Design tokens:
  - Background: bg-[#0A0612]
  - Primary: #8B5CF6 (violet)
  - Accent: #F472B6 (pink)
  - Text: text-white / text-gray-400
  - Borders: border-[#8B5CF6]/20
  - Cards: rounded-lg, subtle border
Do NOT import any UI library. Use native HTML
elements styled with Tailwind. Return a single
default-export functional component.

Client Component Shell

'use client';

import { useState } from 'react';

export default function Generator() {
  const [prompt, setPrompt] = useState('');
  const [code, setCode] = useState('');

  async function generate() {
    const res = await fetch('/api/generate', {
      method: 'POST',
      body: JSON.stringify({ prompt }),
    });
    const reader = res.body.getReader();
    const decoder = new TextDecoder();
    let result = '';
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      result += decoder.decode(value, { stream: true });
      setCode(result);
    }
  }

  return (
    <div className="space-y-4">
      <textarea
        value={prompt}
        onChange={(e) => setPrompt(e.target.value)}
        className="w-full rounded-lg border border-[#8B5CF6]/20
                   bg-[#0A0612] p-4 text-white"
        rows={4}
        placeholder="A pricing card with three tiers..."
      />
      <button
        onClick={generate}
        className="rounded-lg bg-[#8B5CF6] px-6 py-2
                   font-medium text-white hover:bg-[#7A4DE0]"
      >
        Generate
      </button>
      {code && (
        <pre className="overflow-x-auto rounded-lg border
                        border-[#8B5CF6]/20 bg-[#0A0612] p-6
                        text-sm text-gray-300">
          <code>{code}</code>
        </pre>
      )}
    </div>
  );
}

API Route

// app/api/generate/route.ts
export async function POST(req: Request) {
  const { prompt } = await req.json();

  const response = await fetch('https://api.openai.com/v1/chat/completions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
    },
    body: JSON.stringify({
      model: 'gpt-4o',
      stream: true,
      messages: [
        { role: 'system', content: SYSTEM_PROMPT },
        { role: 'user', content: prompt },
      ],
    }),
  });

  return new Response(response.body, {
    headers: { 'Content-Type': 'text/plain; charset=utf-8' },
  });
}

Notes

  • • Stream the response so the user sees code appear token-by-token.
  • • Validate generated code before rendering — strip markdown fences and imports.
  • • For production, add rate limiting and prompt sanitization.
  • • The preview iframe should use a separate origin to prevent XSS.