Where to Put Logic in Vue Storefront: Middleware vs. Composables
When building with Vue Storefront, the biggest hurdle is deciding whether the transformation logic should live in the frontend composables or the server middleware.
29 Apr 2026, 03:26 UTC

When building a headless commerce storefront with Vue Storefront, developers often hit an architectural wall: where should the transformation logic live? If you need to fetch product data from a commerce engine, format it for your UI, and apply a business‑specific discount calculation, do you write that code in a Vue composable or in the Node.js server middleware?
The answer determines your security posture and your ability to scale. In the modern composable‑era architecture of Vue Storefront, the rule of thumb is to use the middleware for data normalization and secret management, while keeping composables focused on UI‑state and reactive logic.
The Role of Server Middleware as a BFF
The Vue Storefront server middleware acts as a Backend‑for‑Frontend (BFF). It sits between your Nuxt‑based frontend and the various commerce APIs (like Shopify, Commercetools, or SAP). Because this middleware runs on the server‑side, it is the only safe place to handle sensitive API keys and credentials.
By placing logic here, you achieve three things:
- Security: Secrets never reach the client browser.
- Normalization: You transform inconsistent API responses from different backends into a single, unified schema that your frontend understands.
- Performance: You can aggregate multiple API calls into a single internal request, reducing the number of hops the client must make.
The Role of Frontend Composables
Composables (like useCart or useProduct) are the bridge to your Vue components. Their job is to manage the reactive state of the application. They handle loading states, error messages, and the local interaction with the data.
If you put heavy data transformation or direct API calls inside a composable, you risk exposing secrets and bloating the frontend bundle. Furthermore, if you later decide to build a mobile app using the same back‑end, you would have to rewrite all that logic trapped in the web‑specific frontend.
Worked Example: Merging Product Data
Imagine you need to display a product page that includes data from your commerce engine plus real‑time loyalty points from a separate microservice. Instead of making two calls from the frontend, we handle this in the middleware.
1. Define the Middleware Extension
In your middleware configuration (typically within the src/server directory), you define a custom endpoint that merges these sources. This is an illustrative representation of a Node.js handler:
// server/middleware/custom-product-loyalty.js
export default async function handleRequest(req, res) {
const { id } = req.params;
// Fetch base product from Commerce Engine
const productResponse = await fetch(`${process.env.COMMERCE_API}/products/${id}`, {
headers: { 'Authorization': `Bearer ${process.env.COMMERCE_KEY}` }
});
const product = await productResponse.json();
// Fetch loyalty points from separate service
const loyaltyResponse = await fetch(`${process.env.LOYALTY_API}/points/${req.session.userId}`);
const loyalty = await loyaltyResponse.json();
// Normalize the data for the frontend
const merged = {
...product,
loyaltyPoints: loyalty.points,
displayPrice: calculateDiscount(product.price, loyalty.points)
};
res.json(merged);
}
2. Consume the Endpoint from a Composable
In your Nuxt app you can now create a lightweight composable that simply calls this endpoint:
// composables/useMergedProduct.js
import { useFetch } from '#app';
export default function useMergedProduct(productId) {
const { data, pending, error } = useFetch(`/api/custom-product-loyalty/${productId}`);
return { data, pending, error };
}
Notice that the composable only deals with the final shape of the data; all heavy lifting happens in the middleware.
3. Verify the Flow
- Deploy the middleware and ensure it logs the incoming request.
- Call the endpoint from your browser or Postman and confirm the merged JSON appears.
- In the Nuxt component, use
useMergedProductand check thatdata.value.loyaltyPointsis populated.
Trade‑Offs and Limitations
While the middleware offers clear benefits, it introduces an extra network hop and a second deployment target. For a single‑storefront scenario with no sensitive data, the overhead may outweigh the advantages. Additionally, normalization can hide platform quirks; if a backend feature has no equivalent in the shared model, you either extend the model or bypass the middleware for that call.
Actionable Takeaway
When deciding where to put logic:
- Put secret‑handling, multi‑service orchestration, and data normalization in the middleware.
- Keep UI state, reactive helpers, and simple API wrappers in the frontend composables.
- Always verify the middleware’s behavior in a sandbox before promoting to production.
By following this pattern you keep secrets safe, reduce client complexity, and create a single source of truth that can be reused across web, mobile, and other channels.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.