Blog

The stack behind gramsstyling.com: Medusa v2 and Next.js on Cloudflare Workers

How we replaced a WooCommerce plugin stack with a headless architecture: Medusa as the commerce engine, a Next.js storefront on Cloudflare Workers, and the shop's own logic as reusable modules.

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:

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.

Read next

ServicesBlogMarketplaceAchievementsClientsContact