Skip to main content
ASoc
Guide

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.

The ASoc Team10 min read

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 itemWhat it scales withCadenceSet by
Hosting & servicesRequests that need compute; usage-metered add-onsMonthlyArchitecture, once
Dependency upkeepNumber of direct dependenciesWeekly–monthlyArchitecture, once
Content changesHow often the copy and catalogue moveContinuousYour business
Framework migrationsMajor versions of the things you build onEvery 1–2 yearsArchitecture, 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 dependenciesnext, 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.js to CSS-first @theme blocks in globals.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.ts became proxy.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 saysAsk
"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 versionsThis 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

MistakeWhat it costsBetter
Budgeting maintenance as a flat monthly feeUnderfunded in the migration yearsMonthly for items 1–3; a reserve for item 4
Counting the whole dependency treePanic about 551 packages you never chosePlan around your direct dependencies
No CI gateEvery upgrade is a manual regression pass, so upgrades stop happeningLint + tests + a real production build on every push
Request-time image optimization by defaultA recurring charge coupled to trafficBuild-time derivatives where the images are known at build
Content changes that require a developerHourly billing for copy editsKeep content in typed data, not components
Skipping upgrades to save moneyYou pay for three majors at once, laterSmall, frequent, green-CI bumps
Treating "it still loads" as maintainedSilent SEO and accessibility decayRe-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.

Keep reading

Guide8 min read

Website Template Copyright: What You Own, What You Don't

Buying a template never transfers copyright in its code. What you actually own, plus the hardcoded footer year this codebase shipped unfixed for a month.

Read more
Guide12 min read

Atomic Design in Next.js: The Import Rule That Does the Real Work

Naming five layers changes nothing. Ordering their imports changes everything — including where the client boundary lands, which on this site was never designed at all.

Read more