Your architecture sets your cost curve before you have a single user. Scale doesn't create the curve, it only reveals it. And the free tier that carries you to launch will fail on your best day, not your worst.
The concept
Three related but distinct concerns:
- Performance, how fast it feels. A user problem.
- Cost, what it takes to serve someone. A business problem.
- Scaling, what breaks as volume grows. An engineering problem.
They interact: a slow query costs both patience and money; caching helps all three.
⭐ The cost curve is an architectural choice
COST PER USER PER MONTH
$$$ │ ╱ AI per interaction
│ ╱ (model call per action)
│ ╱
$$ │ ╱
│ ╱ ────────────── Server-rendered + media
$ │ ╱ ────
│ ╱────
0 │═══════════════════════════════════ Static + client-computed
└──────────────────────────────────────
1k 10k 100k 1M users
Which line you're on is decided by a handful of early choices:
| Choice | Flattens the curve | Steepens it |
|---|---|---|
| Content | Static, bundled or CDN | Generated per request |
| State | Computed on device | Computed server-side per view |
| Media | Optimised, cached | Full-size, re-processed |
| AI | None, or cached/tiered | A model call per interaction |
| Data | Read-mostly, indexed | N+1 queries, full scans |
You cannot refactor a cost curve later without rewriting the product. Decide it deliberately (Chapters 01, 23).
Know every ceiling before launch
Free tiers are a legitimate strategy, not a compromise, provided you know where each one ends and how it fails.
FOR EACH SERVICE, WRITE DOWN:
the limit · the PERIOD (per second / day / month / cumulative) ·
what happens at the limit (throttle? reject? bill?) ·
whether it fails LOUDLY or SILENTLY ·
the exact cost of the next tier
The period matters more than the number. A daily email limit fails differently from a monthly one, it fails on the day your post works, silently, to the people you most wanted to reach.
⭐ Scaling problems arrive on your best day. The post that lands, the launch that works, the mention that goes around, that's when you hit the ceiling. Pay the small monthly fee before the campaign, not during it.
Typical breaking points
Approximate, but the shape is reliable:
| Users | What tends to break first |
|---|---|
| ~1k | Email quotas (bursty), free-tier project pausing |
| ~10k | Database size, monthly active limits, N+1 queries under real data |
| ~100k | Bandwidth, egress costs, single-instance capacity, support volume |
| ~1M | Everything you deferred; you now need a team |
Performance work that actually matters
In order of user-perceived impact:
- Time to first meaningful content. Everything else is secondary.
- Input responsiveness. Under ~100ms feels instant; over ~200ms feels broken.
- Avoiding layout shift. Content that jumps is worse than content that's slow.
- Perceived speed. Skeletons, optimistic updates and streaming beat real speed for user satisfaction.
Measure before optimising. Developer intuition about bottlenecks is unreliable; profilers are not.
The usual real culprits, in frequency order: unoptimised images · N+1 queries · missing indexes · blocking JavaScript · unnecessary re-renders · synchronous work on the main thread.
Database scaling, in order
Do these in sequence, most people skip to the last:
- Add the missing index. Usually the whole problem.
- Fix N+1 queries. One query per row is the most common performance bug in web apps.
- Cache read-heavy, rarely-changing data.
- Read replicas for read-heavy loads.
- Denormalise deliberately, with a labelled cache.
- Partition or shard. You are unlikely to need this.
Cost control that must exist before launch
- Billing alerts on every paid service. Multiple thresholds.
- Hard spend caps where supported.
- Rate limits per user on anything that costs you money.
- A kill switch for expensive features.
- Know your cost per user and check it monthly.
Cloud bill surprises usually come from egress, log volume, or a runaway loop, not from success.
📐 Best practice
Choose your cost curve deliberately, early.
Write down every ceiling, its period, and its failure mode.
Set billing alerts before launch.
Upgrade the bursty-limit services before any campaign.
Measure before optimising.
Optimise images first, usually the largest single win.
Index what's slow; don't index speculatively.
Cache aggressively, with a deliberate invalidation strategy.
Bound every queue and retry.
Know your cost per user per month, and re-check it as features land.
Load-test the one flow that would break, usually signup or the main read path.
💀 Common mistakes
Optimising before measuring. Days on something that wasn't the bottleneck.
Not knowing a limit's period. Daily limits fail on your best day.
No billing alerts. The first signal is an invoice.
Ignoring egress. Bandwidth out is often the largest line item.
N+1 queries. Fine with 10 rows, fatal with 10,000.
Unbounded growth, queues, logs, caches, uploads.
Premature scaling architecture. Microservices and Kubernetes for zero users.
Free tier project pausing. Some providers suspend inactive projects, fine while building, catastrophic the week before launch.
Counting anonymous users as billable actives without realising.
Bundle size treated as a hosting cost. It's a conversion problem first.
Uncapped AI features. An unbounded liability with a marketing budget (Chapter 23).
The professional workflow
1. CHOOSE THE COST CURVE — static/client-computed vs server vs AI-per-call
2. INVENTORY LIMITS
for each service: limit · period · failure mode (loud/silent) ·
next-tier cost
3. SET BILLING ALERTS + hard caps where available
4. INSTRUMENT
p50/p95 latency · error rate · cost per user · slowest queries
5. MEASURE BEFORE OPTIMISING
6. FIX IN ORDER
images → N+1 → indexes → blocking JS → caching
7. BOUND EVERYTHING — queues, retries, uploads, logs, caches
8. LOAD-TEST the one flow that would break
9. PRE-CAMPAIGN CHECK
upgrade bursty-limit services BEFORE traffic arrives
10. REVIEW COST PER USER MONTHLY
Tools, websites & costs
| Need | Tool | Cost |
|---|---|---|
| Web performance | Lighthouse, WebPageTest, PageSpeed | Free |
| Real-user monitoring | Vercel Analytics, SpeedCurve, Sentry | $0-$$$ |
| APM / tracing | Sentry, Datadog, Grafana Cloud | Free tiers |
| Query analysis | EXPLAIN ANALYZE, pganalyze | $0-$$$ |
| Load testing | k6, Artillery | Free (OSS) |
| Image optimisation | sharp, Squoosh, CDN transforms | $0 |
| CDN | Cloudflare, Bunny | $0-20/mo |
| Cost monitoring | Provider dashboards, Vantage, Infracost | Free tiers |
| Caching | Redis (Upstash), platform caches | Free tiers |
| Uptime | Better Stack, UptimeRobot | Free tiers |
A typical indie stack serving ~100k users runs roughly $50-150/month, infrastructure is rarely the constraint. Distribution is.
Alternatives & trade-offs
Optimise now vs later. Premature optimisation wastes time; deferred optimisation becomes a rewrite. Architectural decisions (cost curve, data model) early; implementation optimisation later, when measured.
Cache vs compute. Caching is fast and cheap and introduces staleness and invalidation bugs. Computing is always correct and costs more. Cache what's read-heavy and changes rarely.
Client vs server compute. Client compute is free to you and limited by the weakest device. Server compute is consistent and costs money per user.
Scale up vs out. Vertical (a bigger machine) is simpler and hits a ceiling. Horizontal is unbounded and needs statelessness and coordination. Scale up until it genuinely stops working, that's much further than most people assume.
Managed vs self-hosted. Managed costs more per unit and removes an operational burden. Self-hosting saves money at scale and costs your attention. Below ~$1k/month in infrastructure, managed almost always wins.
Checklist
- I know which cost curve I'm on and chose it deliberately
- Every service's limit, period and failure mode is written down
- I know which limits fail silently
- Billing alerts configured on everything paid
- I know my cost per user per month
- Any AI feature has caps and tiering
- I measured before optimising
- Images optimised; no photographic PNGs
- No N+1 queries on hot paths
- Indexes added where measurably slow
- Queues, retries, uploads and logs are bounded
- Bundle size reviewed as a conversion issue
- The one breakable flow has been load-tested
- Bursty-limit services upgraded before any campaign
📓 Case Study: a flat cost curve, arrived at by accident
Project: SOLIS. At launch scale, running costs were $0/month, against fixed annual costs of a developer account and a domain.
The architecture that produced it:
| Decision | Effect on the curve |
|---|---|
| Content bundled in the app, not served | No per-read cost |
| Local-first; state computed on device | Most actions generate zero backend load |
| Derived values (streak, rank, progress) computed client-side | No server aggregation |
| No AI at runtime | No variable cost per interaction |
| Static marketing site | Effectively free |
Projected ceilings, from the real stack:
| Layer | Free ceiling | Breaks at | Fix | Cost |
|---|---|---|---|---|
| Transactional email | 300/day | ~1k users (bursty) | Paid tier | ~$9/mo |
| Database size | 500 MB | ~30k users | Pro | $25/mo |
| Monthly actives | 50k | ~50k users | Pro | included |
| Web bandwidth | 100 GB | ~250k page views | Pro | $20/mo |
| Payments layer | under $2.5k/mo revenue | ~$30k/yr revenue | 1% | scales with revenue |
Roughly $55/month plus 1% of revenue to serve ~100k users.
⚠️ Deviation 1: the flat curve was an accident, not a design.
It fell out of an engineering preference for offline-first, not a business decision. It could easily have gone the other way, the product plan's Phase 2 was an AI feature replying to user writing, which would have moved cost per user from ~$0 to a real per-message number.
To its credit, that feature was costed before building (Chapter 23), tiered models, free tier excluded, caps, and a margin check. But that analysis was triggered by a feature that happened to be expensive, rather than being standard practice from chapter one.
The daily limit that fails on your best day. The email service allowed 300 sends per day. A single post that produced 400 waitlist signups would mean 100 people silently receiving nothing, on the best day the project had. That's the shape this chapter warns about: the limit is bursty, the failure is silent, and it lands precisely when you'd most notice.
Nine dollars a month, paid before a campaign rather than during it.
⚠️ Deviation 2: bundle size was treated as a hosting question, not a conversion question.
The app bundled 525 MB of images. The decision was defensible, it fixed genuine reliability problems with remotely-loaded assets and made the app fully offline.
But 525 MB is an install-conversion problem before it's a cost problem: slow downloads, cellular-download limits, and abandonment on the store's size line. The correct architecture, bundle the first portion, lazy-download the rest with a local cache, is about a day of work and was never done.
⚠️ Deviation 3: one image was optimised; the pipeline wasn't.
A hero image went from a 1.9 MB PNG to a 117 KB JPEG, 16× smaller, visually identical. The same folder still contained a 1.9 MB logo, two ~1.8 MB scene images, and a 1.5 MB avatar.
📐 Optimising one file is a fix; optimising the pipeline is a solution. A pre-deploy check that fails on any image over ~300 KB would have caught all of them.
One thing anticipated correctly: anonymous users were gated behind a meaningful action rather than created on app launch. Without that gate, every casual install would have counted toward the billable monthly-active metric, a cost problem disguised as an auth decision (Chapter 19).
🚩 Unvalidated. No users, so no observed load. These are projections from the real architecture against published service limits, the method is sound; the numbers are untested.
Lessons
- ⭐ Architecture determines the cost curve; scale only reveals it. Choose it deliberately and early.
- Local-first and static content are cost strategies, not just UX ones.
- Know the period of every limit, not just the number.
- The limits that fail silently are the dangerous ones. Email is the usual offender.
- ⭐ Scaling problems arrive on your best day. Upgrade before the campaign.
- Compute AI costs before building. Uncapped is unbounded liability.
- Bundle size is a conversion problem first.
- Optimise the pipeline, not the file. Add a size check to your deploy.
- Measure before optimising. Intuition about bottlenecks is unreliable.
- Anonymous users can be billable users. Gate their creation.
- ~$55/month serves ~100k users on a modern stack. Infrastructure is not your constraint, distribution is.
Next: Chapter 32: Content and Asset Production at Scale →
This completes Part VI, Quality.