A6 · Part 3 · How it holds up
What breaks first
Traffic is the least of it. The limits that are close are a search index, a storage plan and a few files that grow with every exercise.
05On this page
Headroom, measured
A design review earns its keep in the part that says where the design stops working. Here that question has numbers, because each pressure point has a limit and a current reading.
In the order they would give way
| Under pressure from | What gives | What it would take |
|---|---|---|
| Deploying often | Deployment storage, and it already has. Each deployment is about 190 MB. The daily cleanup keeps the live one, 3 production, 2 previews and anything under 6 hours old, so its floor is five or six deployments. Its ceiling is however many pushes land in six hours. | Fewer, batched pushes; a paid plan; or smaller deployments. Most of the size is prerendered output. |
| Writing chapters in one topic | That topic's search index. JavaScript is at 89% of the 85 KB budget, and its 41 chapters average 1,887 gzipped bytes each. At that rate the remaining 9,693 bytes are about five more chapters. Node is planned at 41 chapters too. | An inverted index sharded per topic, or a hosted search service. The budget test is the trigger. |
| Adding exercises | Three things grow together. Every exercise adds two prerendered pages, /problems/[slug] and its /cases page, which is 1,076 of today's 1,803. It adds a row to /problems, whose HTML is 283 KB for 538 problems. And it adds to the exercise-bank chunk the mock room loads whole, 1.70 MB raw today. |
Load the mock room's questions by category rather than all at once; paginate or virtualise /problems only if content-visibility stops being enough. |
| Writing the 358 outlined chapters | Less than it looks. Outline chapters already prerender, so the page count does not move. Build time and deploy size grow with the body text, and the chapters written so far average about 15,400 characters of HTML. | Nothing urgent. The whole static generation step takes 71 seconds on 3 workers. |
| A burst on the narrator | Function cost and the upstream. The in-process limiter lives in one instance, and serverless spreads traffic across many. The firewall rule holds at the edge. | A shared counter, which means state, which this design does not have. |
| Readers with two devices | Progress, streaks, saved code, run history and whiteboards all live in localStorage. They do not sync, and nothing could make them. |
Accounts and a database. |
| Big whiteboards | Storage before rendering. Rendering is culled above 150 elements. But images are stored inline as data URLs in the same localStorage every other feature uses, and when a save fails the board says so rather than silently losing it. |
IndexedDB for boards and images. |
| Traffic on the pages themselves | Nothing. The build lists three dynamic routes, all under /api/. Every other route is a file on a CDN. |
— |
The two that are really about money
Storage is the limit this project has actually hit, twice. It is not a code problem. A site that prerenders 1,803 routes ships all of them in every deployment, and the prerendered folder of a local build alone is 198 MB. The project itself now holds 7 deployments. The team's total still reads 10.32 GB of 10 GB, because the figure covers every project on the account. The daily job handles this project's share. Only a plan change or cleaning up the other projects handles the rest.
The narrator is the only feature whose cost grows with readers rather than with
deploys. A GET to /api/tts is cacheable at the edge for a year, so the same sentence
in the same voice is synthesised once. But every new sentence, voice or speed is a new function
call and a new request to a service the site does not control.
At ten times, and at a hundred
Ten times the readers changes almost nothing. The pages are cached files. The extra load falls on three functions, and only the joke caches its upstream, for 5 minutes. The first thing a reader would notice is the search index on a slow phone connection, and that is a payload problem, not a capacity one.
A hundred times is where the shape stops being right, not because the CDN struggles but because a site with that many readers needs different things:
- accounts, and so a database
- knowing which chapter people leave from, and so product analytics
- publishing without a deploy, and so a content pipeline that is not the Git repository
Each of those adds a piece this design deliberately leaves out.
What this design does not have
Each of these is a decision, not an oversight. Together they keep the system small enough to hold in your head.
- No database. The content is the repository, and the reader's state is their browser.
- No authentication. Nothing is gated, so there is no session, no reset flow and no account to breach.
- No cache layer of its own. The CDN is the cache and the build is the invalidation.
- No queue, no background jobs. Anything slow happens at build time or in the reader's browser. The daily cleanup runs on GitHub, not in the app.
- No server-side code execution. The playground runs on the reader's machine, so untrusted code never reaches the server.
Nothing here is close to falling over under load. What is close is a set of budgets: a search index at 89% of its limit, and a storage plan past 100%. Both grow with writing and deploying, not with readers. That is the right way round for a site whose job is to be written, and it means the next scaling decision is about content structure and hosting cost, not about servers.
Finished reading?
0 of 26 chapters read in this series