How to Build Your Own Lovable: An AI App Builder on Upstash Box
Lovable turns a sentence into a running web app. You describe an app, a coding agent writes the code, and a live preview updates next to the chat. You can close the tab and come back next week.
To build this, every project needs a coding agent, its code, a dev server for the preview, and a history of versions. Most projects also sit unused for days.
That is almost exactly what an Upstash Box is:
- A box is a computer with a coding agent already inside. The agent edits the files directly, so your server does not need to send files to a model and write the answers back.
- Any port can get a public URL, so the live preview is one call.
- A box pauses when idle and you pay only for active CPU. An unused project costs only storage, and opening its preview wakes it up.
- Snapshots make templates and forks easy.
So we built it: one box per project. Here is how it works, with the numbers from our test runs.
- One box per project holds the agent, the code, and the dev server. Nothing needs to be kept in sync.
- Opening the preview of a paused project wakes it in about 2 seconds.
- The whole app is about 950 lines of TypeScript, UI included. About 15 lines call the Box SDK.
- A 72-second prompt used 4 seconds of CPU, about $0.0001. The model tokens are the real cost.

What an app builder needs, and what Box gives you
| You need | The usual way | With Upstash Box |
|---|---|---|
| A coding agent | Run an agent loop on your server and choose which files to send to the model | box.agent.stream(). Claude Code, Codex, or OpenCode runs inside the box |
| A safe place to run the code | A sandbox, plus code to copy every edited file into it | The box is the sandbox |
| Live preview | Start a dev server and route a URL to it | box.getPublicURL(5173) |
| Fast project start | Install dependencies for every project | Box.fromSnapshot() from a ready template |
| Versions and undo | Save file trees in a database | git commit inside the box |
| Fork an app | Copy files to a new project | box.snapshot() + Box.fromSnapshot() |
| Projects that last for weeks | Keep sandboxes running, or rebuild them | Boxes pause when idle and wake on the preview URL |
| Chat memory | Save and replay the chat | The agent keeps its session in the box |
For comparison, Open Lovable, the popular open source clone, runs the model on its server. The server parses the generated files and writes them into a sandbox. Its 27 API routes alone are about 6,200 lines, some of them for its website scraping features. Most of that work disappears when the agent lives in the box.
How it fits together
Browser (chat + preview)
│
Next.js app ──── Upstash Redis: projects, chat history, locks, rate limits
│
One Upstash Box per project
├─ Vite + React + Tailwind app (a git repo)
├─ Claude Code
└─ dev server :5173 ──► public preview URL
The Next.js app only creates boxes, forwards prompts, and streams the answers. It never parses model output or copies generated files around.
Step 1: Make a template once
New Lovable projects use TanStack Start. We use React and Vite here because a plain Vite dev server is the simplest thing to preview. Installing it takes about a minute, so do it once and save a snapshot:
const box = await Box.create({ runtime: "node" })
await box.exec.command("cd /workspace/home && npx -y create-vite@latest app --template react-ts --no-interactive")
await box.exec.command("cd /workspace/home/app && npm install && npm install tailwindcss @tailwindcss/vite")
await box.files.write({ path: "/workspace/home/CLAUDE.md", content: rules })
await box.exec.command("cd /workspace/home/app && git init -q && git add -A && git commit -qm 'Starter'")
const template = await box.snapshot({ name: "vite-react-tailwind" })This is shortened. The full script also writes the Vite config, adds the Tailwind plugin and its CSS import, and creates a starter App.tsx.
Two tips:
- Let Vite accept the preview domain:
host: true,allowedHosts: [".preview.box.upstash.com"], andhmr: { clientPort: 443 }. Hot reload then works through the public URL. - Put the agent's rules in
/workspace/home/CLAUDE.md. The agent starts there, so it reads them automatically. Ours are short: the app is inapp/, the dev server is already running, use Tailwind, keep data in localStorage.
Step 2: Create a project
const box = await Box.fromSnapshot(template.id, {
agent: { harness: Agent.ClaudeCode, model: ClaudeCode.Sonnet_5, apiKey: process.env.ANTHROPIC_API_KEY },
initCommand: "cd /workspace/home/app && nohup npm run dev > /tmp/vite.log 2>&1 &",
})
const { url } = await box.getPublicURL(5173)initCommand runs every time the box starts or wakes up, so the dev server is always running when someone opens the preview. A new project was ready in 8 to 15 seconds.
Step 3: Send a prompt
const box = await Box.get(project.boxId)
const stream = await box.agent.stream({ prompt })
for await (const chunk of stream) {
if (chunk.type === "text-delta") send({ type: "text", text: chunk.text })
if (chunk.type === "tool-call") send({ type: "tool", label: toolLabel(chunk) }) // "Edited src/App.tsx"
}The route sends these chunks to the browser as server-sent events. The preview is an iframe on the public URL, and it updates while the agent is still working.
The agent remembers earlier prompts by itself. We told it a secret word in one prompt and asked for it in the next one. It answered correctly.
Step 4: Versions, restore, and fork
After each run that changes files, we save a commit and show its version on the assistant reply:
git add -A && git commit -qm "<prompt>"Restore puts the files back to an older commit with git read-tree -u --reset <sha> and commits that, so history only moves forward. If package.json or the lockfile changed, we run npm install. One trick: after a restore, the agent still remembers the newer code, so we add a short note to the next prompt: "The project was restored, read the files again."
Fork is a snapshot and a new box from it:
const snapshot = await box.snapshot({ name: "fork" })
const fork = await Box.fromSnapshot(snapshot.id, { agent, initCommand })Step 5: Come back tomorrow
There is no code for this step. An idle box pauses. When the user opens the project again, the preview URL wakes the box, initCommand starts the dev server, and the page loads. In our tests this took 1.6 to 2.1 seconds.

Numbers from our runs
All runs used a small box (2 vCPU, 4 GB) and Claude Sonnet 5.
| Step | Time |
|---|---|
| Build the template (once) | 36 s |
| Create a project | 8 to 15 s |
| First prompt (a small app) | 36 to 49 s |
| Follow-up prompt | 19 to 43 s |
| Wake a paused project | 1.6 to 2.1 s |
| Restore a version | 3 s |
| Fork a project | 23 s |
Cost. The agent spends most of its time waiting for the model, and Box bills only active CPU. One 72-second prompt used 3.98 CPU-seconds, about $0.0001. An idle project is about 100 MB of storage, about $0.01 per month. So 1,000 users with 30 projects each cost about $300 per month while idle.
Two limits: the Free plan allows 10 boxes, and paused boxes count, so a real product needs Pay as You Go. Pay as You Go allows 1,000 boxes by default. For 30,000 projects, ask for a higher quota. See pricing.
Redis stores what the UI needs without waking a box: the project list, chat history, a lock per project so two actions never run at once, and rate limits because every prompt costs tokens.
FAQ
How does Lovable work?
You describe an app in chat. A coding agent writes the project, runs it in a sandbox, and shows a live preview. Each change is saved as a version. New projects use TanStack Start, and older ones use React and Vite.
Can I build my own Lovable?
Yes. You need a coding agent, a sandbox, a dev server with a public URL, version history, and cheap storage for idle projects. With one Upstash Box per project, the whole app is about 950 lines of TypeScript, UI included.
What happens to a project when the user leaves?
The box pauses. Files, packages, git history, and the agent's memory stay on disk. Opening the preview wakes it in about 2 seconds.
Which coding agents can I use?
Claude Code, Codex, and OpenCode are built in. To switch, change the harness, the model, and the API key. The rest of the app stays the same. You can also bring your own agent.
Try it
The full source is on GitHub: alitariksahin/box-builder. New to Box? Start with the quickstart.
