N
HeadlessCoreHeadless Core
Back to Home

Deployment

HeadlessCore splits into two deployable units: WordPress (backend) and Next.js (frontend).


Architecture overview

                    ┌─────────────────┐
   Editors ────────▶│  WordPress      │
                    │  (PHP + MySQL)  │
                    └────────┬────────┘
                             │ WPGraphQL (HTTPS)
                             ▼
                    ┌─────────────────┐
   Visitors ───────▶│  Next.js        │
                    │  (Vercel/Node)  │
                    └─────────────────┘
                             ▲
                             │ POST /api/revalidate
                    WordPress webhooks

WordPress hosting

Any PHP 8+ host with MySQL:

  • Shared hosting (cPanel, Plesk)
  • VPS (DigitalOcean, Linode, Hetzner)
  • Managed WP (optional — you only need admin + GraphQL, not public theme)

Checklist

  • HTTPS enabled
  • HeadlessCore theme active
  • WPGraphQL + JWT plugins active
  • Permalinks set to "Post name"
  • Next.js URL set in theme settings
  • Revalidation + preview secrets configured
  • Contact webhook URL set (optional)

Hardening

See Security Guide and Production — aaru.live.

  • Restrict /wp-admin by IP or Cloudflare Access
  • Disable XML-RPC if unused
  • Keep WordPress core, plugins, and PHP updated
  • Use strong secrets (openssl rand -base64 32)

Next.js hosting

Recommended: Vercel (zero-config for Next.js App Router).

Alternatives: Netlify, Railway, DigitalOcean App Platform, self-hosted Node.

Environment variables (production)

NEXT_PUBLIC_WORDPRESS_API_URL=https://cms.yourdomain.com/graphql
NEXT_PUBLIC_WORDPRESS_URL=https://cms.yourdomain.com
NEXT_PUBLIC_SITE_URL=https://yourdomain.com
REVALIDATION_SECRET=<same as WordPress>
PREVIEW_SECRET=<same as WordPress>
WORDPRESS_AUTH_TOKEN=<JWT for draft preview>

Build command

cd nextjs-starter-kit && npm ci && npm run build

Custom domain

Point yourdomain.com to Vercel. WordPress can live on cms.yourdomain.com or a subdomain.

Update next.config.ts images.remotePatterns if WordPress is on a different domain.


Revalidation in production

WordPress must reach your Next.js /api/revalidate endpoint. On Vercel this is automatic.

  1. headlesscore_nextjs_url = https://yourdomain.com
  2. Firewall allows outbound HTTPS from WordPress host to Next.js
  3. REVALIDATION_SECRET matches on both sides

Test after deploy:

curl -X POST https://yourdomain.com/api/revalidate \
  -H "Content-Type: application/json" \
  -d '{"secret":"YOUR_SECRET","path":"/blog","tag":"posts"}'

See API Routes and Troubleshooting.


Preview mode

Preview links require:

  • PREVIEW_SECRET set in Next.js (matches WordPress)
  • WORDPRESS_AUTH_TOKEN (JWT) for authenticated draft GraphQL queries

Generate JWT via WPGraphQL login mutation with the JWT Authentication plugin.


CI/CD

HeadlessCore includes a GitHub Actions workflow at .github/workflows/ci.yml.

What runs on push/PR to main:

  1. npm ci in nextjs-starter-kit/
  2. npm run lint
  3. npm test (Vitest — 9 unit tests)
  4. npm run build

Suggested production pipeline

# .github/workflows/deploy.yml (example)
name: Deploy Next.js

on:
  push:
    branches: [main]

jobs:
  test-and-build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
          cache-dependency-path: nextjs-starter-kit/package-lock.json
      - run: cd nextjs-starter-kit && npm ci
      - run: cd nextjs-starter-kit && npm run lint
      - run: cd nextjs-starter-kit && npm test
      - run: cd nextjs-starter-kit && npm run build
        env:
          NEXT_PUBLIC_WORDPRESS_API_URL: ${{ secrets.WP_GRAPHQL_URL }}
          NEXT_PUBLIC_WORDPRESS_URL: ${{ secrets.WP_URL }}
          NEXT_PUBLIC_SITE_URL: ${{ secrets.SITE_URL }}

  deploy:
    needs: test-and-build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # Deploy to Vercel, Netlify, or your host

WordPress deploys via git pull, rsync, or host-specific pipeline.

See Testing for test details.


Docker (dev/staging)

docker compose up -d
  • WordPress: http://localhost:8080
  • Theme volume-mounted from repo

For production, use separate managed services instead of Docker Compose.


Docker Compose in production (Coolify or any Docker host)

docker-compose.prod.yml at the repo root deploys WordPress as a production-ready container. Point a Coolify "Docker Compose" resource at it (or docker compose -f docker-compose.prod.yml up -d on any Docker host), with env vars from .env.production.example.

Git auto-deploy on Coolify: connect GitHub, enable Auto Deploy on both the Docker Compose (CMS) and Nixpacks (Next.js) resources. See Coolify Deploy for the full aaru.live setup (repo root build, env vars, two-resource layout).

Remote MySQL

On a small VPS (e.g. a 2GB Linode), running MySQL as a container next to WordPress competes with it for RAM. If you'd rather offload the database — to a managed MySQL service (Linode Managed Database, RDS, etc.) or a separate small VPS running just MySQL — use docker-compose.prod.remote-db.yml instead. It's identical to docker-compose.prod.yml minus the local db service; WordPress connects out to REMOTE_DB_HOST (accepts host:port) instead.

docker compose -f docker-compose.prod.remote-db.yml up -d

Fill in the REMOTE_DB_* vars in .env.production.example (host, user, password, database name) instead of the local MYSQL_* ones. Two things to check with your provider before switching:

  • Firewall / allowed IPs — the remote MySQL must accept connections from your WordPress host's IP.
  • TLS — most managed MySQL requires SSL. The official WordPress Docker image supports this via mysqli_ssl_set() in a WORDPRESS_CONFIG_EXTRA snippet (see the wordpress:php image docs) if your provider mandates it; many allow non-SSL connections from an allow-listed IP as a simpler alternative for a single-server setup.

In Coolify: create the resource the same way, just set the Compose file path to docker-compose.prod.remote-db.yml and use the REMOTE_DB_* env vars instead of MYSQL_*.


Monitoring

Layer What to monitor
Next.js Vercel Analytics, error tracking (Sentry)
WordPress Uptime on /graphql endpoint
Revalidation Test by publishing a post after deploy
Contact form Check Contact Logs CPT + webhook delivery

vs WP Engine Atlas

HeadlessCore WP Engine Atlas
Hosting Self-hosted Managed
Cost Server only Platform fees
WP admin Standard Standard
Frontend Next.js (you own) Faust.js / Next.js
Vendor lock-in None Platform-dependent
Site modes 8 built-in Custom per project
CI/tests Vitest + GitHub Actions Platform-dependent

You get the same decoupled architecture with full control over code and infrastructure.