How Vue Storefront’s Server Middleware Proxies Commerce Backends (and How to Configure It)
Vue Storefront’s storefront never calls your commerce backend directly — a separate middleware server holds credentials and normalizes APIs. Here’s how the layer works, with a real config, an extension example, and the mistakes to avoid.
27 Oct 2025, 09:46 UTC

If you are evaluating Vue Storefront (now branded Alokai) or wiring up your first integration, the single most important architectural fact is this: your Nuxt storefront never talks to the commerce backend directly. Every product query, cart mutation, and login call goes through a separate Node.js middleware server that holds the backend credentials and normalizes each platform’s API into one shape. Understanding that layer is the difference between a clean headless build and a leaky, hard‑to‑cache mess.
Why the middleware exists
Commerce platforms like commercetools, Magento, Shopify, and SAP Commerce Cloud all expose different APIs, auth schemes, and data models. Vue Storefront’s answer is a standalone middleware process that loads an integration package per backend. The integration exposes a typed API client; the middleware mounts it as HTTP endpoints; the storefront calls those endpoints through composables such as useProduct, useCart, and useUser.
This buys you three things:
- Secret isolation. API tokens and private keys live only in the middleware process environment. The browser bundle never sees them — this is the primary security rationale for the extra service.
- Backend‑agnostic UI. Page components call composables, not platform SDKs. Swapping commerce engines mostly means changing middleware config, not rewriting templates.
- SSR‑friendly data flow. During server‑side rendering, Nuxt hits the middleware over your internal network, so the same code path works on server and client.
A worked configuration
The middleware is configured in middleware.config.js at the project root. Each entry under integrations names the integration, points at its server package, lists extensions, and carries the backend connection settings. A representative example (exact option names vary per integration — check your integration’s README):
// middleware.config.js — runs ONLY in the middleware Node process
module.exports = {
integrations: {
ct: {
location: '@vue-storefront/commercetools-api/server',
configuration: {
api: {
uri: process.env.CT_API_URL, // e.g. https://api.europe-west1.gcp.commercetools.com
authHost: process.env.CT_AUTH_HOST,
projectKey: process.env.CT_PROJECT_KEY,
clientId: process.env.CT_CLIENT_ID, // secret: never in Nuxt public config
clientSecret: process.env.CT_CLIENT_SECRET,
scopes: [process.env.CT_SCOPES]
},
currency: 'EUR',
locale: 'en-IE',
acceptLanguage: ['en-IE', 'en']
},
extensions: (extensions) => [
...extensions,
require('./extensions/extend-product-query')
]
}
}
};
Once the middleware starts, it mounts endpoints of the form /api/{integration}/{method} — for example POST /api/ct/getProduct. The storefront’s SDK/composable layer calls these over HTTP. You can verify the layer works before touching any UI: start the middleware and POST to an endpoint with curl, or just watch its logs while loading a page.
Customizing behavior with extensions
The supported way to change an integration’s behavior — say, adding a custom field to a product query — is an extension, not a fork. An extension is a function that receives the integration’s existing API and returns modified or additional methods:
// extensions/extend-product-query.js
module.exports = {
name: 'extend-product-query',
extendApiMethods: {
// Override one method; the rest pass through untouched
getProduct: async (context, params) => {
const response = await context.api.getProduct({
...params,
// request the extra projection/field your backend supports
expand: ['productType', 'taxCategory']
});
return response;
}
}
};
Because the override wraps the original client, your composable call ( search on useProduct, for instance) keeps working unchanged — the modification is invisible to the UI.
Limits and common mistakes
The extra hop is real
Every storefront request now travels browser → middleware → backend. That is a second service to deploy, scale, and monitor, and on a high‑traffic catalog it can roughly double backend load if nothing is cached. In practice you want caching at the middleware or CDN level for anonymous catalog traffic. A practical check: load‑test one category page through the middleware and compare backend request counts with and without a cache layer. If counts are identical per page view, your cache is not working.
Leaking credentials through Nuxt config
The most damaging mistake is putting backend tokens in Nuxt’s public runtime config “for convenience.” Anything public is shipped to the browser. Secrets belong only in the middleware process environment, referenced from middleware.config.js via process.env. Verification is easy: open the browser dev tools network tab and confirm the page only calls your middleware domain, and grep the built client bundle for your token string — it must not appear.
Mixing Vue Storefront 1 and 2 patterns
Vue Storefront 1 (Vue 2, core‑based) and the current SDK/middleware architecture share a name and little else. Composable names, config file locations, and integration APIs differ; a tutorial written for one will often not compile in the other. Before following any example, confirm which major version it targets.
Fragile extensions after upgrades
An extension that overrides getProduct depends on that method’s signature. If an integration package upgrade changes the signature, your override can fail silently or pass wrong arguments. Pin integration versions, and after any bump, run a smoke test that calls each overridden method. A cheap CI check is one test per override asserting the custom behavior still executes.
Assuming uniform composable behavior
Pagination defaults, filter syntax, and retry/error handling are implemented per integration, not by the shared core. useProduct against commercetools does not paginate identically to useProduct against Magento. Read the specific integration’s docs before building listing logic, and test filters against your actual backend rather than trusting the generic API surface.
How to verify your setup end to end
- Start the middleware alone with a mocked or sandbox backend URL and confirm
/api/{integration}/{method}responds before wiring up Nuxt. - Load a catalog page and inspect the network tab: all commerce calls should target your middleware host, and no backend token should appear in requests or the JS bundle.
- Add one extension overriding one method, call it from a composable, and confirm the override runs; re‑run after the next integration upgrade.
- Load‑test a catalog page and compare backend hit counts with caching on and off to quantify the middleware hop’s cost.
Version note: this describes the Vue Storefront 2 / SDK‑and‑middleware architecture as of late 2024. Package names and exact config keys drift between releases, so treat the snippets as a pattern and confirm option names against your integration’s current documentation before shipping.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.