Skip to main content

VibeFrame

VibeFrame renders AI-generated React components in a sandboxed iframe. It handles code compilation, data passing, auto-resizing, and error handling—all while maintaining strict security isolation.

Basic Usage

import { VibeFrame } from 'genkit/react'

function Preview() {
  const code = `
    export default function Widget({ data }) {
      return (
        <div className="p-4 bg-blue-50 rounded-lg">
          <h2 className="text-xl font-bold">{data.title}</h2>
          <p className="text-gray-600">{data.description}</p>
        </div>
      )
    }
  `

  return (
    <VibeFrame
      code={code}
      data={{ title: 'Hello', description: 'World' }}
    />
  )
}

Props

PropTypeDefaultDescription
codestring-React component code to render
idstring-Component ID (for saved components)
dataRecord<string, unknown>{}Data passed to the component as props
rendererUrlstring/api/vibe/renderBase URL for the render endpoint
classNamestring-CSS class for the container
minHeightnumber100Minimum iframe height in pixels
maxHeightnumber600Maximum iframe height in pixels
onRender() => void-Called when component renders successfully
onError(error: string) => void-Called when rendering fails
loadingFallbackReactNode-Custom loading UI
errorFallbackReactNode | ((error: string) => ReactNode)-Custom error UI

How It Works

1. Code Compilation

When you provide code, VibeFrame generates a data URL containing:
  • React 18 (loaded from CDN)
  • Babel standalone (for JSX compilation)
  • Tailwind CSS (loaded from CDN)
  • Your component code
// This code:
<VibeFrame code={myCode} />

// Becomes this data URL:
data:text/html;charset=utf-8,<!DOCTYPE html>...

2. PostMessage Bridge

The iframe and parent communicate via PostMessage:
Parent                          Iframe
  │                                │
  │  ───── VIBE_READY ──────────>  │  (iframe loaded)
  │                                │
  │  <──── VIBE_DATA ───────────   │  (parent sends data)
  │                                │
  │  ───── VIBE_RENDERED ───────>  │  (component rendered)
  │        { height: 240 }         │
  │                                │
  │  ───── VIBE_ERROR ──────────>  │  (if error occurs)
  │        { error: "..." }        │

3. Auto-Resize

The iframe automatically resizes based on content:
// Iframe sends its height after rendering
window.parent.postMessage({
  type: 'VIBE_RENDERED',
  height: document.body.scrollHeight
}, '*')
VibeFrame constrains this between minHeight and maxHeight.

Security Model

Sandbox Restrictions

<iframe sandbox="allow-scripts" />
This sandbox policy:
  • ✅ Allows JavaScript execution
  • ❌ Blocks same-origin access (can’t read parent DOM)
  • ❌ Blocks form submission
  • ❌ Blocks popups and new windows
  • ❌ Blocks top-level navigation
  • ❌ Blocks plugins

Code Validation

Before rendering, code is validated for dangerous patterns:
// These patterns are blocked:
eval()                // Dynamic execution
Function()            // Function constructor
new Function()        // Function constructor
import()              // Dynamic imports
require()             // CommonJS imports
process.              // Node.js globals
global.               // Node.js globals
window.               // Browser globals
document.             // DOM access
__proto__             // Prototype pollution
constructor[]         // Constructor access

Rendering Modes

Direct Code Rendering

Pass code directly for previews:
<VibeFrame
  code={generatedCode}
  data={previewData}
/>

Saved Component Rendering

Pass an ID to render saved components:
<VibeFrame
  id="component-123"
  data={liveData}
  rendererUrl="/api/vibe/render"
/>
This fetches the component from your render endpoint.

Custom Loading States

<VibeFrame
  code={code}
  data={data}
  loadingFallback={
    <div className="flex items-center gap-2">
      <Spinner />
      <span>Compiling component...</span>
    </div>
  }
  errorFallback={(error) => (
    <div className="text-red-500">
      <p>Failed to render: {error}</p>
      <button onClick={retry}>Try Again</button>
    </div>
  )}
/>

Event Handling

function Preview() {
  const [status, setStatus] = useState<'loading' | 'ready' | 'error'>('loading')
  const [error, setError] = useState<string | null>(null)

  return (
    <>
      <VibeFrame
        code={code}
        data={data}
        onRender={() => {
          setStatus('ready')
          analytics.track('component_rendered')
        }}
        onError={(err) => {
          setStatus('error')
          setError(err)
          analytics.track('component_error', { error: err })
        }}
      />

      {status === 'ready' && <Badge>Live</Badge>}
      {status === 'error' && <Alert>{error}</Alert>}
    </>
  )
}

Data Updates

Data is automatically sent when it changes:
function LiveDashboard() {
  const [deals, setDeals] = useState([])

  useEffect(() => {
    const unsubscribe = subscribeToDeals(setDeals)
    return unsubscribe
  }, [])

  // VibeFrame re-renders when deals change
  return (
    <VibeFrame
      code={dashboardCode}
      data={{ deals }}
    />
  )
}

API Endpoint Setup

For saved components, create a render endpoint:
// app/api/vibe/render/[id]/route.ts
import { NextRequest } from 'next/server'
import { vibeApi } from '@/lib/vibe'

export async function GET(
  request: NextRequest,
  { params }: { params: { id: string } }
) {
  const component = await vibeApi.load(params.id)

  if (!component) {
    return new Response('Not found', { status: 404 })
  }

  const html = generateRenderHtml(component.code)

  return new Response(html, {
    headers: { 'Content-Type': 'text/html' }
  })
}

Best Practices

1. Always Set Max Height

Prevent malicious components from expanding infinitely:
<VibeFrame
  code={untrustedCode}
  maxHeight={600}
/>

2. Validate Data Before Passing

Don’t pass sensitive data to generated components:
// ❌ Bad: Passing sensitive data
<VibeFrame data={{ user, apiKey, internalConfig }} />

// ✅ Good: Only pass display data
<VibeFrame data={{ userName: user.name, stats: publicStats }} />

3. Handle Errors Gracefully

Always provide error handling:
<VibeFrame
  code={code}
  onError={(error) => {
    toast.error('Component failed to render')
    logError(error)
  }}
  errorFallback={<ComponentErrorState />}
/>

4. Use Loading States

Show users something while compiling:
<VibeFrame
  code={code}
  loadingFallback={<Skeleton className="h-64" />}
/>