Built with v0?
Your v0 app looks ready to ship. The part that breaks is the part you can't see.
v0 is the strongest of the AI builders at the part you can see. It generates Next.js with shadcn/ui components, and the React it writes stands up to professional review; plenty of teams use it for exactly that. The gap is behind the screens: data that persists, server actions that check who is calling, sign-in the server enforces, environment variables that exist outside v0's workspace. A demo exercises none of that, which is why the app that impressed everyone on Friday cannot take a real customer on Monday.
Fixed £4,500 · 3 days · pass-or-refund · next slot w/c 3 August
What actually breaks
- The screens can be finished while the data is still mockWhether v0 wired a database depends on how the app was prompted. It can connect Neon or Supabase in one click, but a preview that looks complete does not prove it did. Lists, dashboards and tables often render from arrays written directly into the component file, or from state that lives only in the browser. Refresh the page and the new record is gone, because nothing ever wrote to a database. Every one of those screens has to be re-plumbed from props and useState to real queries.
- Server actions with no check on who is callingEvery server action your UI actually calls is reachable by direct POST request, not just through your buttons; Next.js only strips out actions nothing references. It blocks cross-site requests by comparing origin headers, but it does not check identity or permissions, and its own docs tell you to verify both inside every action. v0 wires the action to the button and stops there. Anyone who can send a POST can call it directly, with any arguments, without ever opening your UI.
- Route handlers that return whatever id you ask forGenerated API routes fetch by the id in the URL, because that is what the page needed. The clause that goes missing is the one tying the row to the signed-in user. When the database is plain Postgres reached from server code, the query is the entire authorisation layer; there is no row level security behind it unless someone wrote it. Change the id in the request and you are reading another customer's order.
- A sign-in page is not an auth systemv0 produces a polished login form on request. What that form protects depends entirely on what enforces it, and a redirect that runs in the browser is not enforcement. Real protection lives server side: in middleware, and in every action and route that touches data, checked on each request. The form is the easy tenth of the job, and v0 did that tenth well. The other nine tenths never show up in a demo, which is exactly why they get skipped.
- State that survives the demo but not the deployA common pattern in generated code is a module-level array in a route handler standing in for a database. In local dev it works, because one long-lived process serves every request. On Vercel your code runs as serverless functions: instances multiply, get recycled and share nothing, so data written this way appears, vanishes, or differs between two requests seconds apart. Files written to disk go the same way. It is the classic works-locally failure.
- Environment variables that only exist inside v0Your keys live in v0's project settings, synced with the Vercel project v0 created, not in the repository it pushed. A clean clone on your machine, or a fresh Vercel project of your own, starts with none of them; the build fails or runs against nothing, and nowhere is there a written list of what was set. When you recreate them, one prefix matters: anything named NEXT_PUBLIC_ is compiled into the client JavaScript at build time, so no secret can safely carry it.
How I work in a v0 project
I work in the repository, not in the v0 chat. v0 syncs that chat to a private GitHub repository you own; that repository is the source of truth, and v0 keeps no separate copy of the code. v0 works on its own branches and opens pull requests; it never pushes to main. I work the same way: a branch, then a pull request you can read before it merges. While the sprint runs, v0's pull requests wait unmerged; if something needs changing mid-sprint, route it through me so main only moves in one direction. Once I hand back, you carry on prompting v0 against the fixed codebase.
What “done” looks like
We agree one of these in writing before you pay anything. If it passes, the sprint passed. If it doesn't, you get a full refund and keep the work.
- A record created through the live app is still there after a refresh, a sign-out and a redeploy, because it was written to a database rather than held in component state.
- Every server action and route handler refuses a request signed in as the wrong user, and the proof is a scripted POST that never touches your UI.
- A signed-out visitor who types the URL of a protected page is turned away in middleware, before the page renders, not after a flash of content.
- Your repository builds and deploys from a clean clone with a documented list of environment variables, and nothing secret carries a NEXT_PUBLIC_ prefix.
We agree the acceptance test before any work starts. If it doesn't pass within three working days, you get a full refund and keep every change I've committed. The risk is mine, not yours.
Other things that block launches
- Your Lovable app works in the preview. It breaks in production.
- Stripe took the money. Your customer got nothing.
- Your Bolt.new app runs fine in the preview. The published version doesn't.
- Your Replit app works in the workspace. The published version doesn't.
- Your Base44 app works when you use it. It breaks when your customers do.
- Your Cursor app runs on your machine. That is the only place it has ever been tested.
- Your customers can see each other's data. It is never just the one screen you found.
- Users can't sign in. It worked every time you tested it.
- It runs on your laptop. It will not deploy anywhere else.
- Your AI feature is impressive when it works. You can't put it in front of a customer.
- The full The Launch-Blocker Sprint