Decoupling Data in Vue Storefront 2: Moving from Vuex to Composables
Vue Storefront 2 replaces Vuex with a composable architecture. Learn how useFetch eliminates SSR double-fetching and how to build backend-agnostic business logic using middleware extensions.
09 Jan 2026, 10:48 UTC

The Problem: State Management Bloat and Backend Coupling
In earlier versions of Vue Storefront, data fetching was tightly coupled to Vuex modules. Every domain—products, cart, user—required a dedicated module with actions, mutations, and getters. This created two primary engineering bottlenecks: first, components became heavily dependent on a global store namespace, making unit testing difficult without mocking the entire store. Second, supporting multiple backends (like Magento and Shopify) often required duplicating logic or creating fragile abstraction layers within the store itself.
Thesis: Composables as Portable Business Logic
Vue Storefront 2 shifts the architectural weight from a global state manager to composables—specialized functions like useProduct, useCart, and the low-level useFetch primitive. By encapsulating API calls, loading states, and caching logic into these standalone functions, business logic becomes framework-agnostic. The frontend components no longer care if the data comes from a GraphQL endpoint or a REST API; that complexity is delegated to the middleware layer.
Solving the SSR Hydration Duplicate Request
A common issue in Server-Side Rendering (SSR) is "double-fetching," where the server fetches data to render the HTML, and the client fetches it again upon hydration. Vue Storefront 2 solves this via the useFetch composable, which wraps Nuxt's useAsyncData.
When useFetch executes on the server, the result is serialized into the page payload. Upon client-side hydration, the composable checks for this existing state. If found, it populates the reactive data ref immediately without triggering a new network request. This ensures the user sees a fully rendered page instantly while reducing the load on the backend API.
Worked Example: Implementing a Custom useWishlist Composable
To add a feature not present in the core SDK, you must extend the middleware and then wrap that extension in a composable. This example assumes a Magento backend with a custom GraphQL resolver.
Step 1: Extend the Middleware
Modify your middleware.config.js (running in Node.js) to add the custom resolver. This ensures the logic remains on the server side.
// middleware.config.js
module.exports = {
extensions: [
{
name: 'wishlist',
extendApiMethods: ({ extendQuery }) => ({
getWishlist: extendQuery(async (args, context) => {
const { customerToken } = args;
return context.client.query({
query: 'query getWishlist($token: String!) { wishlist(token: $token) { items { id name } } }',
variables: { customerToken: token },
fetchPolicy: 'no-cache'
});
})
})
}
]
};Step 2: Create the Composable Wrapper
Create src/composables/useWishlist.ts to provide a clean API to your Vue components.
import { useFetch } from '@vue-storefront/core';
export const useWishlist = () => {
const fetch = useFetch((params) =>
context.$magento.api.getWishlist(params)
);
return {
load: fetch.execute,
wishlist: fetch.data,
loading: fetch.loading,
error: fetch.error
};
};Step 3: Component Integration
Use the composable in a <script setup> block. Run npm run dev to verify that the initial load triggers only one request in the Network tab.
<script setup lang="ts">
const { load, wishlist, loading } = useWishlist();
// Trigger fetch on mount
onMounted(() => load({ customerToken: 'user-token-123' }));
</script>
<template>
<div v-if="loading">Loading Wishlist...</div>
<ul v-else>
<li v-for="item in wishlist?.items" :key="item.id">{{ item.name }}</li>
</ul>
</template>Engineering Trade-offs and Limitations
- Type Synchronization: TypeScript types for composables are generated from GraphQL schemas. If the backend schema changes, you must run
npm run generate:typesto avoid runtime mismatches. - Event Loop Blocking: Middleware extensions run in Node.js. Heavy synchronous computation within a resolver (like complex data transformation) can block the event loop, slowing down all concurrent requests.
- Version Variance: Composable method names have evolved (e.g.,
load()vsloadCart()). Always check yourpackage.jsonversion to ensure you are using the correct method signature. - Query Complexity: Deeply nested relations (Product → Variants → Attributes) can lead to expensive GraphQL queries. Implement query cost analysis in the middleware to prevent potential DoS scenarios.
Verification and Next Steps
- Project Setup: Initialize a project using
npx @vue-storefront/cli@latest create(requires Node 18+). - Audit Composables: Inspect
src/composables/to see howuseProductanduseCartare structured. - Hydration Check: Run
npm run dev, open the Network tab, and confirm that navigating back to a previously visited product page does not trigger a duplicate API call. - Unit Testing: Use Vitest to test your custom composables by mocking the middleware client, allowing you to verify business logic without mounting a DOM.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.