This is the engineering companion to our case study on moving G.R.A.M.S Styling off WooCommerce. The migration is in progress: the stack below is built, loaded with the real data, and runs behind Cloudflare Access until launch.
Before and after
On WooCommerce, one PHP server did everything: it rendered every page, ran the database, and hosted dozens of plugins for currencies, shipping, product add-ons, caching, search, mail, marketplace sync and more. Every feature was someone else's code sharing one process and one database table of options, and every visitor waited for that server.
The new architecture separates three things. The storefront runs on Cloudflare Workers, close to the visitor, and serves cached pages. The commerce engine, Medusa, runs on a small server and only answers the requests that need it: carts, checkout, accounts, the admin. Everything specific to this shop is written as Medusa modules, versioned and tested like any other code. The diagram above shows the two setups side by side.
The commerce engine: Medusa v2
Medusa gave us the pieces a shop needs and nothing else: regions and currencies, carts, orders, fulfilments, customers, an admin UI, and a module system to extend all of it. It runs as a Docker Compose stack with Postgres and Redis, managed by Coolify on a 2 vCPU server. A GitHub Actions workflow builds and scans the image on every merge, and the server only pulls it, so a deploy takes about a minute and a half. The database is dumped every hour and copied off-site, and the restore has been tested.
The shop's own behaviour lives in a set of MIT-licensed modules we wrote to be reusable on other Medusa shops:
- Delivery prices from the cart weight and a per-country rate table, the same rules the shop always used.
- Product add-ons (fitting kits, end plates, accessories) as real variants with their own price.
- PayPal and Revolut payments on the official SDKs, with verified webhooks and refunds.
- Customer passwords verified against the WordPress hashes at first login and upgraded silently, so nobody resets anything.
- Transactional mail through the mailbox the shop already uses.
- Cloudflare Turnstile on every public form, failing closed in production.
- Order tools: capture, ship with tracking, box presets, parcels per order, team notes.
- A workshop production board per site, with marketplace orders arriving as cards.
- Live courier quotes, one-click booking and label storage.
- A small CMS for pages, site texts, the blog and the customer car gallery, with translations.
- Product feeds for Google Merchant Center, Meta and Pinterest.
- An AI shop assistant that maps a car and a part to the right category and variants, with a plain picker as fallback.
The storefront: Next.js 16 on Cloudflare Workers
The storefront is a Next.js App Router application deployed as a Cloudflare Worker, with pages cached at the edge and revalidated in the background, so the commerce engine sees a small share of the traffic. The URL carries the market (/uk, /eu, /us, and language variants such as /eu-fr), with canonical and hreflang per market. Middleware handles the redirects from the old shop, the region suggestion by country, the language cookie and the security headers.
The blog is a separate static site rebuilt automatically when an editor saves a post in the Medusa admin. The workshop board is a static page behind Cloudflare Access.
How the data moved
Every record imported from WooCommerce keeps its original identifier in metadata, so the comparison between the old shop and the new one can be re-run at any time: products, variants, prices, weights, customers, orders, payments, refunds and tracking numbers were checked by id, and the last run showed no differences on prices. Orders placed on the old shop before launch are imported one by one.
What we would do again
This is the architecture we now propose to any business that needs a shop with global reach, edge speed and per-market targeting, with the quality of a Shopify-class store and no percentage taken on each sale. Headless was the right call for this catalogue. The owner's shipping rules, add-ons and finishes did not fit any hosted platform, and writing them as small modules kept each one testable. Putting the storefront on Workers removed a whole class of hosting work. And keeping the old identifiers made every audit a script instead of a guess.
