Cart & sessions¶
Architecture¶
flowchart LR
Browser --"fetch('/api/cart/add')"--> Route[Next.js Route Handler]
Route --"POST with Cart-Token + Nonce"--> Woo[Woo Store API]
Woo --"Set Cart-Token + Nonce"--> Route
Route --"Set HttpOnly cookies"--> Browser
Route -->|JSON body| Browser
The PWA never talks to Woo directly from the browser. Instead:
fp_cartHttpOnly cookie holds the Woo Store APICart-Token(session id)fp_nonceHttpOnly cookie holds the Woo REST nonce (CSRF)- Every client call goes to
/api/cart/*on our own origin — no CORS - The route handler forwards the token+nonce to Woo, captures any refreshed values from the response, and writes them back to the cookie
Code lives in apps/web/lib/woo-proxy.ts and apps/web/app/api/cart/.
Client state¶
Zustand store at apps/web/stores/cart.ts:
The header's <CartCount /> component subscribes to the count and updates in real time.
API surface¶
| Route | Method | Forwards to |
|---|---|---|
/api/cart |
GET | /wc/store/v1/cart |
/api/cart/add |
POST | /wc/store/v1/cart/add-item |
/api/cart/update |
POST | /wc/store/v1/cart/update-item |
/api/cart/remove |
POST | /wc/store/v1/cart/remove-item |
Server-side cart reads¶
For the SSR cart page (app/cart/page.tsx), we can't use the route-handler-based proxy (cookies are writable only in route handlers). Instead readCart() uses a lighter variant that reads the cookie but skips writing back:
export async function readCart(): Promise<unknown | null> {
const cartToken = (await cookies()).get("fp_cart")?.value;
const res = await fetch(base() + "/cart", {
headers: cartToken ? { "Cart-Token": cartToken } : {},
cache: "no-store",
});
return res.ok ? res.json() : null;
}
Stale by design; the client hydrates via /api/cart on mount.
Gotcha: mutations need the nonce¶
Woo Store API returns 401 woocommerce_rest_missing_nonce if a mutation arrives without a Nonce header. The proxy auto-primes it by doing a GET /cart first if no nonce is stored yet — that GET returns both Cart-Token and Nonce headers.
Cart → order¶
When the user hits Checkout, the cart items are carried into Woo's native checkout via the same Cart-Token. The Stripe checkout UI (planned) is built with Stripe Elements, POSTs directly to Woo's /checkout endpoint with the payment method, and Woo creates the order + fires ShipStation webhook.