Headless WooCommerce: The architecture decisions that matter
Whether to go headless
Start by asking what the existing WooCommerce stack costs us that a headless rebuild would fix. If the answer is "the storefront is slow", that's solvable inside WordPress with caching and a CDN, no rebuild needed. If the answer is "we want to ship the same catalog to a mobile app, a kiosk, and a marketing site", headless starts to earn its keep.
If the rebuild makes sense, these architecture decisions determine whether the project ships in three months or twelve.
Decision 1: REST vs GraphQL
WooCommerce ships with a mature REST API. WPGraphQL + WooGraphQL adds a GraphQL layer on top. Both work, and the choice depends on your team and your read patterns.
Pick REST when: the storefront does mostly category and product page fetches, your team has no GraphQL experience, or you want to avoid the N+1 query performance trap that bites unoptimized GraphQL resolvers under load.
Pick GraphQL when: the frontend needs to compose data from products, categories, posts, and ACF fields in a single request, or when you're already running GraphQL elsewhere in the stack.
On a recent project, I migrated from GraphQL to REST mid-build. The reason was that WPGraphQL's variable-product queries were generating 80+ SQL queries per request, and the fix required patching WooGraphQL's resolvers. Falling back to two REST calls plus client-side composition was faster to ship and easier to cache at the CDN edge.
Decision 2: cart on WordPress or cart on frontend
This decision shapes the rest of the project. Every other architecture choice bends around it.
Cart on WordPress (CoCart, Store API): sessions, shipping, taxes, coupons, payment all stay in WooCommerce. The frontend is a view layer. Pros: zero risk of cart divergence from order; all extensions (subscriptions, bookings, bundles) work; payment methods stay PCI-scoped to WordPress. Cons: every cart interaction is a network round-trip to WordPress, and WordPress becomes a hard runtime dependency for the frontend.
Cart on frontend (custom Stripe/Square integration): the cart lives in the frontend (Zustand, localStorage), and the order is created on WordPress only at checkout completion. Pros: instant cart interactions, frontend can work briefly during WordPress downtime. Cons: you reimplement every WooCommerce cart feature (shipping, taxes, coupons, stock validation), which is months of work and a permanent maintenance tax.
For 90% of projects, cart-on-WordPress via Store API is the right answer. The latency cost is manageable with optimistic UI updates. Reimplementing WooCommerce's cart logic is a trap.
Decision 3: ISR, SSR, or static
Next.js offers all three. The pattern I've settled on for medium catalogs (under 50k SKUs):
- Static + revalidate: product pages, category pages, marketing pages.
generateStaticParamsfor top-100 products, ISR with a 5-minute revalidate window for the long tail. - Server-rendered: search results, account pages, anything authenticated.
- Client-side: cart, mini-cart, recently viewed.
Static with revalidate is what makes headless feel fast. A product page that's been generated once serves from the CDN in <100ms regardless of WordPress load. The 5-minute window means stock counts may be slightly stale, but a webhook from WooCommerce on stock change triggers an on-demand revalidate within seconds for popular SKUs.
Decision 4: hosting split
The frontend wants edge caching, instant deploys, and global distribution (Vercel, Netlify, Cloudflare Pages). WordPress wants a beefy origin with predictable database performance (Kinsta, WP Engine, or a tuned VPS). Putting them on the same host serves neither well.
The split I use: WordPress on Kinsta (admin and REST/GraphQL endpoint), Next.js on Vercel (frontend), Cloudflare in front of both. WordPress hostname is treated as an internal API; the public domain points at Vercel.
Decision 5: how authentication works
WooCommerce assumes WordPress sessions: a PHP session cookie, set by WordPress, read by WooCommerce. Headless breaks that.
JWT auth works here, via the jwt-authentication-for-wp-rest-api plugin (or a custom equivalent), with the JWT stored in an HttpOnly cookie set by the Next.js server. The frontend forwards this cookie on every API call; WordPress validates and returns the user-scoped data.
For checkout specifically, CoCart's cart-key-based guest session is needed. New visitors get a cart key on first add-to-cart, and that key persists in localStorage until login, at which point the guest cart merges into the user cart.
Decision 6: what the migration looks like
You don't rebuild a live store overnight. I've used this pattern twice now:
- Subdomain launch: ship the headless frontend to
new.example.com. Both stores share the same WooCommerce backend, so orders flow into the same admin. - Soft migrate top SKUs: redirect the top 50 product URLs to the new frontend. Monitor conversion, fix what breaks.
- Full cutover: swap DNS, set up 301 redirects for any URL structure changes, keep WordPress at
shop.example.comfor admin.
Sharing the backend during migration lets you ship in stages without holding two databases in sync. The admin team keeps working in WordPress, and the storefront only changes which frontend renders the catalog.
Operational reality
Headless WooCommerce adds runtime dependencies. A broken WPGraphQL plugin update can take down your frontend. A misconfigured CDN can serve stale stock. A WordPress migration with a different domain breaks every permalink field in the frontend cache.
The fix is monitoring and graceful degradation: health checks on the WordPress endpoint, fallback static catalog snapshots for true outages, and a frontend that can show "checkout temporarily unavailable" without 500-ing the whole page.
