The Stack at a Glance
- Server management: RunCloud handles the web server, SSL, domains, and the PHP environment on our own VPS.
- CMS: WordPress, served under
/cms/. It exists only for editors to create and manage posts and pages. - Frontend: Next.js, living in
/frontend/, running on Node.js inside a Docker container. - Process supervision: Supervisor keeps the frontend container running, restarts it on failure, and brings it back after a server reboot.
- Connector plugin: a custom WordPress plugin that handles communication between WordPress and Next.js, including revalidation.
- Release tooling: release, deploy, and rollback scripts so every change to the frontend is repeatable and reversible.
How the Pieces Connect
- WordPress as a content source WordPress is deliberately limited to one job.
- Editors log in at
/cms/, write posts, upload media, and publish. The familiar editor means almost no training for the team. - No public theme is rendered for visitors. The theme layer is minimal, and public traffic never hits WordPress templates.
- Content is exposed through the REST API, shaped by the connector plugin so the frontend receives exactly the fields it needs.
- The connector plugin This is the glue between the two systems, and it is where most of the custom logic lives.
- Registers custom endpoints or fields so the frontend gets clean, predictable data instead of raw WordPress output.
- Listens for content events such as publish, update, unpublish, and delete.
- Calls a revalidation endpoint on Next.js with a shared secret, telling it which paths or tags are now stale.
- Supports previewing drafts on the real frontend before they go live.
- Next.js as the delivery layer Everything a visitor sees is rendered by Next.js.
- Pages are statically generated or cached, so most requests are served without touching WordPress at all.
- An API route verifies the secret from the connector plugin, then revalidates only the affected pages.
- Design, components, and performance are fully controlled in code, which keeps the brand consistent across every page.
- Docker and Supervisor on RunCloud RunCloud does not manage Node.js apps the way it manages PHP apps, so I fill that gap myself.
- The Next.js app runs in a Docker container, which pins the Node.js version and keeps the runtime identical between builds.
- Supervisor owns the container process. If it crashes, it restarts; if the server reboots, it comes back automatically.
- The web server proxies public traffic to the container, while
/cms/continues to be served by PHP as usual.
Publishing Flow
Editor publishes or updates a post in /cms/ ↓ Connector plugin catches the save event ↓ Plugin calls the Next.js revalidate endpoint with a secret ↓ Next.js verifies the secret, refreshes only the affected pages ↓ Visitors get the updated page, served from cache
The editor does not need to know any of this happens. They click publish, and within seconds the live site reflects the change, without a full rebuild or a deployment.
Release, Deploy, and Rollback
Content changes flow through revalidation. Code changes flow through scripts. Keeping those two paths separate is one of the main benefits of this setup.
Release
The release script builds the frontend into a new, versioned release directory. Each release is self-contained, so building a new version never touches the one currently serving traffic.
Deploy
The deploy script points the active release to the new build and restarts the process through Supervisor. The switch is quick, and if the build failed, the current version keeps running untouched.
Rollback
Previous releases are kept on the server. Rolling back means pointing back to the last known good release and restarting, one command, no rebuild. This decision is made before an incident, not during one, which matches the principle I described in A Practical Deployment Pipeline.
Cleanup
Only a limited number of old releases are kept, so disk usage stays predictable while there is still enough history to roll back safely.
Advantages
- Performance: cached and statically generated pages are fast by default, which helps both user experience and Core Web Vitals.
- Security: the public never interacts with WordPress templates or plugins directly, which reduces the attack surface considerably. The
/cms/area can be locked down further. - Brand consistency: layout and design live in code and components, so editors cannot accidentally break the look of the site.
- Familiar editing: the marketing team keeps the WordPress editor they already know. No new CMS to learn.
- Independent release cycles: content ships through revalidation, code ships through scripts. A frontend deploy never blocks publishing, and publishing never requires a deploy.
- Safe deployments: versioned releases and one-command rollback turn a bad deploy into a short inconvenience instead of an outage.
- Portability: the frontend could later consume a different CMS, or WordPress could feed other channels, without rebuilding everything.
Disadvantages
- Two systems to maintain: WordPress core, plugins, and PHP on one side; Node.js, Next.js, and Docker on the other. Both need updates and monitoring.
- Custom glue code: the connector plugin is ours. When WordPress or Next.js changes behavior, we are responsible for keeping it working.
- Lost WordPress conveniences: many plugins assume a WordPress theme renders the page. Page builders, form plugins, and some SEO features either do not work or need to be rebuilt on the frontend.
- Preview is harder: draft previews require extra work to feel as natural as they do in a classic theme.
- Revalidation edge cases: if a revalidation call fails or misses a related page, such as a listing or category page, visitors may see stale content until the next refresh.
- More operational knowledge: RunCloud simplifies the PHP side, but Docker, Supervisor, and the release scripts require someone who understands them.
- Higher initial cost: the first build takes noticeably longer than installing a theme and going live.
When It Makes Sense
| Situation | Headless | Traditional WordPress |
|---|---|---|
| Performance and brand control matter | Strong fit | Possible, with effort |
| Editors rely on page builders | Poor fit | Strong fit |
| A developer maintains the site | Strong fit | Strong fit |
| No technical owner available | Risky | Safer |
| Frequent code changes and releases | Strong fit | Harder to manage safely |
For our brand site, the trade was worth it. We have a technical owner, the design needs to stay tightly controlled, and the content workflow is mostly posts and pages rather than complex page building.
Lessons From Running It
Keep WordPress boring
Install only the plugins that support content editing. Every extra plugin is another thing to update and another thing that may not make sense in a headless context.
Treat the connector plugin as a product
Version it, document its endpoints, and log its revalidation calls. When content looks stale, those logs are the first place to check.
Revalidate relationships, not just pages
A new post affects more than its own URL. Home page sections, listing pages, and category pages usually need to refresh too.
Test rollback before you need it
A rollback script that has never run is only a hope. Run it deliberately on a quiet day so it is familiar when it matters.
Monitor both sides
Uptime checks on the public site, error tracking in Next.js, and basic health checks on /cms/ make problems visible before visitors or editors report them.
A headless setup is more work than a classic WordPress site, and I would not recommend it to every company. When the brand, performance, and release safety matter, and there is someone to own the technical side, it gives editors a familiar tool and gives visitors a faster, more reliable site.