What it is and why it matters
An app built with Claude Code works great with you and three friends. Then a hundred people show up at the same time and the pages take forever, or don't open at all.
The reason usually isn't a small server. It's the way the app talks to the database. With little data and few users, each bit of waste costs milliseconds. With lots of data and lots of people at once, the waste adds up.
A load test lets you see it ahead of time: you simulate lots of people at once, measure how long each page takes, and find out what breaks. You first, before your users.
Rule number one, no exceptions: the test runs on a copy of the project or on the test environment (staging), with fake data. Never on the database with the real data, never on the live site. A load test is a small attack: on the real site you slow down or take down the app for the people using it.
1. Set up the right place
Before writing the prompt, I make sure of three things.
- I have a separate copy: a test environment, or the project on my computer with its own database.
- I've made a git commit, so I can go back.
- I know which external services the app uses (payments, email, notifications, other people's APIs). They must not be really called during the test.
On the last point: a load test against a site or an API that isn't yours can break their terms of use. Only test your own test environment. To start, I ask Claude to check:
First of all, check which database and which external services the project is connected to right now. If anything points to the real data or to production, stop and tell me. I want to run the test only on a copy, with fake data, and with the external services replaced by fake versions.
2. The test with a thousand fake users
Then in Claude Code I write this:
Run a test with a thousand fake users and tell me what breaks. Fill the test database with realistic fake data, write a script that simulates a thousand people on the main pages at the same time (use a free, open-source tool, for example k6, Locust or Artillery) and measure how long each page takes. Give me the three slowest pages, the reason and the fix you propose. Don't touch the code until I say ok.
What happens, in practice. Claude fills the test database with lots of fake but believable rows: users, orders, products. This matters more than it seems: with ten rows everything is fast, the problems only show up with realistic volumes.
Then it writes a script that sends requests to the main pages as if they came from lots of people at once. k6, Locust and Artillery are three free, open-source tools that do exactly this: let Claude choose based on the project.
The last sentence is the most important: first I want to understand, then I decide. It has to create the test script and the fake data, but not touch the app's code.
For an extra guarantee while it proposes the fixes, use plan mode: Shift+Tab until you see “plan mode on” at the bottom, or /plan at the start of your message. There Claude reads and proposes, but doesn't edit files until you approve the plan. For the test itself, stay out of it: it needs to create the script and the fake data.
A thousand right away, or step by step
A thousand people at once is a lot, even for the computer generating the test. Better to go up in steps and see at what point the pages slow down. And the test environment is usually smaller than the real one: what counts is before and after, not the absolute number.
Run the test in steps: first 50 users, then 200, then a thousand. For each step tell me the average time, the time of the slowest requests, and at what point the errors start.
One fix at a time
When it gives you the list of the three slowest pages, don't ask it to fix everything at once. Write:
Fix the first one, run the test again and tell me the times before and after.
And so on with the second and the third. If you fix everything in one go and then something gets better, or worse, you don't know what did it.
3. The three things it fixes almost every time
When a page is slow, the reason is almost always one of these three.
A hundred questions instead of one
A page shows 100 products. The code first asks the database for the list of products, then, for each one, asks for the price. Total: 101 questions to the database for a single page. It's called the “N+1 problem”: one question for the list, plus one for each item.
On your own you don't notice it. With a thousand people it becomes a hundred thousand questions, and the database gets clogged. And the code often looks clean: the problem is behind the scenes.
The fix is to ask for everything together: a single question that gets products and prices, or two at most (the list, then all the prices in one go). Ask:
Find the queries inside a loop and turn them into a single one.
Tables without indexes
Think of the index at the back of a book. Without it, to find a word you flip through every page. The database does the same: without an index, to find a user's orders it reads all of them, one by one. With the index it goes straight there.
Usually the main id of each table already has its own index. The columns you use to search, filter and link tables often don't. Ask:
Add indexes on the columns you use to search, filter and sort (user id, email, date).
Indexes aren't free, though: they take up space and make writes a bit slower, because with every new piece of data the database also updates the index. Put them only where they're needed. I always ask for proof:
For each index you add, tell me which page or query uses it and show me the time before and after. If an index is no use, don't add it.
Data reloaded every time
A price list or a menu that changes once a day shouldn't be fetched from the database on every visit. You keep it in memory for a few minutes: that's the cache.
Cache the data that rarely changes and tell me how long it takes to refresh.
The cache has a cost too: the data can be stale. If you change a price and the cache lasts ten minutes, for ten minutes someone sees the old one. That's why you need to know how long it takes to refresh. And a user's personal data must not end up in a shared cache, or someone else might see it.
Check that the cache holds no personal data of one user that another user could see.
Be careful if the app runs on serverless hosting: there the code runs on many instances that start and stop on their own. One instance's memory isn't shared with the others and empties often, so a cache kept there is of little use. You need a shared cache, for example Redis. Ask Claude which one your hosting offers:
Does the app run on serverless hosting? If so, tell me which shared cache my hosting offers and use that instead of the server's memory.
Common mistakes
- Running the test on the live site or on the database with the real data. Always do it on a copy or on the test environment, with fake data.
- Testing with an almost empty database. Fill it with realistic amounts of fake data.
- Fixing everything in one go. One fix at a time, with the times before and after.
- Putting indexes on every column. Only on the ones you use to search, filter and sort.
- A cache without a clear expiry. Decide how long it takes to refresh and keep personal data out of it.
Checklist
- I'm working on a copy or on the test environment, not on production
- The test database has realistic amounts of fake data
- In the test, the external services are fake
- I have the list of the three slowest pages, with reason and fix
- I fixed one thing at a time, with the times before and after
- I know how long each cache takes to refresh
Frequently asked questions
Are a thousand users a lot for my app?
It depends. A thousand sign-ups aren't a thousand people at the same moment: usually only some of them use the app together. A thousand at once is a tough test, and that's the point: you see where it gives way before it really happens.
Do I need to know how to code to do all this?
No. You need to know how to ask and how to read the results: which pages are slow, why, and how much they improve after the fix. If an explanation isn't clear to you, ask Claude to repeat it in simple words before you say ok.