The default architecture for a new website is wildly over-built. You reach for a server because the tutorial had one, a database because the framework assumed it, and a runtime that has to boot, query, render, and serialize on every single request — all to return a page that hasn’t changed in three weeks. The machinery is real, the bill is real, and the failure modes are real. The content is static.
Most of the web is documents. A blog post, a marketing page, a changelog, a docs site — these are read far more often than they’re written, and they’re identical for every visitor. When the output is the same for everyone and changes rarely, computing it fresh on every request is not flexibility. It’s waste with extra steps.
What “static” actually buys you
A static site is HTML, CSS, and a little JavaScript, generated once at build time and served from a CDN edge. The substitution sounds small and the consequences are not. You trade per-request computation for build-time computation, and that single move collapses an entire category of problems.
There’s no server to patch, no database to back up, no connection pool to exhaust, no N+1 query hiding behind a slow page. Your “infrastructure” is a bucket of files behind a cache. The attack surface shrinks to almost nothing, because there’s almost nothing to attack — no SQL to inject, no runtime to exploit, no secrets sitting in an environment waiting to leak.
Performance stops being a project. A cached file served from an edge node a few milliseconds from the user is about as fast as the web gets, and you get it by default instead of by heroics. The page that took a dynamic stack 400ms to assemble arrives in 20ms because it was assembled once, weeks ago, and has been sitting ready ever since.
The fastest request is the one your server never has to think about. Static sites win not because they’re clever, but because they refuse to do work that was already done. — the principle I keep coming back to
The economics nobody mentions
Dynamic hosting is priced like a thing that’s awake. You pay for a process that has to be ready to respond, which means you pay whether anyone shows up or not. Idle capacity is still capacity, and capacity is still a line item.
Static hosting is priced like storage and bandwidth, both of which are nearly free at the scale a normal site operates. A personal site, a company blog, a documentation portal — these run on the free tier of half a dozen providers indefinitely, and the moment they get popular, a CDN absorbs the traffic without you touching anything. The cost curve for static is flat where the cost curve for dynamic bends sharply upward exactly when you’d least want it to: under load.
// "dynamic": run on every request, forever
app.get("/post/:slug", async (req, res) => {
const post = await db.posts.findBySlug(req.params.slug);
const html = await render(layout, post);
res.send(html); // identical for every visitor, computed every time
});
// "static": run once at build, serve from the edge
for (const post of await db.posts.all()) {
const html = await render(layout, post);
await writeFile(`dist/post/${post.slug}.html`, html);
}
The dynamic version does the same render forever. The static version does it once and walks away. Same output, wildly different bill.
When dynamic actually earns it
This isn’t an argument against servers — it’s an argument against reflexive servers. Some things genuinely have to be computed per request, and pretending otherwise is its own kind of waste.
If the page is different for every user — a logged-in dashboard, a personalized feed, a cart — you need dynamic rendering, full stop. If the data is changing by the second and staleness is unacceptable — live prices, live scores, live availability — a cache won’t save you. And if the core of the product is the computation — a search engine, an editor, a simulator — then the server isn’t incidental, it’s the point.
The honest test is simple: does this page change based on who is asking or when they ask? If the answer is no, it should be a file. The mistake isn’t using dynamic rendering. The mistake is using it for the 99% of pages that are just documents wearing a server costume.