Vibe Coding Architecture: Matching Your App to Its User Count
When you vibe code, the agent picks your architecture for you — usually wrong. How to match structure to your real user count, keep the app stateless, and scale by measured bottleneck.
The short answer: when you vibe code, the AI agent silently picks your architecture — and it gets scale wrong in both directions. So this is the first thing to keep in mind while vibe coding: architecture should match the user count you actually have, not the one the agent imagines. For most apps that's a single structured monolith plus a managed database — it handles thousands of users fine. You scale by measured bottleneck, not by milestone: add instances behind a load balancer, add a cache, add read replicas, and only split into services when the pain is organizational, not technical. Over-architecting for scale you don't have is the most expensive mistake a vibe-coded project makes.
This is the one an AI agent gets wrong in both directions. Ask for "a scalable app" and it'll hand you Kubernetes, a message queue, and five services for your to-do list. Ask without the word "scalable" and it'll store sessions in a local variable that evaporates the moment you run a second instance. Neither is right. Scale is a dial, and you have to set it on purpose.
The tiers, honestly
Here's how I think about it, bucketed by rough concurrent-user scale. These numbers are deliberately fuzzy — your mileage depends entirely on what each request does — but the shape of each tier is the point.
| Scale | Shape that fits | What to avoid | |-------|-----------------|---------------| | ~0–1k users | One app server (or serverless functions) + one managed database. Monolith. Deploy to a PaaS. | Microservices, self-hosted databases, Kubernetes, anything with "orchestration" in the name | | ~1k–50k users | Same monolith, now stateless and run as multiple instances behind a load balancer. Managed DB with a read replica. Redis for sessions + cache. CDN for static assets. | Splitting into services; premature sharding | | ~50k–500k users | Horizontal app tier, DB read replicas, aggressive caching, background jobs moved to a queue + workers, CDN everywhere. | Rewriting into microservices unless team structure demands it | | 500k+ / many teams | Now service boundaries can earn their keep — but driven by team autonomy, not traffic. Partitioned/sharded data, dedicated caching tiers, observability as a first-class concern. | Treating this tier's patterns as the starting template for tier 1 |
The single most important line here: you move between tiers by measuring a bottleneck, not by hitting a user number. A simple CRUD app can serve 50k users from a tier-1 setup; a chat app with live presence might strain a tier-2 setup at 5k. The user count tells you roughly where to look; the metrics tell you what to actually do.
The non-negotiable that makes every later tier possible
If you do exactly one thing right at tier 1, make your app stateless. No in-memory sessions, no "this counter lives in a module variable," no uploaded files written to the local disk. Everything that must persist goes to the database, the cache, or object storage.
A stateless app is one you scale by changing a number ("run 4 instances instead of 1"). A stateful one requires a rewrite. This single decision is the difference between tier 1 being a dead end and being a launchpad — and an AI agent will almost always generate the stateful version unless you tell it not to.
Scale the database before you scale the app
When an app gets slow, the instinct is "add more servers." Usually wrong. App servers are cheap, stateless, and trivial to duplicate. The database is the part that's hard to scale and almost always the real ceiling. In order:
- Add the missing indexes. The single highest-leverage fix. An unindexed query that table-scans is the most common "the app got slow at scale" cause there is.
- Kill N+1 queries. The agent-generated loop that runs one query per item will murder you at scale. Batch it.
- Add a read replica and send reads to it. Most apps are read-heavy.
- Cache expensive reads in Redis with a sane TTL.
- Then, if the app tier is genuinely the bottleneck, add instances.
This is the same measure-first discipline I apply to finding the root cause of React performance problems — profile to find where the time actually goes, change one thing, verify it moved. Guessing at scale is as wasteful as guessing at a slow render.
When microservices actually earn it
Almost never because of traffic. A monolith running 50 instances serves an enormous amount of load. You split into services when multiple teams need to deploy independently and the shared monolith has become a coordination bottleneck — a release train everyone's stuck on. That's an org-chart problem wearing an architecture costume, and I wrote about exactly this trap in micro-frontends: when they help and when they hurt. The same logic applies on the backend: start as a modular monolith with hard internal seams, and those seams are where you split later — if you ever need to.
Use agents and skills deliberately
- An API / interface-design skill when you're drawing module or service boundaries — getting the contract stable early is what makes a future split a refactor instead of a rewrite.
- A performance-optimization skill aimed specifically at your database layer (indexes, query plans, N+1 detection), since that's where the real scaling ceiling lives.
- A backend-architecture-patterns skill to sanity-check that the tier your agent proposed matches your actual numbers — ask it directly: "what breaks in this design at 100x the users?"
Point one specialized agent at the question "what's the first bottleneck here under real load?" before you accept a generalist agent's architecture. It'll flag the in-memory state and the unindexed query the generalist happily shipped.
Right-sizing architecture is the highest-leverage decision in a vibe-coded project because it's the hardest to undo. Build for the scale you have with a clear migration path to the next tier, keep the app stateless, and scale the database first. That beats a tier-4 cathedral serving twelve users every single time.
This is one of four vibe-coding deep-dives — the others cover choosing a backend, security and networking, and the frontend, all gathered in the vibe-coding precautions overview.
The vibe-coding series
Things to keep in mind while building with AI agents — this post is one part of the full checklist.
- Start here: the precautions overview
- Choosing a backend
- Security & networking
- Frontend precautions
- Spec & prompt discipline
- Reviewing AI-generated code
- Testing & verification
- Dependency safety
- Data & database precautions
- Error handling & observability
- Cost control
- Keeping it maintainable
- Deployment & platform choice
Frequently asked questions
How many users before I need to scale my architecture?
A single well-chosen server with a managed database comfortably handles thousands of concurrent users for most apps. The signals to scale are specific: CPU or memory consistently high, the database becoming the bottleneck, or response times climbing under load — not a user-count milestone. Measure before you re-architect.
Do I need microservices to scale?
Almost never at the start. A well-structured monolith scales horizontally (multiple instances behind a load balancer) far further than most teams ever need. Microservices solve an organizational scaling problem — many teams deploying independently — not a traffic problem, and they add real operational cost.
What's the first thing to scale when an app gets slow?
Usually the database, not the app server. Add indexes, fix N+1 queries, add a read replica, and put a cache (Redis) in front of expensive reads before you reach for more app instances. App servers are cheap to duplicate; a mis-modeled or unindexed database is the real ceiling.