Frameworks & CMS

Your Free CMS Turns You Into an Unpaid Sysadmin

A headless CMS can make a client site easier to redesign and harder to maintain. For a solo freelancer, that trade is often a bad one: the content API, frontend, preview flow, and deployment each need an owner, and that owner is you. I would default to a conventional CMS unless a paid requirement justifies keeping those pieces in sync.

A small content site rarely needs two systems to publish one page

The appeal of a headless CMS is straightforward: editors work in one place, and a frontend built with Next.js, Astro, or another framework decides how content appears. The maintenance bill is less straightforward. A published page may depend on a content model, an API token, a build, a cache, and a host. When a client says “the change is in the CMS but not on the site,” you have to identify which boundary failed before you can fix the page.

Read Top Frameworks and CMS for Modern Web Development skeptically: a useful list of candidates cannot tell you who will repair a broken preview on a Friday evening. For a solo practice, I would compare WordPress and a headless CMS with a Next.js frontend against the same client request: add a field to an existing page template, preview the edit, publish it, and restore the previous version if the client changes their mind.

WordPress wins when the client mainly edits pages that share established templates, because its editor, rendering, revisions, and publishing controls can live in one system. Its cost is ongoing core, theme, and plugin maintenance; a neglected WordPress installation is not a low-maintenance installation. Headless CMS plus Next.js wins when one content model must feed genuinely different outputs or the frontend needs behavior the CMS theme would fight, because the API gives those outputs a shared source. Its cost is maintaining the schema, API integration, frontend dependencies, preview authentication, and deployment together.

For budgeting rather than forecasting, put 2 hours per month on the headless option’s maintenance line before adding feature work. That is a planning allowance, not a measured average: use your own time logs to replace it. It makes an otherwise invisible obligation discussable with a client. If they will pay for a redesigned frontend but not for recurring checks and repairs, the project has a funding mismatch, not a technology requirement.

I would not choose headless merely because a marketing site might someday need a mobile app, because a hypothetical second consumer does not pay for the second system you must maintain now. I would ask for an actual content-sharing requirement, an owner for editor training, and a support agreement that covers publishing failures. Without those, a conventional CMS is the more honest default.

The content model becomes a contract you must migrate

A field name in a headless CMS is effectively an API contract. Rename heroImage to bannerImage in Contentful or Sanity, and an existing Astro component may still request the old field; the editor can save successfully while the rendered page loses its image. GraphQL schemas can expose that mismatch during development if queries are validated, but they cannot migrate existing entries or decide what an empty field should display. REST responses may fail more quietly if the frontend assumes optional data is always present.

Read The Rise of Headless CMS in Modern Web Architecture with a migration ledger beside you: separating content from presentation also separates the people and tools that must agree on each change. For a one-person operation, “independent teams can deploy independently” is not a benefit unless someone else actually owns one of those deployments.

Before changing a published content model, write down the current field, its replacement, the affected templates, and the entries that need updating. Check whether an editor can still create the old shape while the new frontend is deploying. A safe sequence is to add the replacement field, make the frontend accept both shapes, migrate entries, verify a published page, and only then remove the old field. That sequence takes longer than a direct rename because it protects content already in use.

Use a 30-day overlap as a value to tune, not a universal rule, when retaining an old field during migration. A site edited weekly may need the overlap so late edits and cached builds do not expose an incompatible shape; a site with a controlled publishing freeze may need less. Record the removal date, because “temporary” compatibility code becomes permanent when nobody is assigned to delete it.

Preview deserves its own test. Published content and draft content may use different API tokens, endpoints, or permissions, and a preview URL that works for you can fail for an editor without your account. If draft access uses a token in an environment variable, verify that the token stays server-side and that a failed authorization returns a clear error rather than a blank page. That check matters because preview failure often appears at the moment a client is trying to approve an urgent change.

A content model also needs a deletion policy. If an editor removes an entry referenced by another page, decide whether the frontend should hide the section, show a fallback, or block publishing. I prefer a visible validation failure for required content, because silently dropping a call to action can leave a live page looking legitimate while conveying the wrong message. The rule belongs in the project notes as well as the code, so the next maintenance session does not require reconstructing your intent.

Automated builds do not automate the publishing diagnosis

Webhooks make headless publishing feel instantaneous until one request is delayed, rejected, or sent to the wrong deployment environment. A Contentful or Sanity publish event may trigger a Vercel or Netlify build, while a Next.js site using Incremental Static Regeneration may instead refresh a route after a request. Those mechanisms have different failure modes, so “the CMS says published” cannot be your sole acceptance test.

Set a 15-minute investigation threshold as an operational choice: if a published edit has not appeared by then, check the CMS event, deployment log, and live URL rather than repeatedly pressing Publish. The threshold is deliberately tunable; a scheduled campaign may require a shorter window, while an infrequently updated portfolio may not. What matters is having a point at which you stop guessing and follow a known path.

For each site, keep one short publishing runbook with the production URL, CMS project and environment, hosting project, webhook destination, and the location of build logs. Include who can rotate an API token and what triggers a rebuild. Do not paste secrets into that document; record where they are stored instead. This is mundane work, but it reduces the time you spend rediscovering which account owns a site after several quiet months.

Check the distinction between a successful build and a correct page. A build can pass while rendering stale content from a cache, and a webhook can return HTTP 200 while targeting a staging deployment. Conversely, HTTP 429, defined as “Too Many Requests” in RFC 6585, gives you a concrete reason to inspect rate limits or retry behavior rather than editing the content model. HTTP status codes narrow the search; they do not prove what visitors see.

For a client with occasional edits, I would prefer a documented manual rebuild over a chain of poorly understood webhook handlers, because the manual step is easier to diagnose and can be stated in the publishing instructions. That choice loses immediacy and requires someone to press the button. If prompt publication is a paid requirement, keep the webhook, but test it with a harmless draft-to-publish change and verify the production URL, not merely the deployment dashboard.

Dependency checks are useful only when you reserve time to act

A headless frontend inherits maintenance outside the CMS. Node.js, Next.js, npm packages, the hosting adapter, and the CMS SDK can change on different schedules. WordPress has its own update burden, but a separately deployed JavaScript frontend adds another lockfile and build process to inspect. The practical question is not whether updates exist; it is whether your contract leaves time to test them against editor workflows.

In an npm project with a package-lock.json, this Bash check runs without changing project files. It reports known production-dependency advisories and outdated packages; it does not prove that an upgrade is safe or detect every compromised package.

#!/usr/bin/env bash
set -euo pipefail
test -f package-lock.json || { echo "Run in an npm project"; exit 1; }
npm audit --omit=dev
status=0
npm outdated || status=$?
if [ "$status" -gt 1 ]; then exit "$status"; fi
echo "Review outdated packages, then test draft and publish."

Run it from the project directory with npm installed, then read the output before changing versions. npm audit –omit=dev excludes development dependencies from its advisory check; a build-time dependency can still break a deployment, so the flag is a scope choice rather than permission to ignore the rest of the tree. npm outdated exits with status 1 when it finds outdated packages, which is why the snippet preserves that result for review instead of treating it as a fatal command error.

After an update, test one existing page, one draft preview, and one publish-and-verify cycle. Those 3 checks are a proposed minimum, not measured coverage: add cases for forms, search, or localization when the site has them. A passing dependency scan cannot catch a CMS field that an editor has stopped filling, and a successful build cannot confirm that a draft URL remains private.

Price this work explicitly. A support agreement can name a monthly update window, the number of test publishes, and what happens when an upstream change requires a paid repair. Without that boundary, “minor maintenance” can absorb unpaid evenings because every system still expects an owner. If the client declines ongoing support, reduce the number of independently maintained parts rather than assuming you will find spare time later.

Your first decision should be made on a real edit

Before proposing a stack, ask the client for one representative edit they expect to make next month. Perform it on a small prototype, including preview, publication, and rollback, and record every login and manual step. Quote the recurring work that exercise reveals. If the client cannot fund it, choose the simpler publishing path now; a future redesign can justify a different architecture when its requirements are real.