Skip to content

Why Does My App Crash When More Than One Person Uses It at Once?

6 min read

Your app works perfectly when you test it, then falls over the moment three or four people use it together. The short answer: you built and tested it as a single user, and a handful of simultaneous users hit limits and shared state that one person never touches. The most common culprits are database connection limits, global variables shared between requests, and free-tier rate caps. None of them show up in a demo.

Why this happens

When it is just you, your app handles one request at a time. Requests line up politely and nothing collides. The instant a second person arrives, requests overlap, and anything your app treats as "there is only one of these" breaks.

An AI agent builds exactly the app you described, and you described a demo. You said "let users upload a file" or "show the dashboard," and it made that work for one person. You never said "and make sure two hundred people can do it at once," so nothing was built for it. The three usual failure points:

  • Database connections. Every request opens a connection. Managed databases cap them low (Supabase's free pooler and many Postgres plans sit around 15 to 60). Ten users clicking around can exhaust the pool, and new requests hang or error.
  • Shared state. A variable defined once at the top of a server file is shared across every request. One user's data leaks into another's, or two requests overwrite each other.
  • Rate limits. Free tiers on your database, email provider, or AI API cap requests per second. A few real users trip the cap and everything 500s.

How to check

You do not need real users to reproduce this. You need concurrency.

  1. Watch your connections. In the Supabase dashboard, open Database → Reports and look at active connections while you test. On raw Postgres, run SELECT count(*) FROM pg_stat_activity;. If the number climbs and does not fall back down, you have a leak.

  2. Simulate a crowd. Install a load tool and fire 50 concurrent requests at a real page:

    npx autocannon -c 50 -d 10 https://your-app.com/dashboard
    

    Watch the error count and the latency. If errors appear or response times balloon, you have found your ceiling well below where a beta will sit.

  3. Read the errors. When it breaks, check your logs for phrases like too many connections, remaining connection slots, ECONNREFUSED, or rate limit exceeded. Those name the exact wall you hit.

The fix

  1. Use a connection pooler and reuse one client. On Supabase, connect through the pooler (transaction mode) URL, not the direct database URL. In your code, create one database client at module load and reuse it, rather than opening a fresh connection per request. A leaking pool is almost always a client created inside a request handler.
  2. Remove shared mutable state. Anything a request writes to must live inside that request, or in the database, never in a module-level variable. If you see a let at the top of a server file that requests update, move it.
  3. Raise or respect your rate limits. Check the caps on your database, email, and AI providers. Add retries with backoff for calls that can wait, and upgrade the tier for the one service that is actually the bottleneck. Do not blindly upgrade everything.
  4. Re-run the load test from step 2 and confirm errors stay at zero and latency stays flat as concurrency rises. That is your proof, not a hope.

The trap to avoid

The tempting wrong fix is to reach for the upgrade button and throw money at a bigger server. A bigger server with a connection leak just exhausts a larger pool a few seconds later. Fix the leak and the shared state first; scale the plan only after a load test tells you the code itself is sound. Otherwise you are paying more to fail slightly slower.

Where this fits

Concurrency is the gap between "it works for me" and "it works for my users," and it is invisible until real people arrive together, which is the worst possible moment to discover it. The free Readiness Report runs a load check against your actual project and tells you where your ceiling is before you send the invites. If you would rather have the connection pooling and rate-limit fixes done for you, that is the Finishing Pass. It is closely tied to why your app is fast on your laptop but slow for users and to knowing whether your app is broken for real users.