New:Upstash has a remote MCP server
·8 min read

How to Build Your Own Lovable: An AI App Builder on Upstash Box

Ali Tarık ŞahinAli Tarık ŞahinSoftware Engineer @Upstash
https://upstash.com/blog/build-your-own-lovable

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.

Summary
  • 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.

The builder: chat and versions on the left, live preview on the right

What an app builder needs, and what Box gives you

You needThe usual wayWith Upstash Box
A coding agentRun an agent loop on your server and choose which files to send to the modelbox.agent.stream(). Claude Code, Codex, or OpenCode runs inside the box
A safe place to run the codeA sandbox, plus code to copy every edited file into itThe box is the sandbox
Live previewStart a dev server and route a URL to itbox.getPublicURL(5173)
Fast project startInstall dependencies for every projectBox.fromSnapshot() from a ready template
Versions and undoSave file trees in a databasegit commit inside the box
Fork an appCopy files to a new projectbox.snapshot() + Box.fromSnapshot()
Projects that last for weeksKeep sandboxes running, or rebuild themBoxes pause when idle and wake on the preview URL
Chat memorySave and replay the chatThe 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"], and hmr: { 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 in app/, 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.

The home page with recent projects

Numbers from our runs

All runs used a small box (2 vCPU, 4 GB) and Claude Sonnet 5.

StepTime
Build the template (once)36 s
Create a project8 to 15 s
First prompt (a small app)36 to 49 s
Follow-up prompt19 to 43 s
Wake a paused project1.6 to 2.1 s
Restore a version3 s
Fork a project23 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.