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":
- Customer hits
https://dev.fruitplug.co.uk/p/mangosteen— Caddy proxies to the Next.js PWA. - PWA calls
/wp-json/wc/store/v1/products?slug=mangosteen(Woo Store API). SSR renders the PDP. - Customer clicks Add to Cart → PWA client posts to
/api/cart/addon our own origin. - The Next.js Route Handler forwards to
/wc/store/v1/cart/add-itemwith the cart token + nonce from our HttpOnly cookies. - Woo adds the item, returns the refreshed cart + new token+nonce. We write cookies back to the browser.
- At checkout: Stripe Elements collects the card → PWA POSTs to Woo checkout → Woo creates the order → WooPayments captures → ShipStation picks it up.
- On
woocommerce_order_status_completed, thefruitplug-apihook 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.