← all posts
engineering2026-04-29 · Orion Jones

How Lander's Claude builder turns a description into a live deploy

Architecture deep-dive: the two-pane builder, click-to-edit, parsing Claude's response into Dockerfile + files + CMD, and shipping to Fargate without a round-trip.

When you type "build me a portfolio for a designer named Sarah" in Lander's Pro plan builder, this happens:

  1. The text + previous turns go to /api/build/generate as a structured chat history
  2. Claude Sonnet 4.6 runs with a custom system prompt that constrains output to a strict XML structure
  3. The response is parsed into {plan, files, run}, a deployable unit
  4. The files preview-render in an iframe; the chat shows Claude's plan
  5. When you click "Deploy," Lander packages the files into a tarball, kicks off CodeBuild, and rolls out to a fresh Fargate task

This post is the architecture under that flow.

The system prompt

Claude is great at code generation but you have to constrain output sharply or you get conversational filler that breaks the parser. The Lander system prompt looks like:

You are a senior full-stack engineer building a production-ready, deployable
web app for a Lander customer.

Lander hosts the result on AWS Fargate behind an HTTPS subdomain. Containers
must listen on port 80.

Output strictly the following XML structure, in order, with no commentary
outside the tags:

<plan>
A 2-4 sentence plain-English plan of what you're going to build.
</plan>

<files>
  <file path="Dockerfile">
  ...
  </file>
  <file path="package.json">
  ...
  </file>
  ...
</files>

<run>
A 1-line command Lander uses to run inside the container, OR
"see Dockerfile CMD" if the Dockerfile already specifies it.
</run>

The strict schema means we can pass partial responses through a streaming parser and update the preview as Claude generates each file.

Click-to-edit

The right-pane preview is a sandboxed iframe of the rendered site. To enable click-to-edit:

  1. We inject a small JS shim that captures click events, walks the DOM up to the nearest semantic element (button, link, heading, image), and computes a CSS selector + a 50-char snippet of text.
  2. On click, we open a small input field next to the cursor: "What should this become?"
  3. The user types their edit ("make this red", "change the button text to Subscribe"), and we send a refine call to Claude with the selector + snippet + new instruction.
  4. Claude returns a patch, usually 10-50 lines of changed file content, and the preview updates without re-rendering the whole site.

This is the core differentiation vs v0.dev. v0 generates components in isolation; Lander's builder modifies your live site in place.

Why Sonnet 4.6 and not Opus 4.7

Cost. Sonnet 4.6 is ~6x cheaper than Opus 4.7 on input/output tokens. We benchmarked code-generation quality on 50 internal samples. The difference was within the noise of grader judgment. For full-stack web app generation, Sonnet is enough.

The cost saving lets us include 20 turns/mo on $75 Pro instead of 5. That's the difference between "you'd use this maybe twice" and "you'd actually use this for real projects."

The deploy step

When the user clicks "Deploy," we:

  1. POST the files to /api/build/deploy with a slug + name
  2. Server creates a new env row in Postgres + a fresh CDK stack (or reuses an existing one)
  3. Files get tarred + uploaded to a Lander-managed S3 bucket
  4. CodeBuild starts a build with SOURCE=s3://lander-uploads/<tarball> instead of the customer's GitHub repo
  5. Build runs the standard Dockerfile pipeline: clone (S3 instead of git), docker build, push to ECR, update task definition
  6. ECS rolls out the new task; user sees a live URL in 4-6 minutes

The whole thing fits in ~600 lines of Builder.tsx + the API route. Most of the complexity is in the system prompt and the streaming parser, not the deploy pipeline.

Open questions

A few things I still don't have great answers for:

  • Multi-file edits: when the user says "add a contact form," Claude needs to update the Dockerfile (maybe a server library), package.json, and the page itself. Sometimes Claude does this right; sometimes it forgets one file.
  • Long-context drift: by turn 15, Claude starts forgetting the original design language. We're experimenting with summarization checkpoints to compress the chat history.
  • Stuck states: when Claude introduces a bug, the user sees a broken preview and has to navigate "did I do this or did Claude?". A diff view would help.

If you have ideas on any of these, ping hello@lander.host.


Try the builder on Lander Pro: $75/mo, 20 turns included.

Stop reading. Start shipping.

free plan · no credit card · commercial use OK$ deploy now → lander.host