Skip to content
weLabsweLabsStorefront Playbook
Live demoQuickstart

Storefront setup

Deployment

Deploy the storefront to Cloudflare Workers with OpenNext or to any Node host, with the right environment, proxy and SITE_URL settings.

The storefront deploys to Cloudflare Workers through OpenNext, which is the setup the repository ships with, or to any host that runs Node 22. Either way it is a single app with no database of its own: it needs its environment variables and a route to your WordPress site, nothing else.

Whatever the host, a production storefront needs these at minimum:

COMMERCE_SOURCE=woocommerce
WOO_STORE_URL=https://cms.example.com
RSC_API_URL=https://cms.example.com/wp-json/retail-store-companion/v1
AUTH_SECRET=your-generated-secret
SITE_URL=https://shop.example.com
STOREFRONT_SHARED_SECRET=same-value-as-RSC_STOREFRONT_SHARED_SECRET
WOO_CONSUMER_KEY=ck_your_key
WOO_CONSUMER_SECRET=cs_your_secret

Add the look and behaviour variables you chose in Configuration. Treat AUTH_SECRET, STOREFRONT_SHARED_SECRET and WOO_CONSUMER_SECRET as secrets.

The app builds into a Cloudflare Worker with OpenNext. The Worker’s settings are in apps/storefront/wrangler.jsonc: its name (retail-store-frontend), the nodejs_compat and global_fetch_strictly_public compatibility flags, the static assets binding and observability.

The scripts live in the app’s package.json, so run them from apps/storefront:

Script What it does
pnpm cf:build Builds the Worker into .open-next/
pnpm cf:preview Builds, then runs the Worker locally in Cloudflare’s runtime
pnpm cf:deploy Builds and deploys a new version
pnpm cf:upload Builds and uploads a version without making it live
pnpm cf:typegen Regenerates the Cloudflare environment types
  1. Sign in to Cloudflare from the app folder.

    Terminal window
    cd apps/storefront
    pnpm exec wrangler login
  2. Check the build locally.

    Terminal window
    pnpm cf:preview
  3. Deploy.

    Terminal window
    pnpm cf:deploy
  4. Set the environment variables in the Cloudflare dashboard, on the Worker’s Settings → Variables and secrets. Store AUTH_SECRET, STOREFRONT_SHARED_SECRET and WOO_CONSUMER_SECRET as secrets. Saving deploys a new version with the values.

  5. Add your domain on the Worker’s Settings → Domains & Routes, then set SITE_URL to that address.

Variables live in the dashboard, not in git. wrangler.jsonc sets keep_vars: true, so a deploy keeps the dashboard’s variables instead of wiping them. The trade-off is that they are not versioned: set them up by hand for every environment, and keep a record of what each one holds.

Continuous deployment runs on Cloudflare Workers Builds, connected to the repository. The Worker name in wrangler.jsonc must match the Worker the Workers Builds project deploys to. If they differ, CI deploys to its own name and offers a pull request to reconcile, and a local pnpm cf:deploy would update a different Worker. Variables that start with NEXT_PUBLIC_ are compiled in at build time, so give the build step those values too.

Most deployments sit behind something: Cloudflare, a load balancer or nginx. Two things need to be right.

  • Sign-in must see the public host. Set AUTH_TRUST_HOST=true so Auth.js trusts the forwarded host rather than the address the app sees internally. Do the same behind a tunnel.
  • The shopper’s IP must arrive in X-Forwarded-For. With STOREFRONT_SHARED_SECRET set, the storefront signs the first X-Forwarded-For address (or X-Real-IP) and sends it to WordPress so payment rate limits count shoppers. The proxy must set this header, not append to a value the visitor sent, or a visitor could choose their own address. Cloudflare sets it for you.

For nginx in front of a Node host, that looks like:

location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
}

SITE_URL is the storefront’s public address, such as https://shop.example.com, without a trailing slash. The storefront never trusts the request’s Host header for this. It builds from SITE_URL:

  • canonical addresses and the links between Locales (hreflang),
  • Open Graph and X sharing tags,
  • /sitemap.xml, /robots.txt and /llms.txt.

Only the origin is used; a path after the domain is ignored. In development it defaults to http://localhost:3000 (or your PORT). In production there is no default: unset or invalid, pages print no canonical address, sharing address or language links, and the server log warns once. Set it on every production and staging deployment, to that deployment’s own address.

A staging storefront should look like production and touch nothing real.

  • Connect it to a staging WordPress with payment gateways in test or sandbox mode, never to the live store.
  • Set STOREFRONT_CUSTOM_SCRIPTS=off so test visits never reach live analytics or ad pixels.
  • Give it its own SITE_URL, AUTH_SECRET and WooCommerce REST key pair.
  • Set the staging WordPress’s Storefront URL to the staging storefront, so emails and preview links point there.
  • Put staging behind access control (Cloudflare Access, HTTP authentication at the proxy) so search engines and the public stay out.
  • Card wallets (Apple Pay, Google Pay) need HTTPS and a domain registered with Stripe. The Companion plugin registers the Storefront URL automatically, but never local or test hosts.

The repository’s GitHub Actions check every push to develop and main and every pull request. They do not deploy; Cloudflare Workers Builds does.

Workflow job What it runs
checks pnpm typecheck, pnpm lint, a right-to-left styling guard, pnpm test
smoke The full Playwright smoke suite in Demo Mode
family-smoke The design-bearing tests, once per Template Family
locale-smoke The language tests on an English and Arabic store
address-legacy-smoke Checkout and account tests without the Address Book
offline-only-checkout-smoke Checkout tests with only offline payment methods

A second workflow, WooCommerce contract (payment), runs the contract suite against a real, disposable WooCommerce store every Monday at 03:00 UTC and on demand. It places test orders, so it needs its own store and the repository secrets WOO_CONTRACT_STORE_URL, WOO_CONTRACT_CONSUMER_KEY, WOO_CONTRACT_CONSUMER_SECRET and WOO_CONTRACT_STRIPE_SECRET_KEY. See Testing.

Storefront Playbook · Built by weLabsFeaturesFAQTalk to us