Headless WooCommerce B2B2C: Lessons from the Kwely Build

Headless WooCommerce is an architectural decision, not a technical trend to follow for its own sake. I spent three years building the Kwely B2B2C platform on a headless WP/WooCommerce stack with a Vue.js frontend and an AWS-backed CI/CD pipeline. Here is what I actually learned.

Kwely headless storefront
The Kwely storefront: a headless WooCommerce back end behind a modern JavaScript front end.

When Headless WooCommerce Makes Sense

The honest answer: not as often as the blog posts suggest.

Headless WooCommerce makes sense when:

  • The storefront needs to do something the WooCommerce UI cannot. Kwely needed a buyer-type-aware interface that behaved differently for business purchasers and end consumers. Standard WooCommerce has no concept of buyer type at the session level. Building that into a custom theme would have been fighting the framework. A Vue.js frontend that consumed the REST API was cleaner.
  • The frontend team has Vue or React expertise that WooCommerce theme development would waste. Forcing a skilled Vue developer into WooCommerce template PHP is a mismatch. Headless lets each layer use its natural tooling.
  • The deployment model benefits from separation. For Kwely, separating the content/commerce backend from the frontend meant each could be deployed, scaled, and rolled back independently.

If none of those apply, a well-built WooCommerce theme is usually simpler and faster to ship.

The REST API Gaps

WooCommerce’s REST API is good but not complete. For a B2B2C build, I ran into gaps:

  • Customer segmentation — the API has no native concept of buyer type. I extended the REST API to expose a custom customer meta field that the Vue frontend read to determine UI state.
  • Custom pricing rules — WooCommerce’s pricing hooks work beautifully inside PHP. Exposing custom prices to the REST API required a custom endpoint that applied the same rules in API context.
  • Cart state — the WooCommerce REST API’s cart handling (via Store API) was more stable than the older wc/v3 cart endpoints, but still required careful session management across decoupled requests.

None of these are blockers. They are architectural work that the headless approach requires you to do explicitly, rather than relying on WooCommerce to handle.

Vue.js Frontend Architecture

I used Vue 3’s Composition API throughout. For a commerce application, the state management requirements are non-trivial: cart state, user session, buyer type, product filtering, checkout flow. I built a Pinia store for cart and session state, with a clean separation between stores (data) and composables (behaviour).

The Vue/WooCommerce integration pattern I settled on:

  • REST API calls are isolated in a service layer — no fetch() calls scattered through components
  • Cart mutations go through the Store API’s batch endpoint where possible (one round trip for complex cart operations)
  • Authentication uses WooCommerce’s JWT auth plugin, with the token stored in httpOnly cookies via a thin server-side proxy

The proxy decision was important. Putting the WooCommerce REST API credentials in the browser is a bad idea. A lightweight Node/Express proxy on AWS handled auth token exchange and kept secrets server-side.

CI/CD on AWS

The pipeline: GitHub Actions triggered on push to main. Tests ran (PHPUnit for the backend extensions, Vitest for the Vue components). On pass, the backend deployed to an AWS EC2 instance via rsync over SSH; the frontend built and deployed to S3 + CloudFront.

The frontend CDN distribution was the right call. Vue 3’s build output is a static bundle — there is no reason to serve it from a PHP host. CloudFront edge delivery improved global load times measurably.

The Mentorship Component

Junior developers make different mistakes than senior developers. Senior developers make architectural mistakes early and course-correct. Junior developers make implementation mistakes throughout.

On Kwely, I built the architecture and let the junior team implement against it. Code review was daily. The feedback was always about why, not just what — I wanted the team to understand the reasoning so they could make the same call independently next time.

By the end of the engagement, they had a codebase they understood, a deployment pipeline they could operate, and a set of architectural patterns they could apply to new features. That was the intended outcome.