Service modes
This is the single most useful thing to know on day one.
Every commerce and pages domain in a theme — products, categories, prices, inventory, cart, orders, accounts, addresses, organizations, teams, auth, payment, shipping, markets, order lists — resolves through a service key. In development every one of those keys defaults to mock, served from fixture data bundled with the base layer.
npm install
npm run dev
That's it. You get a working storefront with a catalog, product pages, a cart, and a checkout — with no API key, no tenant, and not a single environment variable.
The practical consequence: onboarding a developer onto a theme project is a git clone and an npm install. Don't provision credentials before you need them. You can build blocks, style pages, review layouts with a designer, and demo the thing on a plane.
How the switch works
Each domain has a key in app.config.ts. The base layer ships every key set to mock:
export default defineAppConfig({
productService: 'mock',
categoryService: 'mock',
priceService: 'mock',
inventoryService: 'mock',
cartService: 'mock',
orderService: 'mock',
accountService: 'mock',
authService: 'mock',
// …and so on for every domain
})
Your theme's own app.config.ts merges over that, key by key. Setting a key to api swaps in the implementation that calls the platform through the public gateway:
export default defineAppConfig({
productService: 'api',
categoryService: 'api',
// everything you don't mention stays on mock
})
Two values per domain, and the second one is the same word everywhere:
| Key value | What it resolves to |
|---|---|
mock | Bundled fixture data, served from the layer's own assets. No network, no credentials. |
api | The live implementation, calling the platform through https://api.revenexx.com from your server routes. |
The domains
| Key | Domain |
|---|---|
productService | Product list and product detail |
categoryService | Categories and the category tree |
priceService | Prices |
inventoryService | Stock and availability |
cartService | The cart, and multi-cart when live |
orderService | Orders |
orderListService | Saved order lists |
accountService | The buyer's profile |
addressService | The address book |
organizationService | The buyer's organization |
contactsService | Contacts within the organization |
authService | Login, registration, recovery |
paymentService | Payment methods |
shippingService | Shipping methods and rates |
marketService | Markets and currencies |
themeService | The tenant's branding |
schemaService sits alongside these and resolves the form schemas the checkout renders; its default is local-file.
Why per-domain, not one global switch
Because "all mocks" and "full production wiring" are both the wrong place to be for most of a project.
Flip productService and categoryService to api while you build the product pages against a customer's real catalog, and leave the cart, orders, and account on mock — so you're not creating real orders in a customer's tenant every time you test the checkout button. Then flip the cart when you get to the cart.
This is also how you isolate a problem. If the product grid breaks after you go live, set productService back to mock for one request: if the grid renders, the bug is in the data path, not the block.
Switching at runtime
You don't have to restart to change modes. The dev panel writes a cookie the server reads per request, so a domain can be switched from the browser while the app is running. Resolution order for each domain:
- A per-domain override in the dev-mode cookie.
- The global mode in the cookie —
livepicks the domain's live key,mockforces the mock. - The key in
app.config.ts. The layer default ismock.
That's a development convenience. The value that ships is the one in app.config.ts.
What api needs from you
Two things, both set as Nuxt runtime config:
NUXT_REVENEXX_API_URL=https://api.revenexx.com
NUXT_REVENEXX_TENANT=<your-tenant-slug>
NUXT_REVENEXX_API_KEY=<your-api-key>
runtimeConfig.public, and it is read exclusively by the theme's server routes. A component that fetches api.revenexx.com directly would need the key in the browser, which is why components call /api/* on your own origin instead. See The BFF pattern and Authentication.On a deployed site you don't set these by hand at all: the platform injects a scoped key and endpoint for you. See Environment variables.
In production
Production builds run with the keys set to api, so the mock path never serves a customer. That's a configuration fact rather than a build-time one — the mock implementations are still in the bundle, they're just not selected.
Two habits keep this honest:
- Commit the production
app.config.ts. The keys that ship should be in version control, not set by whoever last ran the deploy. - Check it on a preview. A preview host runs the real build with the real configuration. If a domain is still on
mockthere, you'll see fixture product names — which is much easier to catch before a domain points at the site. See Previews.
Next steps
- Local development — the dev loop and the full environment variable set.
- The BFF pattern — where the
apiimplementations actually run, and why. - Catalog, cart and checkout — the surfaces these domains back.
- Overriding the base layer — replacing an implementation rather than switching one.