Website Maintenance Cost: Four Line Items, Three Decided Before Launch
Published ranges span $5 to $5,000 a month because they average across architectures. The four things that actually meter, itemised from a 551-package, 344-route build.
Website maintenance cost is four line items, not one number: hosting, dependency upkeep, content changes, and the episodic migration when a framework releases a major version. Published figures span $5 to $5,000 a month because they average across sites whose architectures meter completely differently — and three of the four items are fixed by decisions you make once, before launch.
This is what those four look like on a real site with 111 products, 344 prerendered routes and 551 packages in its dependency tree.
The four items, and what actually meters each one
| Line item | What it scales with | Cadence | Set by |
|---|---|---|---|
| Hosting & services | Requests that need compute; usage-metered add-ons | Monthly | Architecture, once |
| Dependency upkeep | Number of direct dependencies | Weekly–monthly | Architecture, once |
| Content changes | How often the copy and catalogue move | Continuous | Your business |
| Framework migrations | Major versions of the things you build on | Every 1–2 years | Architecture, once |
Only the third is genuinely about your business. The other three are consequences of the build, which is why "how much does maintenance cost" cannot be answered without knowing what was built — and why the answer is largely decided before the first month's bill arrives.
We are not going to publish our invoices, because a single site's bill on a specific plan is the least transferable number in this whole subject. What transfers is the metering axis: which quantity, when it doubles, doubles your bill. That is checkable against your own architecture today.
Item 1: hosting, and the line item we deleted
A fresh production build of this site:
✓ Generating static pages using 3 workers (352/352)
344 of those routes are prerendered into the CDN — every product page, every blog post and its OG card, the seven category hubs, the marketing pages. Eight routes are ƒ, server-rendered on demand: two API handlers, the auth callback, and five authenticated pages. So the compute meter only ever turns for a request to one of eight paths. A visitor reading twelve product pages generates zero server invocations.
The more interesting one is the meter that does not exist here at all. This codebase renders plain <img> tags pointing at derivatives built at build time by scripts/images/build-variants.ts — a 1060px -card.webp for grid cards, a 1600px -view.webp for the detail frame — rather than passing originals through a request-time image optimizer:
// src/lib/imageVariants.ts — resolved at render, generated at build
cardImage(product); // → /images/<slug>-card.webp
viewImage(product); // → /images/<slug>-view.webp
Request-time image optimization is billed per source image per month on most platforms. Doing the transforms at build time converts a recurring, traffic-coupled charge into a one-off build step. That is the shape of every good hosting decision: not "find a cheaper host," but "remove a metered dimension."
The counterpart cost is honest to state — build minutes go up, and adding a product means remembering to run the script. We made that a test failure rather than a discipline problem: npm test fails if a product's -card/-view variants are missing.
Item 2: dependency upkeep scales with 26, not 551
$ npm ls --all --parseable | wc -l
552 # the whole tree, root included
551 packages, and every one of them is a potential CVE. That number frightens people into buying "maintenance plans," and it is the wrong number to plan around. The tree is:
- 13 runtime dependencies —
next,react,react-dom, three MDX packages, two Supabase packages,lucide-react,@vercel/analytics,remark-gfm,rehype-slug,server-only. - 13 dev dependencies — TypeScript, ESLint and its config, Prettier and its Tailwind plugin, Tailwind, vitest, type packages.
- 525 transitive packages you never chose, that arrive underneath those 26 and are upgraded by upgrading them.
Your maintenance surface is 26. A transitive advisory is resolved by bumping whichever of the 26 pulls it, and if none of them will, that is a signal about the direct dependency, not a separate job. This is why "number of plugins" is the single best predictor of a WordPress site's maintenance bill and "number of dependencies" is its equivalent here — each one is an independent upgrade decision with its own release cadence and its own maintainer.
What makes those decisions cheap is not the count, though. It is having something that tells you a bump broke something:
# .github/workflows/ci.yml — on every push and pull request
- run: npm ci
- run: npm run lint
- run: npm run format:check
- run: npm run test --if-present
- run: npm run build
305 tests across 31 files, plus a full type-checking production build, in under fifteen minutes. That gate is what turns a dependency bump from "schedule a regression pass" into "read the diff, merge if green." A site without it does not have a lower maintenance cost — it has an unmeasured one, paid later and in worse conditions.
Item 3: content changes, and the tooling that decides whether they're cheap
Content is the only line item that scales with your business rather than your stack, and the one most maintenance quotes charge hourly for. What decides its cost is whether a change is a data edit or a code edit.
Here, adding a product is one typed entry in src/data/catalog.ts, from which the product page, all the SEO surfaces, the cards, the category hubs and the changelog are derived. Adding a blog post is three files that npm test refuses to let drift apart. Nobody touches a component to publish.
The version of this that bites at scale is knowing what needs attention. This storefront's products live in 113 separate repositories, and the question "which of these changed since their last release?" would be 113 clones. Instead:
// scripts/release/check-drift.ts — no clone, one fetch per repo
const url = `https://raw.githubusercontent.com/${repo}/HEAD/package.json`;
It reads each repo's version from GitHub raw and compares it to the catalogue's released latestVersion. Anything ahead is edited-but-unreleased. A recurring audit that would be an afternoon becomes a command, which is the difference between a maintenance task that happens and one that gets skipped until something is badly out of date.
Item 4: the migration, which is where the money actually goes
The monthly items are predictable. The one that blows up a maintenance budget is the major version, and it arrives every year or two whether or not you budgeted for it. Two we have been through:
- Tailwind v3 → v4. Configuration moved from
tailwind.config.jsto CSS-first@themeblocks inglobals.css. Not a find-and-replace: the token model changed, and the migration is a real read-and-decide pass over every custom value. - Next.js 15 → 16.
middleware.tsbecameproxy.ts, and the exported function had to change with it. Keeping both files is a build error rather than a warning — a hard failure at exactly the moment you are trying to ship. The details are a decent proxy for what a framework major costs in general.
Both were survivable in an afternoon each, and the reason is Item 2's gate: a migration you can run 305 tests and a type-checking build against is an afternoon; the same migration without them is a week of clicking around hoping.
The planning lesson is that this item is episodic, not monthly. A maintenance retainer priced as a flat monthly fee is either overcharging you in the quiet twenty months or underfunded for the two that matter. Budget it as a reserve.
What this means for the quote you were given
Read a maintenance proposal against the table at the top:
| If the quote says | Ask |
|---|---|
| "Hosting included" | Which meters — requests, bandwidth, image transforms, database rows? |
| "Security updates" | Of the direct dependencies, or only the platform? Who verifies nothing broke? |
| "Unlimited content edits" | Are those data edits or code edits? Can I make them myself? |
| "Support" | Response time to what — an outage, or a copy change? |
| Nothing about major versions | This is the expensive one. It is missing on purpose. |
And the version that applies before you have a site at all: the cheapest maintenance bill is bought at build time. Fewer direct dependencies, more prerendering, fewer usage-metered dimensions, and a CI gate that makes upgrades boring. None of those can be retrofitted cheaply.
Common mistakes
| Mistake | What it costs | Better |
|---|---|---|
| Budgeting maintenance as a flat monthly fee | Underfunded in the migration years | Monthly for items 1–3; a reserve for item 4 |
| Counting the whole dependency tree | Panic about 551 packages you never chose | Plan around your direct dependencies |
| No CI gate | Every upgrade is a manual regression pass, so upgrades stop happening | Lint + tests + a real production build on every push |
| Request-time image optimization by default | A recurring charge coupled to traffic | Build-time derivatives where the images are known at build |
| Content changes that require a developer | Hourly billing for copy edits | Keep content in typed data, not components |
| Skipping upgrades to save money | You pay for three majors at once, later | Small, frequent, green-CI bumps |
| Treating "it still loads" as maintained | Silent SEO and accessibility decay | Re-measure; a score is one URL, not conformance |
Frequently asked questions
Is a static site really cheaper to maintain, or just cheaper to host? Both, but for different reasons. Cheaper to host because 344 of our 352 routes never invoke compute. Cheaper to maintain because a prerendered route has no runtime to fail: the failure modes that generate maintenance tickets — a server erroring under load, a database connection exhausting, a cache going stale — do not have a place to happen. The eight dynamic routes are where essentially all operational attention goes.
How does this differ from ecommerce maintenance? An ecommerce site adds usage-metered dimensions that a marketing site does not have — payment webhooks, order data, gated downloads — and each one is an integration with its own version cadence. We itemise that separately in ecommerce site cost, where the four architectural decisions that set a storefront's recurring bill are worked through against this same codebase.
Should I pay for a maintenance retainer or handle it myself? Depends entirely on whether you have a CI gate. With one, dependency upkeep is a fortnightly half-hour of reading diffs and merging green PRs, and a retainer is buying you back very little. Without one, you are not buying maintenance, you are buying someone else's regression testing — and that is worth paying for, right up until you build the gate.
What is the one thing that most reduces long-run cost? Reducing direct dependencies. Every one is an independent upgrade decision, a possible breaking change, and a possible abandonment. This site renders 91 components with zero UI-library dependency, which is a deliberate trade with real downsides — we measure both sides of it here — but on the maintenance axis specifically it is the single largest lever.
Templates built on these decisions
ASoc Coin is an online-banking marketing site with a cash-overview dashboard mock and a three-tier pricing page. ASoc Compound is an automated-investing site built around goal-based portfolios. ASoc Cortex is an AI-agency site with services, case studies and a lead funnel. All three prerender, ship the same build-time image pipeline, and carry no UI-library dependency.
Browse the full sets: Next.js landing page templates, Tailwind landing page templates. For the build half of the budget, read website development cost; for whether to hire out the design work at all, should I hire a web designer.
