Skip to content
afterbuild.dev
Research12 min read

77 of 100 Lovable apps 404 on your own server

I pulled 100 public repositories each from Lovable, Replit and v0, and measured the same things in all three.

Measured 2026-08-03ScriptsRepublication terms
Contents

Every one of these tools shows you a working app in its own preview. That preview is not a web server, and the difference only becomes visible on the day you point your own domain at it.

I wanted to know how often that goes wrong, so I stopped guessing and counted. Three tools, one hundred public repositories each, the same handful of measurements. Everything below can be re-run from the commands at the end.

What breaks on your domain

A Vite single-page app is one HTML file plus a bundle of JavaScript. Routing happens in the browser: you click a link and the app rewrites the address bar without asking the server for anything. That works until someone asks the server directly — by reloading the page, or by opening a link you sent them.

Then the request for /pricing arrives at a host that has no file called pricing, no rule pointing it at index.html, and no reason to serve the app at all. A plain static host answers the only way it can.

yourapp.com/pricing
404
This page could not be found.
Notice the address bar: this is a page of the app, not a mistyped link. The same address works if you reach it from inside the app.
77of 100

Lovable projects contain no rewrite rule of any kind — no _redirects, no vercel.json, no netlify.toml, no nginx.conf, no .htaccess, no Dockerfile.

afterbuild.dev100 public Lovable repositories, read 2026-08-03

That number is about the repository, not about a live site — which matters, and I want to be exact about it. If you deploy to a platform that recognises a Vite app, the platform can add the rule for you and nothing breaks. The moment you deploy anywhere that does not — your own server, a plain object bucket, a shared host — there is nothing in the project that would stop the 404.

Of the 23 projects that did have a rule, 14 had it in vercel.json. That file is written by the platform, not by the person who built the app.

How I measured this

Only the GitHub API: repository search, the git trees API, and reading individual files. Nothing was cloned. No deployment was requested, no live site was touched, and no key found in any repository was used.

HARVEST
$ python3 harvest.py 100
search/repositories lovable-tagger in:file → 295 unique repos
taking the first 100 alphabetically
git/trees per repo, recursive, blobs only
✓ 100 repositories read, 0 cloned
✓ written to data.json
Notice that everything happens against the API. Reading a repository is not the same as probing somebody's running app, and only one of those is ours to do.

The fingerprint for Lovable is reliable: the lovable-tagger package, a componentTagger() call in vite.config.ts, a non-standard build:dev script, and a <meta name="author" content="Lovable"> tag in the built HTML. Four query variants returned 295 unique repositories; I took the first 100 alphabetically, so the sample is not picked for convenience.

The 404 figure is an inference, and I would rather say so than dress it up. I did not load 100 strangers' sites and refresh them. I read each repository and checked whether it contains anything that would answer a request for an inner route. Seventy-seven contain nothing.

Keys in public repositories

A browser app has to be handed everything it needs before anyone logs in, so some keys are public by design and that is fine. The problem is the second copy: the local environment file, committed to the repository, holding values that were never meant to leave the machine.

view-source:yourapp.com/assets/index-Bq7xKp.js
const Ir=Nt("https://xqf3pvbmzlkraydu.example.co","eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZWYiOiJ4cWYzcHZibXpsa3JheWR1Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3MTgyNDAwMDB9.q7Xk2VbN0sJmR4dHwPzYtCu1LfA9eKgO3ZiM8vHrQnY"),Bt=async()=>{const{data:e}=await Ir.from("users").select("*");return e}
Notice that this is the file every visitor downloads anyway. The highlighted string belongs here; what matters is the setting at the other end of it.
26of 100

Lovable repositories have a committed .env. All 26 of them already had a .gitignore.

afterbuild.dev100 public Lovable repositories, read 2026-08-03

The ignore file was there in every one of the 26 cases. The key had simply been committed before anyone thought to write the rule.

Once a value is in the history, deleting the file changes nothing — the commit is still public. Only 16 of the 100 have a .env.example, so for the rest, the list of variables the app needs is written down nowhere and can only be recovered by reading the code.

The tools are not the same

I expected one story across all three, and that is not what the data says. Lovable and Replit both produce Vite single-page apps and fail the same way. v0 produces Next.js, which routes on the server, and this failure essentially does not arise.

MeasuredLovableReplitv0
No rewrite rule77%75% of Vite projects5%
.env committed26%2%0%
A source file over 20 KB52%76%49%
Any test at all30%12%4%
Notice the last row rather than the first. The routing problem is an afternoon's work; the absence of tests is what makes every later change expensive.

No rewrite rule in the repository

100%50%077%Lovable75%Replitof Vite projects5%v0
Notice that v0 is not a better-configured version of the other two. It is a different architecture, so this particular failure does not exist there.

For v0 the 5% is a different measurement: next.config was readable in all 100, and five of them set output: 'export'. That is the only configuration where a Next.js app inherits the single-page routing problem.

Replit turned out to be the odd one in the other direction. Fifty-two of its 100 projects ship a real backend — an Express server, and 46 use Drizzle — where Lovable and v0 mostly produce a front end that talks to somebody else's API. That changes which failures matter: an app with a server of its own has to survive a reboot, and nothing in these repositories arranges that.

What I got wrong

Three measurements went into an early draft and came out again. They are worth more than the ones that survived, because they show how easily a confident number can mean nothing.

Corrected · 01

A metric meant to count insecure http:// links reported 22%. Every single match was http://www.w3.org/2000/svg — the SVG namespace, which is required to look exactly like that. Real insecure links found: zero. Dropped, not rewritten.

Corrected · 02

I had a figure saying 93% of v0 projects have no rewrite rule. True, and meaningless: Next.js routes on the server, so the file is not part of how it works. The number was measuring the absence of something that should be absent.

Corrected · 03

An abandonment figure turned out to be an artefact of GitHub's default result ordering. Same query, different sort — and the median time since the last commit moved from 370 days to 4. The ordering was the finding, not the projects.

Same query, two orderings

search/repositories?q=…in:readme
370median days since last commit

The default ordering. Six of the hundred had been touched in the last 90 days.

Switch the ordering and watch the conclusion invert. Nothing about the population changed; only which hundred rows came back did.

The rule I took from this: any number produced from the first N results of a search describes the search, not the population.

How long these projects live

So I measured it a way that survives that objection: don't sample at all. The search API reports a total count for any filter, so you can ask how many repositories with a given fingerprint were created in a given month, and how many of those have been pushed to recently. That is a census, and sort order cannot touch it.

3%Lovable

Of every 100 repositories created in August 2025, three had a commit in the 90 days before measurement — a year on.

4%v0

Measured independently, on its own population of 9,380, and it lands in the same place.

Notice that the two tools agree. Two populations, measured separately, land in the same place — which points at how people use builders rather than at the builders.

The whole curve, by creation month: 3–4% of the August 2025 cohort was still being touched a year later, 6–9% of the December cohort, 10% of February's, 21–28% of April's.

That is not a scandal. Most of these repositories are experiments, and an experiment that ends is not a failure. It does mean any claim about abandoned apps has to say which cohort, measured when — and that the people worth talking to are the small share still working on theirs.

What this doesn't show

Limits of the method

  1. Public repositories only. Serious commercial work is often private, and none of it is in this sample.
  2. Repository activity is not product activity. A live, paying app can sit on code nobody has touched in a year.
  3. Database settings are invisible from code. Three quarters of these projects have no migrations at all, so whether a table is protected cannot be answered from the outside — and I did not try to test it on anyone's live app.
  4. Nothing here was built or run. I read structure; I did not compile a hundred projects or measure how fast they load.

How to repeat this

Four scripts, in order. They need the gh CLI, authenticated. Nothing is cloned and nothing from the repositories is executed.

SHELL
# 0 · find the population by its fingerprint
python3 search.py lovable 100 > repos_raw.txt
# 1 · structure, secrets, fallback rules
python3 harvest.py 100
# 2 · layers, file sizes, tests
python3 arch.py
# 3 · lifespan by cohort, whole population
python3 cohortes.py
Notice that none of these touches a live site. The last one asks the API for counts only, which is why its result does not depend on result ordering.

The scripts are here: github.com/titov-d/afterbuild-scripts.

The per-repository results are not, and that is deliberate. Those scripts produce a table mapping real repositories to findings like "committed .env", and publishing it would be publishing a list of live targets. Method and totals are in the repository; run it and you will have your own copy of the detail in twenty minutes. That is a different act from handing it to everyone at once.

If your own app is one of the 77, the repair is an afternoon and you do not need me to describe it: the scripts above will tell you where you stand, and so will any AI you ask. If you would rather hand it to someone who has done it a few hundred times, send it over — we look before we quote.