vilow.dev · Integrations · v0 (Vercel)

Add Vilow to a v0 app

v0 generates Next.js + React. Use a Server Action (or a Route Handler) to keep the Vilow API key on the server and return only the reply to the browser. Deploys to Vercel; their env-var system is the natural place for the key.

  1. Get a Vilow API key

    vilow.dev/dashboardAPI keys → Create. Copy the ck_live_… string.

  2. Save the key in Vercel project settings

    In your Vercel dashboard for this project: Settings → Environment Variables → Add:

    VILOW_API_KEY = ck_live_...
    VILOW_API_BASE = https://api.vilow.dev/v1

    Apply to Production + Preview (so v0's preview deploys get the key too) + Development (for vercel dev).

  3. Add a Server Action

    Ask v0: "Add a Server Action chatWithVilow in app/actions.ts that POSTs to https://api.vilow.dev/v1/chat/{user}/{character}/send with the VILOW_API_KEY environment variable."

    If you'd rather paste:

    // app/actions.ts
    "use server";
    
    const KEY = process.env.VILOW_API_KEY!;
    const API = process.env.VILOW_API_BASE ?? "https://api.vilow.dev/v1";
    
    export async function chatWithVilow(opts: {
      user_id: string;
      character_id?: number | null;
      message: string;
    }) {
      let cid = opts.character_id;
      if (!cid) {
        await fetch(`${API}/users`, {
          method: "POST",
          headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
          body: JSON.stringify({ external_id: opts.user_id, display_name: "v0 visitor" }),
        });
        const r = await fetch(`${API}/users/${opts.user_id}/characters`, {
          method: "POST",
          headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
          body: JSON.stringify({ name: "Aria", default_language: "auto" }),
        });
        cid = (await r.json()).id;
      }
      const chat = await fetch(`${API}/chat/${opts.user_id}/${cid}/send`, {
        method: "POST",
        headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
        body: JSON.stringify({ message: opts.message }),
        cache: "no-store",
      });
      const data = await chat.json();
      return { ...data, character_id: cid };
    }
  4. Use the action from a Client Component

    "use client";
    import { chatWithVilow } from "./actions";
    import { useState, useMemo } from "react";
    
    export default function Chat() {
      const [text, setText] = useState("");
      const [reply, setReply] = useState("");
      const [cid, setCid] = useState<number | null>(null);
      const uid = useMemo(() => {
        const k = "vilow_uid";
        let u = typeof window !== "undefined" ? localStorage.getItem(k) : null;
        if (!u) { u = "u_" + crypto.randomUUID(); localStorage.setItem(k, u); }
        return u;
      }, []);
    
      async function send() {
        const r = await chatWithVilow({ user_id: uid, character_id: cid, message: text });
        if (r.character_id) setCid(r.character_id);
        setReply(r.reply);
        setText("");
      }
      return (
        <div>
          <input value={text} onChange={(e) => setText(e.target.value)} />
          <button onClick={send}>Send</button>
          <p>{reply}</p>
        </div>
      );
    }
  5. Test on the v0 preview URL

    v0's previews live on v0.dev and Vercel's *.vercel.app. Both are CORS-allowed by Vilow out of the box (the request actually goes server-side anyway). Send a message — you should get a stateful reply that remembers across page reloads thanks to localStorage vilow_uid + character_id.

  6. Push to production

    Click Deploy in v0 → Vercel will build with the env vars you set. Custom domain in Vercel Settings → Domains.

Common gotchas