fixmyvibe.codes
<  Back to Blog

Your AI-Built App Got Traffic. Here's What Breaks First

7 min read By Ivan Vetrau
scaling performance launch vibe-coding

Your post did well. Someone with a big following shared your launch, Product Hunt kept you near the top for a few hours, and the signup counter is moving faster than you can refresh it. This is the moment you built the app for. It is also the moment AI app scaling stops being a theoretical worry. In our experience, an app that crashes under traffic rarely fails in one dramatic way. It fails in a sequence, and the sequence is remarkably consistent. Here is the order we usually see, and what to do about it before the spike arrives.

Why apps that work for 10 users fail at 1,000

Nothing about your code changes on launch day. What changes is that many people use it at the same moment, and AI coding tools build for one person clicking through the flow on a fast laptop.

Ten users produce ten requests spread over a few minutes. A thousand users arriving in the same hour produce a different shape of load: overlapping requests, simultaneous writes, and the same popular page fetched again and again. The tool that wrote your app was never asked to think about that, so it didn’t.

Take a typical prompt: “show all orders on the dashboard”. The model writes a query that pulls every row and filters them in the browser. With 20 rows you cannot tell. With 20,000 the page times out. The code is not wrong in the way a typo is wrong. It is wrong in the way a bridge designed for pedestrians is wrong when a lorry drives onto it. We cover the general pattern in why vibe-coded apps break in production, and load is the version of it that arrives with an audience watching.

Database connections run out first

If we could only check one thing, it would be this. Most AI-generated backends open a fresh database connection for each request and never think about it again. On serverless hosting, every function invocation can open its own connection. A hundred visitors at once means a hundred connections, and your database plan allows far fewer than you would guess.

When the limit is hit, the symptoms look random. Some pages load, others show a blank screen. Logins fail for one person and work for the next. In the logs you will find messages about “too many clients” or connection slots being exhausted.

The fix is usually small: route the app through a connection pooler, which most managed databases offer as a second connection string. The hard part is knowing you need it. We see this often enough with Supabase that it has its own entry in our list of Supabase mistakes in AI-generated code.

Missing indexes turn slow into stuck

An index is what lets a database find rows without reading the whole table. AI-generated schemas tend to have a primary key and nothing else. Every query that filters by user, date or slug then scans the entire table.

At 500 rows that scan takes a millisecond. At 200,000 rows it takes seconds. And here is the part that makes launch day ugly: a slow query holds its connection for longer, so slow queries make the connection problem worse. The two failures feed each other, which is why they tend to arrive together and why founders describe it as “everything died at once”.

Adding indexes on the columns you filter and sort by is a fifteen-minute job for someone who knows what to look for. Row-level security policies deserve a look at the same time, because a policy that runs a subquery for every row can be slower than the query it protects.

Nothing is cached

Your pricing page, your blog index and your public profiles change perhaps once a week. In most AI-built apps, each visitor still triggers a fresh trip to the database to build them.

This is the cheapest problem to fix and the one we are most opinionated about. Anything that is the same for every visitor should be built once and served from a cache or a CDN, not rebuilt 5,000 times an hour. The other variant is the front-end loop: a component that calls the same endpoint on every render, sometimes several times per page view, because an effect was written without a dependency list. If your app feels sluggish before anyone else has arrived, this is often why a Lovable app is slow, and traffic multiplies every one of those wasted calls.

Free-tier limits arrive without warning

Every tool in your stack has a free tier, and every free tier has a ceiling you did not read. Bandwidth caps on the host. A database that pauses after a week of inactivity. Limits on function invocations. And the one that catches almost everyone: the default email sender on many auth providers allows only a handful of messages an hour.

That last one is nasty. Your first 30 signups get their confirmation emails. The next 300 never do, and they assume your product is broken. Nothing has crashed, so nothing alerts you.

Spend an afternoon opening the pricing page of each service you depend on and writing down the number at which it stops being free. Then decide which ones you will upgrade for the launch month. Paying a small monthly fee for a few weeks is far cheaper than losing the one day when people were actually paying attention.

Third-party rate limits

If your app calls an AI model, a maps service, an email API or anything else metered, that provider limits how fast you may call it. New accounts on lower tiers often have surprisingly small per-minute limits.

The generated code almost never expects a refusal. It calls the API directly from each user action, and when the provider answers with a rate-limit error, the user sees an endless spinner or a blank page. There is no retry, no queue and no friendly message.

The remedy is boring and effective: catch the error, retry with a delay, and tell the person on the screen that it is busy rather than leaving them staring at nothing. For anything expensive, put a queue between the user and the provider.

Warning signs before the spike

You can often see this coming, if you look. The signs we ask about first:

Pages take more than two seconds to load when you are the only person using the app. Your database dashboard shows CPU or connections sitting above half of the limit during your own testing. You cannot say how many rows the largest table has. Your provider has sent emails about usage that you archived without reading. Your error logs are empty, which usually means there are no logs rather than no errors. And the app has never had two people using it at the same time.

That last one is easy to test. Open it in ten tabs and click through the main flow in all of them. If it stumbles, a thousand strangers will find that out for you. For a more honest check, point a free load-testing tool such as k6 at a staging copy, never at production and never at a third-party API you pay for by the call. Our pre-launch checklist walks through this and the rest of the launch-week basics in order.

What to fix now and what can wait

Fix now: the connection pooler, indexes on the columns you filter by, caching for public pages, and any free-tier ceiling you will hit inside a month. Add graceful handling for rate-limit errors, and set up basic alerts so you hear about an outage from a dashboard instead of a stranger on social media.

Fix later: queues for everything, read replicas, splitting the app into services, moving to a different cloud. These are real tools for real scale, and none of them is your problem at 1,000 users.

We will say this plainly, because someone will try to tell you otherwise. A rebuild is almost never the answer to a traffic spike. The app you have is usually a few days of targeted work away from handling ten times the load. Rewriting it from scratch before you have proof of demand is an expensive way to avoid a cheaper fix.


Expecting a launch spike? Let us find the weak points first. Free.

Need help with your AI-generated code?

We fix bugs in vibe-coded apps and make them production-ready. Free code assessment — no commitment required.