Cover image for Why I built a 12-route React app in one day, then threw it away

Why I built a 12-route React app in one day, then threw it away


June 02, 2026

Cover image for Why I built a 12-route React app in one day, then threw it away
Hero: a polished web dashboard rendered with a "single-use" treatment, a dashed cut line running through it, signaling something built to be discarded

Last week I built a twelve-route web app in an afternoon. A home dashboard, patient lists, a session form, a handful of report screens, a settings page. It worked. You could click through all of it. Then I deleted it, on purpose, and I'd decided to delete it before I wrote the first line.

I'm rebuilding a tool for a friend who runs a private therapy practice. She tracks her patients and payments in a spreadsheet that has worked for her for years, and I'm trying to replace it with something better. For the last several weeks I'd been validating the design with her one screen at a time. She reacts, I adjust, we move to the next screen. By the sixth session, every individual screen had passed.

And I still didn't know if the thing worked.

Six screens that pass, and still no app

A screen is not an app. A screen is a photograph. An app is the path you walk between the photographs, and that path is where things actually break.

I could show her a patient profile and she'd say yes, that's clear. I could show her the session form and she'd say yes, that makes sense. What I couldn't show her was the moment that matters: a payment confirmation lands on her phone, she opens the app, and she has to get from wherever she is to the right session and mark it paid. How many clicks is that? Does registering a new patient drop her somewhere sensible, or somewhere she has to claw her way out of? Where does the flow stall?

You cannot answer those questions with static screens. You need something the person can actually navigate, with state that carries from one screen to the next, so that confirming a payment on one screen changes what she sees on another. I needed a real, clickable, stateful app.

Here's the trap I almost walked into. The obvious move is to just start the production app. I'm going to build it anyway, the thinking goes, so why not build the real thing and let her test on that?

Because the real app makes decisions I hadn't earned yet. Starting it means choosing a database, an auth model, a backend, a deployment target, and committing real code to all of them. If I do that now, before I know the flow even holds together, I'm baking unvalidated architecture into the foundation of the thing. The production build should start from answers, not from a guess I'm afraid to delete.

So I deliberately built something I would never ship.

So I built a fake one

A throwaway. Vite and React, hardcoded fake data behind every screen, no tests, no login, no backend, no error handling. Desktop only. The whole point was to make the flow real enough to click through and nothing more.

Twelve routes: a home dashboard, the patient roster, a patient profile, a new-patient intake form, a session detail page, a session registration form, a reports hub with four report views under it, and a settings screen. Around eight thousand five hundred lines of code. One afternoon.

The discipline that makes this safe is treating the folder as radioactive. Its README opens with one rule: if you catch yourself wanting to "do it properly this time" in here, add tests, wire a real database, set up auth, stop. That itch is the signal that you're about to quietly commit to architecture you haven't validated. The throwaway stays a throwaway. The real app gets its own folder, its own architecture pass, and its own first commit, later, when the flow has earned it.

That sounds like a strange thing to be strict about. It's the most important rule in the whole exercise. The value of a throwaway prototype evaporates the moment you start treating it like production, because then you can't bring yourself to throw it away, and now you're shipping unvalidated decisions you only made because deleting the code felt wasteful.

How one afternoon produced twelve routes

I didn't build it in one long sitting with one AI agent. That approach drowns fast: a single chat running five hours of implementation fills up with its own output and starts losing the thread.

I split it instead. One chat acted as the orchestrator. It held the specs, the design, the plan, and the full picture of what the app needed to be. It did not write application code. It wrote prompts. Each prompt was a self-contained brief for one slice of the work: scaffold the project, build the dashboard, port the patient screens, port the session screens, build the report screens, build the settings page.

Each brief went to a fresh executor chat that opened cold, read the files the brief pointed it at, built that one slice, committed its own work, and reported back: here's the commit, here's a screenshot, here are the two decisions I made that you should look at. The orchestrator reviewed each report, decided what came next, and wrote the next brief. It never dropped into the weeds of any single screen.

Diagram: an orchestrator chat at the top holding specs and design, writing self-contained briefs down to several executor chats, each of which reads files, builds one slice, commits, and reports a commit plus screenshots back up

The build came out as ten commits across the afternoon, each one a slice that stands on its own. One of them wasn't a feature at all. After I reviewed the pending-payments screen, I sent it back: the list should sort with the oldest debt on top, not the newest. That correction landed as its own commit. The review loop was doing its job; the orchestrator caught something and the next slice fixed it.

Timeline: ten commits laid out left to right across one afternoon, from "scaffold" at roughly 2:30pm to "settings, the last screen" at roughly 7:30pm, each node a build phase, with the totals 12 routes, around 8,500 lines, 10 commits, one afternoon along the bottom

Why split the work this way instead of just letting one agent grind through it? Context economy. The orchestrator stays light because it never holds the implementation details of any one screen, only the briefs and the reports. The executors start clean every time, so none of them is dragging five hours of accumulated context behind it. The whole thing moves faster and stays clearer than one chat ever could.

The one rule that makes it work: each executor owns its slice end to end, including the commit. The parent reviews the diff, not the keystrokes. If you've ever run a team of contractors, the shape is familiar. You don't watch them type. You hand each one a scoped piece, you review what comes back, you decide what's next.

A quick honesty note, because "twelve routes in an afternoon" is the kind of number that gets misread. The speed came from the disposability, not from a shortcut I'd reach for on production code. None of it was real: no tests, no auth, no database, no backend, just fake data hardcoded behind every screen. It was fast precisely because it was built to be thrown away.

Then I deleted it

The prototype did its job. She sat down and worked through real tasks on it, navigating the flow the way she actually would, and I watched where it snagged and where it didn't. That's the entire reason it existed. Once I had that, the code had nothing left to give me.

So the production build starts fresh, in its own folder. Not all of it gets thrown out, though, and the split is the interesting part.

What ports forward is the visual layer. The screens she reacted to, the layouts, the components, the spacing, the way information is arranged on the page. Those decisions were validated by a real person doing real tasks, and they carry into the production app as the reference for how it should look and behave.

What gets deleted is everything underneath. The state store, the routing, the build setup, the fake data, the whole skeleton holding the pixels up. None of that was ever meant to survive. It was scaffolding, in the literal sense: the temporary structure you stand up to build the real thing, and take down once the real thing can stand on its own.

Comparison: two columns. The left column, "Ports forward," lists the validated visual layer, screen layouts, components, information hierarchy. The right column, "Deleted," lists the state store, routing, build setup, and hardcoded data, labeled as scaffolding

That's the reframe that took me a second to see clearly. The app was never the thing I was making. The pixels were the asset, and the code was a stand I built to hold them up long enough for someone to react to them.

What I keep running into

A few years ago, building twelve screens just to test how they connect, and then throwing them out the same day, would have been indefensible. The code cost too much to treat as scratch paper. You'd protect that investment, you'd find a way to reuse it, and in protecting it you'd carry forward every premature decision baked into it.

That cost is mostly gone now. The code was cheap enough to write in an afternoon and cheap enough to delete by evening, which means I could use it the way you're supposed to use a prototype: build it to answer a question, then throw it away once it's answered. The thing I was actually making was the understanding of what to build and the confidence that it works for the person who'll use it every day. The code was just how I got there.

I'm not calling that a rule. I just keep running into it, in a different shape, at every phase of this project. This was the bluntest version yet: I built a whole app, and the most valuable thing it produced was the permission to delete it.


#BuildInPublic #ClaudeCode #AIEngineering #AIAgenticEngineer