An outpatient endoscopy center was running its public site as a full WordPress install, with PHP, database and plugins serving every patient request. Staff liked editing it. Nobody liked how it loaded. WordPress stayed exactly where it was. Everything in front of it changed.
Replacing a CMS is usually the expensive way to fix a slow site, and it costs the staff everything they know. Keeping the CMS and moving it out of the request path gets the same result without asking anyone to relearn their job.
Staff keep the editorial surface they already know. WordPress exposes its content over GraphQL, the site fetches all of it while building (pages, providers, procedures, and the navigation menu itself) and ships plain HTML. Patients never touch WordPress. Every other decision here follows from that one.
Every response from the CMS is validated before it is used, so a renamed field stops the build loudly instead of quietly rendering a page that is wrong. Editor HTML is treated as untrusted on the way in. And a production build never invents content: if a fetch fails, the result is a not-found page, not a placeholder that looks real.
An editor pressing publish is the only thing that changes the public site. A small WordPress plugin notices the change, waits out a short quiet period so a burst of edits becomes one job, and asks the host for a fresh build. Nothing about that requires an editor to know it is happening.
The CMS could be down all week. Patients would never know.
That is what fetching content at build time actually buys. The pages a patient loads were finished the last time an editor pressed publish, so a plugin update, a slow database or an expiring certificate on the editorial side is a staff inconvenience rather than an outage on the public site. The honest flip side: a build does need WordPress reachable while it runs. When it isn't, the build fails and the previous site stays up, which is the failure mode you want.
Moving the CMS out of the request path moves the risk into the build. A naive version of this architecture ships a broken site the first time WordPress is slow. Most of the engineering here is about that.
A small WordPress plugin we wrote calls the hosting API when published content changes. Edits are debounced through a 90-second scheduled event, so a burst of ten saves produces one build rather than ten. A system cron keeps that scheduler ticking even when no editor has the admin open.
WordPress schemas drift: fields get renamed, field groups get restructured, and IDs come back inconsistently typed. Every GraphQL response is checked against a schema at build time, so a shape change becomes a loud, catchable failure instead of a page that quietly renders wrong.
Editor HTML is injected into pages, which makes it a real attack surface if the CMS trust boundary ever moves. A sanitizing pass strips scripts, frames, forms, event handlers and javascript URLs. While it is in there, it also rewrites broken legacy upload URLs and adds lazy loading to images. It is pattern-based rather than a full HTML parser, which is a deliberate trade-off for a single-tenant site with trusted editors.
Every CMS query routes through a single client with a ten-second timeout and two retries on a backoff. Content fetching had started out duplicated inline across a dozen page files; consolidating it into typed services is what made timeouts, retries and validation a single change rather than twelve, and it later made a major framework upgrade a non-event.
Continuous integration runs a full build against a local mock GraphQL server on every push. Query and schema breakage is caught without depending on the live CMS being reachable from a build runner, and a broken build never reaches the site.
Not-found and fallback states carry noindex, so a bad build cannot leave a stub page in search results. Every page gets a meta description generated from its own content. Fonts are self-hosted, the hero image is prioritised for first paint, and every image carries explicit dimensions so nothing shifts as it loads.
The whole point of keeping WordPress was that staff would not have to learn anything new. So the pipeline between their publish button and the public site has to be invisible to them, and safe when something goes wrong.
The shape of the site was decided in the first month and never revisited. What the rest of the time bought was the discipline around it: validation, sanitization, and a deploy that no longer depends on anyone remembering to press it.
Built with Astro 7, Tailwind CSS 4, Alpine.js, TypeScript, graphql-request, zod, WordPress with WPGraphQL and WPGraphQL Gutenberg, Appwrite Sites, and Node 22. This is website redesign and WordPress migration work for healthcare, and the reason it keeps working is ongoing maintenance.
Tell us what your team edits today and what your site has to do for the people who visit it. We will tell you whether the CMS needs replacing or just needs getting out of the way, and what either one costs.