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

> **Source:** https://upstash.com/blog/build-your-own-lovable
> **Date:** 2026-10-15
> **Author(s):** Ali Tarık Şahin
> **Reading time:** 8 min read
> **Tags:** box, ai, agents, sandbox
> **Format:** text/markdown — machine-readable content for agents and LLMs

Build a Lovable-style AI app builder with a coding agent, live preview, versions, and forks. One Upstash Box per project, with real timings and costs.

---

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](https://upstash.com/docs/box/overall/how-it-works) 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.

![The builder: chat and versions on the left, live preview on the right](/blog/build-your-own-lovable/builder.png)

## 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](https://github.com/firecrawl/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](https://github.com/firecrawl/open-lovable/tree/806b8072f28afea11fa2b028cac1449eff74c511/app/api) 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:

```typescript
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](https://github.com/alitariksahin/box-builder/blob/57cacf7973d3a91ca94e1e45f7328f910a67902e/scripts/build-template.ts) 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

```typescript
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

```typescript
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:

```bash
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:

```typescript
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](/blog/build-your-own-lovable/home.png)

## 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](https://upstash.com/blog/ai-sandbox-pricing-active-cpu-vs-wall-clock). 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](https://upstash.com/pricing/box).

**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](https://upstash.com/docs/redis/sdks/ratelimit-ts/overview) 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](https://docs.lovable.dev/introduction/faq).

### 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](https://upstash.com/docs/box/overall/custom-agent).

## Try it

The full source is on GitHub: [alitariksahin/box-builder](https://github.com/alitariksahin/box-builder). New to Box? Start with the [quickstart](https://upstash.com/docs/box/overall/quickstart).

---

This site has a search endpoint: https://context7.com/api/v2/ask?siteKey=ask_4cf2adc7846aa874f833b068&query=<URL-encoded question>. It returns documentation that answers the question, with a source link for each part. No API key is needed. If nothing matches, it says so.