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.
Production environment
Section titled “Production environment”Whatever the host, a production storefront needs these at minimum:
COMMERCE_SOURCE=woocommerceWOO_STORE_URL=https://cms.example.comRSC_API_URL=https://cms.example.com/wp-json/retail-store-companion/v1AUTH_SECRET=your-generated-secretSITE_URL=https://shop.example.comSTOREFRONT_SHARED_SECRET=same-value-as-RSC_STOREFRONT_SHARED_SECRETWOO_CONSUMER_KEY=ck_your_keyWOO_CONSUMER_SECRET=cs_your_secretAdd the look and behaviour variables you chose in Configuration. Treat AUTH_SECRET, STOREFRONT_SHARED_SECRET and WOO_CONSUMER_SECRET as secrets.
Deploy
Section titled “Deploy”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 |
-
Sign in to Cloudflare from the app folder.
Terminal window cd apps/storefrontpnpm exec wrangler login -
Check the build locally.
Terminal window pnpm cf:preview -
Deploy.
Terminal window pnpm cf:deploy -
Set the environment variables in the Cloudflare dashboard, on the Worker’s Settings → Variables and secrets. Store
AUTH_SECRET,STOREFRONT_SHARED_SECRETandWOO_CONSUMER_SECRETas secrets. Saving deploys a new version with the values. -
Add your domain on the Worker’s Settings → Domains & Routes, then set
SITE_URLto 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.
Any server, container or platform that runs Node 22 or newer works: a VPS, Docker, or a managed Node service.
-
Install on the server, from the repository root.
Terminal window corepack enablepnpm install --frozen-lockfile -
Provide the environment: an
apps/storefront/.envfile, or the service’s own environment settings. -
Build. Variables that start with
NEXT_PUBLIC_must be set at this point, because they are compiled in.Terminal window pnpm build -
Start. The server listens on port 3000; set
PORTto change it.Terminal window pnpm startPORT=3001 pnpm start -
Keep it running under a process manager such as systemd, PM2 or your platform’s equivalent, behind a reverse proxy that terminates HTTPS.
Change a variable, then restart the server. Change a NEXT_PUBLIC_ variable, then rebuild and restart.
Behind a proxy
Section titled “Behind a proxy”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=trueso 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. WithSTOREFRONT_SHARED_SECRETset, the storefront signs the firstX-Forwarded-Foraddress (orX-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
Section titled “SITE_URL”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.txtand/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.
Staging
Section titled “Staging”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=offso test visits never reach live analytics or ad pixels. - Give it its own
SITE_URL,AUTH_SECRETand 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.
Continuous integration
Section titled “Continuous integration”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.