Skip to content

Architecture overview

Two hosts, one product

The system runs across two machines deliberately:

  • The WordPress backend stays on its existing A2 Hosting install so we don't touch 4,188 orders' worth of data.
  • The new PWA lives on a separate server so we can redeploy and experiment without risk to live orders.
Domain Host Role
fruitplug.co.uk A2 Hosting 106.0.62.69 WordPress admin, WooCommerce REST, WooPayments webhooks, ShipStation sync
dev.fruitplug.co.uk This server 147.12.227.117 Next.js PWA (staging)
docs.fruitplug.co.uk This server 147.12.227.117 This wiki

Post-cutover, DNS for fruitplug.co.uk flips to 147.12.227.117, and Caddy proxies /wp-admin, /wp-login.php, /wp-content, /wp-json back to A2 while everything else goes to the PWA.

On the PWA server (147.12.227.117)

flowchart TB
  Internet((Internet :443)) --> Caddy[fatbot-caddy<br/>caddy:alpine]
  Caddy -->|host.docker.internal:3030| Web[fruitplug-web-dev<br/>node:22-alpine + Next.js 16]
  Caddy -->|host.docker.internal:3040| Docs[fruitplug-docs<br/>mkdocs-material]
  Caddy -->|host.docker.internal:8000| FB[fatbot-dashboard]
  Caddy -->|host.docker.internal:8001| FBD[fatbot-docs]
  Caddy -->|host.docker.internal:8050| FBF[footballdesk-app]

Caddy is a shared TLS front door for everything on this server. Auto Let's Encrypt, zero-downtime reloads, config at C:\Users\User\Desktop\Caddy\Caddyfile bind-mounted into the container.

On the WordPress host (A2)

flowchart LR
  subgraph WP [WordPress 6.9 · WC 10.7 · PHP 8.2]
    Woo[WooCommerce]
    Subs[Woo Subscriptions]
    Pay[WooPayments]
    Ship[ShipStation for Woo]
    Drivers[Local Delivery Drivers]
    Status[Custom Order Status Manager]
    FP[fruitplug-api<br/>our custom plugin]
    Plus[+ 14 more<br/>keep list]
  end

  WP --- Woo_DB[(wp_posts · wp_postmeta · wp_users<br/>wp_wc_orders · wp_wc_order_stats)]
  FP --- FP_DB[(wp_fruitplug_points_ledger<br/>wp_fruitplug_streaks<br/>wp_fruitplug_passport<br/>wp_fruitplug_saved_boxes<br/>wp_fruitplug_referrals<br/>wp_fruitplug_push_endpoints<br/>wp_fruitplug_reel_products)]

Everything persistent lives in the WordPress MySQL database. The Next.js app holds no durable state. When a feature needs data Woo doesn't have (loyalty, saved boxes, push subscriptions), the fruitplug-api plugin owns a dedicated table — see Data model.

How a request flows

Take a typical "add mangosteen to cart":

  1. Customer hits https://dev.fruitplug.co.uk/p/mangosteen — Caddy proxies to the Next.js PWA.
  2. PWA calls /wp-json/wc/store/v1/products?slug=mangosteen (Woo Store API). SSR renders the PDP.
  3. Customer clicks Add to Cart → PWA client posts to /api/cart/add on our own origin.
  4. The Next.js Route Handler forwards to /wc/store/v1/cart/add-item with the cart token + nonce from our HttpOnly cookies.
  5. Woo adds the item, returns the refreshed cart + new token+nonce. We write cookies back to the browser.
  6. At checkout: Stripe Elements collects the card → PWA POSTs to Woo checkout → Woo creates the order → WooPayments captures → ShipStation picks it up.
  7. On woocommerce_order_status_completed, the fruitplug-api hook awards Plug Points, stamps the Fruit Passport, and credits any referral.

What's not in this architecture

  • No separate database. No Postgres, no DynamoDB. Everything is in wp_* tables.
  • No standalone auth server. WordPress users are the identity system (JWT overlay planned for mobile apps).
  • No separate API gateway. The WP REST API is the API.
  • No microservices. The PWA is one app; the backend is one monolith (WP + Woo); the data is one DB.
  • No third-party CMS. Content lives in WP pages or MDX in the PWA repo (recipes, preparation guides).

Why this shape

  • Minimal migration risk. 4,188 existing orders, 2,216 customers, 218 reviews stay exactly where they are.
  • Ops familiarity. The back-office team is already trained on wp-admin + ShipStation; nothing changes for them.
  • Best-of-both. A clean React frontend for customers + WordPress's boring-but-proven commerce engine behind it.
  • Reversible. If the headless experiment underwhelms, DNS flip back and the old Butcher theme is still there.