AI writes code that works. It doesn't always write code that's fast. Most slow vibe-coded apps are slow for the same dozen reasons, and none of them show up until you have real data. Here's the list, how to spot each one, and one prompt that makes your agent hunt for them in your own codebase.
In a hurry? Copy The Prompt into Claude Code and let it look. Want to understand what it finds? Start with The Database.
Speed keeps the users you have. It doesn't bring you new ones. Shaving a second off a page nobody visits is Building Theater: it feels like progress and it makes $0.
I'm building a place where you learn to build products that work like a real company's on your own, and get them in front of people who pay, with a CTO who's built two seven-figure software companies in your corner.
Build Fast. Sell Faster.
Free to join the waitlist. Opens at $49/mo, and the price goes up as seats fill, so first in pays the least.
Your agent writes the simplest code that works on the data in front of it. With a dozen test rows, a query inside a loop is instant and doing things one at a time is fine. Nobody notices until real customers show up, and then the page takes ten seconds and the server bill doubles.
The mistakes repeat. Almost every slow app I look at has two or three of the twelve below. And the same agent that wrote them is very good at fixing them, once you tell it what to look for.
Years ago, before AI, a company I worked with pulled sales data from thousands of retail stores. The system processed it one day at a time: finish Monday, then start Tuesday.
It crashed constantly, nobody could see its progress, and adding servers didn't help. One Friday I was asked to babysit a run all weekend. I spent the weekend replacing it instead: every day became its own job in a queue, and several machines worked through it in parallel.
If a page is slow, check the database first. Every trip to it costs time, and AI-written code tends to make a lot of trips.
You load 100 orders, then loop through them and look up each customer. That's 101 trips to the database for one page. It's the most common slowdown in AI-built apps, and it hides inside friendly-looking ORM code.
// Slow: 1 query + 1 per order const orders = await db.order.findMany(); for (const o of orders) { o.customer = await db.customer.findUnique({ where: { id: o.customerId }, }); } // Fast: one query that brings the customers along const orders = await db.order.findMany({ include: { customer: true }, });
include, or one WHERE id IN (...) query for the whole list.An index is the database's table of contents. Without one on the columns you filter and sort by, every lookup is a full scan. Fine at 1,000 rows, painful at a million. Postgres doesn't index foreign keys for you, so user_id columns are a classic miss.
-- Slow on a big table: scans every row SELECT * FROM orders WHERE user_id = 42 ORDER BY created_at DESC; -- Fix: index what you filter and sort by CREATE INDEX orders_user_created ON orders (user_id, created_at DESC);
EXPLAIN ANALYZE on a slow query. "Seq Scan" on a large table is your sign.WHERE, JOIN and ORDER BY. Don't index everything: each one slows writes a little.The agent pulls every row into your server, then filters or counts it in JavaScript. Or it sends 5,000 rows to the browser and paginates there. The database could have answered in a millisecond.
// Slow: every row crosses the wire, then gets tossed const all = await db.order.findMany(); const paid = all.filter(o => o.status === "paid").length; // Fast: let the database do it const paid = await db.order.count({ where: { status: "paid" }, });
findMany() or SELECT * with no limit, followed by .filter, .length or .reduce.LIMIT.Opening a connection is slow, and databases only allow so many. On serverless hosts every function can open its own, so a traffic spike runs you out of connections and requests start failing, not just slowing down.
// Slow: a new client on every call export async function GET() { const db = new DbClient(); ... } // Fast: one shared client, created once const db = globalThis.db ?? (globalThis.db = new DbClient());
Serial means one thing at a time: start the next step when the last one finishes. Parallel means starting everything that doesn't depend on something else at once. AI defaults to serial because it's the easiest code to read. My $3,000 story was this section.
Each call waits for the one before it, even though none of them depend on each other. 50 calls at 200ms each is ten seconds. Run them together and it's closer to the slowest one.
// Slow: one at a time for (const id of ids) results.push(await fetchUser(id)); // Fast: together, but no more than 10 at once import pLimit from "p-limit"; const limit = pLimit(10); const results = await Promise.all( ids.map(id => limit(() => fetchUser(id))) );
await inside a for loop where each pass doesn't need the last one's result.Promise.all with a concurrency cap. Uncapped, 10,000 calls at once will hit rate limits or run out of connections.A page needs the user, their plan and their stats. None depend on the others, but the code fetches them in order, so you pay for three round trips. The same thing happens when a component only starts fetching after its parent finishes.
// Slow: three trips back to back const user = await getUser(); const plan = await getPlan(); const stats = await getStats(); // Fast: start all three, wait once const [user, plan, stats] = await Promise.all([getUser(), getPlan(), getStats()]);
Someone clicks Buy. The code saves the order, sends a receipt, builds an invoice PDF and pings Slack, then says thanks. Only the first step had to happen before the reply. If any of the others hangs, the checkout hangs.
// Slow: the customer waits for all of it await saveOrder(order); await sendReceipt(order); await buildInvoicePdf(order); return ok(); // Fast: save, queue the rest, answer await saveOrder(order); await queue.add("after-order", { orderId: order.id }); return ok();
An import, a report or a sync that loops over every day, file or customer in order. It takes hours, one bad record kills the whole run, you can't see how far it got, and more servers don't help because only one is working. That was my 72-hour job.
// Slow: one process, every day in order for (const day of days) await importSales(day); // Fast: one job per day, many workers for (const day of days) await queue.add("import-day", { day }); // workers run several at once, retry failures, // and progress = jobs done / jobs total
The last four live outside the database: answers you keep recomputing, APIs you call too often, and a browser doing far more work than it should.
Exchange rates, a product catalog, a dashboard total, the same AI prompt with the same input. If the answer barely changes, computing it fresh on every request is wasted time and, with AI and paid APIs, wasted money.
// Slow: same call, every request const rates = await getRates(); // Fast: fetch once, reuse for an hour const rates = await cached("rates", 3600, getRates);
The agent calls an API once per product when it has a batch endpoint, or polls a job status on a timer when the service would send a webhook. You get slow pages, rate-limit errors and a bigger bill.
// Slow: 200 calls for (const id of ids) items.push(await api.getProduct(id)); // Fast: 1 call const items = await api.getProducts({ ids });
setInterval around a fetch, or an API call inside a loop.A whole date library for one formatted date. An icon pack where you use six icons. A 4 MB photo shown at 300 pixels wide. On a phone over cellular, people leave before it loads.
// Slow: ships the whole library import _ from "lodash"; // Fast: ships one function import debounce from "lodash/debounce";
A React effect depends on an object that's rebuilt on every render, so it fires on every render. Or a search box hits the server on every keystroke. The page feels sluggish and your API takes ten times the traffic it should.
// Slow: a new object each render, so this runs each render const filters = { status: "open" }; useEffect(() => { load(filters); }, [filters]); // Fast: a stable value const filters = useMemo(() => ({ status: "open" }), []);
You can't fix slow if you don't know where it is. Five free ways to find out, most of which your agent can run for you.
Turn on query logging in your ORM or database and load one page. If it runs more than a handful of queries, look for N+1.
Put EXPLAIN ANALYZE in front of a slow query. It tells you whether the database used an index or read the whole table.
Open the Network tab in Chrome DevTools and reload. A staircase of requests means serial. One giant file means a heavy bundle.
Log a timestamp before and after each step in a slow endpoint or job. The biggest gap is where to start.
Seed 10,000 rows of fake data and try again. Most of these twelve are invisible with a dozen test rows.
Paste this into Claude Code, Codex or Cursor from the root of your project. It looks for all twelve, ranks them, and waits for you before it changes anything.
You're a senior engineer doing a performance review of this codebase. It was built fast, mostly with AI, and it's getting slow. Find the bottlenecks before you change anything. What feels slow: [the page, endpoint or job, or "not sure"] Stack: [e.g. Next.js, Supabase, Vercel, or work it out from the repo] Look for these: Database 1. N+1 queries: a query inside a loop, or one query per item in a list. 2. Missing indexes: columns used in WHERE, JOIN or ORDER BY with no index, foreign keys included. 3. Fetching everything: SELECT * or findMany with no limit, or loading rows just to filter, count or sum them in code. 4. A new database connection per request, or no connection pooling on serverless. Serial vs parallel 5. await inside a loop over independent items, and the opposite: Promise.all over thousands of items with no concurrency limit. 6. Waterfalls: independent awaits run one after another, or components that only fetch after their parent finishes. 7. Slow work inside a request: email, PDFs, AI calls, image processing or third-party calls the user doesn't need to wait for. 8. Big jobs run as one long serial loop (one day, file or customer at a time) with no queue, no retries and no progress tracking. Everywhere else 9. No caching for data that rarely changes, including repeated identical AI or API calls. 10. Chatty APIs: one call per item where a batch call exists, or polling where a webhook would do. 11. A heavy frontend: whole-library imports, oversized images, nothing lazy-loaded. 12. Re-render loops: effects or queries that rerun on every render or keystroke. For each one you find, give me: - the file and line, with the code quoted - why it's slow, in one sentence a non-engineer would get - how bad it is now, and how bad at 10x the data or traffic - the fix, and what it could break Rank everything by impact, biggest first. Measure where you can: query logs, EXPLAIN ANALYZE, timing logs, a quick load test. Mark anything you couldn't verify as a suspicion, not a finding. Don't change any code yet. Give me the ranked list and stop. When I pick one, fix only that one, measure before and after, and show me the numbers.
Fill in what feels slow and your stack, or leave them for the agent to work out. Leave the rest alone.
It finds more when it can run things. Give it a local database with realistic data, or paste in your query logs and a slow request's timings.
Pick the top item, have it fix just that, and check the numbers. Twelve fixes in one PR means you won't know which one broke checkout.
One N+1 on your busiest page is worth more than twenty small cleanups. The ranked list exists so you don't spend a week on the wrong thing.
Databases have connection limits and APIs have rate limits. Running a thousand things at once just moves the bottleneck. Cap it.
Ask for a before and after: query count, response time, job duration. If the agent can't show you a number, you don't know it worked.
Slow code is often expensive code. Servers running for days, paid API calls repeated for every visitor. My fix was about speed and ended up saving about $3,000 a month.
Anybody can build software now. Not everybody can build a business.
Reading about tools is a great way to feel busy. The Lab is for Founders who'd rather get customers: weekly office hours with a CTO who's built two seven-figure software companies, and a room of other Founders who care whether anybody buys what you're making.
Less Theater. More Customers.
Want my 30-Day Launch Plan? It's for Lab members only: validate, plan, build and launch, one week at a time.
See The Solo Founder Lab ▶