The New Default. Your hub for building smart, fast, and sustainable AI software
Table of Contents
Editor's note: This article was originally written and published in December 2024. It was reviewed and updated in October 2026 to reflect current versions of Pinia, Vuex and Nuxt.
Vue.js modular state management means splitting your application's shared state into small, feature-focused stores instead of one large global store. In Vue.js applications development, state management (how data is stored, shared and updated) decides whether a growing codebase stays clear, fast and scalable, or turns into a tangle of interconnected components.
As the Vue documentation states, each Vue component instance already "manages" its own reactive state. It is a self-contained unit with the following parts:
The state, the source of truth that drives our app;
The view, a declarative mapping of the state;
The actions, the possible ways the state could change in reaction to user inputs from the view.
Here is a simple representation of the concept of "one-way data flow":
)
Image Source: www.vuejs.org
However, the simplicity starts to break down when we have multiple components that share a common state:
Multiple views may depend on the same piece of state.
Actions from different views may need to mutate the same piece of state.
To solve this problem, we could use state management solutions like Vuex or Pinia, but to use them most efficiently, we should apply the concept of modular state management.
In this article, we'll explore what state management in Vue.js is and dive deep into Vue.js modular architecture, with tools and techniques to implement it effectively. From foundational concepts to step-by-step guides using popular tools, you will learn how to structure state for maximum maintainability. Along the way, we'll cover common pitfalls, Vue.js best practices for state management, and advanced use cases such as performance optimization and dynamically loaded modules.
Executive Summary
Modular state management in Vue.js divides application state into small, self-contained stores, each owning one feature or domain such as authentication or the cart. This keeps large codebases easier to scale, test and maintain, and lets teams work on separate modules without conflicts.
Vuex supports modules through namespaced sub-stores registered in a single root store, while Pinia, the officially recommended library for Vue 3, makes every store modular by design with less boilerplate. For teams, this means less time untangling shared state and lower risk when adding features to a growing app.
What Is Modular State Management in Vue.js and Why Does It Matter?
Modular state management organizes the application state by dividing it into smaller, self-contained modules, each responsible for managing a specific piece of the application's data and logic. This approach improves code organization, scalability and maintainability by allowing developers to work on isolated modules independently while keeping a clear and structured state architecture.
State management in Vue.js, more broadly, is the process of organizing and controlling the shared state, or data, of an application across multiple components. Each component has its own local state, but as applications grow, managing data that needs to be shared between components becomes more complex. Tools like Vuex or Pinia support modular state management in Vue, enabling easier debugging, team collaboration, and dynamically loading or reusing state modules as needed.
In larger applications, where managing all state in one place becomes unwieldy, organizing it into smaller units (modules) rather than a single, centralized store becomes a priority. Here are a few reasons why:
Scalability: It breaks down the state into logical pieces, making it easier to manage as the app grows.
Reusability: Modules can be reused across various parts of the application or even in different projects.
Improved organization: Each module is focused on a specific feature or domain (e.g., authentication, products, cart), keeping code clean and maintainable.
Separation of concerns: Each module can independently handle its state, mutations, actions and getters.
Better maintainability: It is easier to locate and fix issues in specific parts of the application.
Independent development: Teams can work on different modules without conflicts.
Improved testing: Modules can be tested individually.
Easier scaling: Adding new features becomes simpler by adding new modules instead of modifying a monolithic store.
Modular state management is essential for building scalable and maintainable Vue applications, especially as complexity grows. By following the best practices below, developers can keep state clean, maintainable and future-proof.
Vuex or Pinia: Which State Management Library Should You Use in Vue.js?
The right choice depends on your app's size, complexity and long-term needs. There are two popular production-ready solutions for handling state in Vue: Vuex and Pinia. For new Vue 3 projects, Pinia is the officially recommended choice; Vuex remains a solid option for teams already invested in it. Here are the key differences at a glance:
Vuex | Pinia | |
|---|---|---|
Official status | Vuex 3 and 4 are still maintained, but new features are unlikely | Official state management library for Vue |
Store structure | Single centralized store, split into modules | Independent stores, modular by design |
Changing state | Synchronous mutations, committed by actions | Actions modify state directly (no mutations) |
Module isolation | Requires | Each store has its own unique ID |
Composition API | Usable via | Built to fit the Composition API |
Boilerplate | More | Less |
How Does Vuex Manage State in Vue.js?
Vuex is a state management library for Vue.js applications designed to centralize and manage shared state across components. It follows a unidirectional data flow pattern and integrates deeply with Vue, making it easy to track state changes in a predictable manner.
Vuex is particularly useful for medium to large applications where multiple components must share and synchronize data. A few core concepts of Vuex are good to know:
State: The single source of truth in Vuex. It stores the shared data for the application, allowing all components to access it.
Getters: Similar to computed properties in Vue, getters are used to derive or filter data from the state.
Mutations: The only way to change the state in Vuex. Mutations are synchronous and allow Vue DevTools to track state changes.
Actions: Used for asynchronous operations (like API calls) before committing mutations to change the state.
Modules: Vuex allows you to break the store into smaller, modular chunks for better organization, especially in large applications.
Vuex works in the following way:
Centralized store: Vuex uses a single store to manage the entire application's state, ensuring that data is consistent across components.
Component interaction: Components can access state, dispatch actions and commit mutations via the Vuex store.
Unidirectional data flow: State flows from the store to the components, and components can update the state only through mutations, making the flow predictable and easier to debug.
The architecture of Vuex is well depicted in the image below:
)
Image Source: www.vuejs.org/guide/
To use Vuex in your project, you first need to define a store. Here is a basic store with state, a getter, a mutation and an action:
// store/index.js
import { createStore } from 'vuex';
export const store = createStore({
state: {
count: 0,
},
getters: {
doubleCount: (state) => state.count * 2,
},
mutations: {
increment(state) {
state.count++;
},
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
},
},
});Then, use it in your Vue component or page:
<template>
<div>
<p>Count: {{ $store.state.count }}</p>
<p>Double Count: {{ $store.getters.doubleCount }}</p>
<button @click="$store.commit('increment')">Increment</button>
<button @click="$store.dispatch('incrementAsync')">Increment Async</button>
</div>
</template>
<script>
export default {
name: "CounterComponent",
};
</script>In the step-by-step guide below, you'll see how to split a store like this into feature-specific modules for a scalable Vue store configuration.
With the rise of Vue 3 and newer tools like Pinia, Vuex's complexity has been re-evaluated. While Vuex remains powerful and widely used, many developers now prefer Pinia for its simplicity and modern API. However, Vuex is still a solid choice for teams already familiar with it or for applications with legacy codebases.
Why Is Pinia the Recommended State Management Solution for Vue 3?
Pinia is a modern, lightweight state management library for Vue.js and the official recommendation for Vue state management. It provides a way to manage and share application state across components cleanly and efficiently. Pinia was designed to address some of the limitations and complexities of Vuex, offering a simpler and more intuitive API while still supporting powerful features, such as:
Simpler API: More straightforward syntax than Vuex, making modular store configuration easier. State, getters and actions are all described in a single, cohesive way.
Composition API friendly: Compatible with Vue 3's Composition API, making it easier to use in modern Vue applications. Since Pinia v3, it supports only Vue 3; Vue 2 projects can keep using Pinia v2.
DevTools integration: Vue DevTools' debugging and inspection capabilities make it easy to track and manage the application's state.
Modular stores: Instead of a single, centralized store like Vuex, Pinia supports modular stores, which allow you to break down your state into smaller, more manageable pieces.
TypeScript support: Type safety and autocompletion out of the box.
SSR support: Works seamlessly with server-side rendering (SSR) frameworks like Nuxt.
No mutations: Unlike Vuex, Pinia doesn't use mutations, making state updates more straightforward by using actions directly.
Pinia is built around a few essential concepts that make managing application state intuitive and efficient:
State: Defines the reactive data that stores and manages the application's state.
Getters: Similar to computed properties, getters are used to derive or calculate data from the state.
Actions: Methods for modifying the state or handling asynchronous tasks like API calls.
To use Pinia in your project, the first thing you need to do is define a store:
// stores/counter.js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++;
},
},
});Then, you can use this store in your Vue component or page like the following:
<script setup>
import { useCounterStore } from './stores/counter';
const counter = useCounterStore();
</script>
<template>
<div>
<p>Count: {{ counter.count }}</p>
<p>Double Count: {{ counter.doubleCount }}</p>
<button @click="counter.increment">Increment</button>
</div>
</template>Pinia is an excellent choice for managing state in Vue applications, and it makes creating a modular store in Vue.js more straightforward. It combines simplicity, power and developer-friendly features, and it's especially recommended for projects using Vue 3.
How to Implement Modular State Management in Vue.js: Step-by-Step Guide
This section is divided into two parts. The first shows how to implement modular state management in Vuex, while the second walks through the same process with Pinia.
How to Create a Modular Store with Vuex Modules
Vuex supports modules through its modules property. Each module is self-contained with its own state, mutations, actions and getters.
First, let's create a module store called auth.js. To implement a modular Vuex architecture, enable the namespaced property in your modules for better encapsulation. For example:
// store/modules/auth.js
export default {
namespaced: true,
state: {
user: null,
},
mutations: {
setUser(state, user) {
state.user = user;
},
},
actions: {
login({ commit }, user) {
commit('setUser', user);
},
},
};And cart.js:
// store/modules/cart.js
export default {
namespaced: true,
state: {
items: [],
},
mutations: {
addItem(state, item) {
state.items.push(item);
},
},
};Next, let's import them into the global store:
// store/index.js
import { createStore } from 'vuex';
import auth from './modules/auth';
import cart from './modules/cart';
export default createStore({
modules: {
auth,
cart,
},
});Finally, to use them in our Vue components or pages, we can do the following:
<template>
<div>
<p>User: {{ user }}</p>
<p>Cart Items: {{ cartItems.length }}</p>
</div>
</template>
<script>
import { mapState } from 'vuex';
export default {
computed: {
...mapState('auth', ['user']),
...mapState('cart', { cartItems: 'items' }),
},
};
</script>And this is how you can implement modular state management in Vue with Vuex. Next, let's look at how it can be done with Pinia.
How to Create Modular Stores with Pinia
Pinia supports modular stores by design. Each store is defined independently using defineStore, which makes it easy to manage.
First, let's create a module store called auth.js:
// stores/auth.js
import { defineStore } from 'pinia';
export const useAuthStore = defineStore('auth', {
state: () => ({
user: null,
}),
actions: {
login(user) {
this.user = user;
},
logout() {
this.user = null;
},
},
});And cart.js:
// stores/cart.js
import { defineStore } from 'pinia';
export const useCartStore = defineStore('cart', {
state: () => ({
items: [],
}),
actions: {
addItem(item) {
this.items.push(item);
},
removeItem(index) {
this.items.splice(index, 1);
},
},
});In comparison to Vuex, we don't need to import these modular stores into a global store. Instead, we can use them directly in our Vue components or pages:
<script setup>
import { useAuthStore } from './stores/auth';
import { useCartStore } from './stores/cart';
const auth = useAuthStore();
const cart = useCartStore();
</script>
<template>
<div>
<p>User: {{ auth.user }}</p>
<p>Cart Items: {{ cart.items.length }}</p>
</div>
</template>And this is how you can use Pinia to achieve modular state management! It is significantly easier to use and understand while delivering similar functionality. This approach visibly reduces boilerplate and promotes cleaner, more maintainable state management in Vue.
What Are the Best Practices for Modular State Management in Vue.js?
When implementing modular state management in your Vue or Nuxt project, there are a few best practices you should be aware of:
Focus on single responsibility: Ensure that each module handles a single responsibility or a specific feature area. This keeps modules focused and predictable, which improves maintainability.
Prioritize Pinia for Vue 3 projects: For projects using Vue 3, Pinia should be the preferred choice due to its cleaner and more intuitive API. Its modern design simplifies modular state management and reduces boilerplate code.
Use namespacing in Vuex: If you're using Vuex, always enable namespacing for your modules. This avoids naming conflicts, ensures clear module boundaries, and allows for more efficient dynamic module registration.
Keep state lean and modular: Avoid state overloading by keeping the state lean and modularized. Break your application state into logical pieces to ensure clarity, maintainability and scalability.
Promote reusability: Create reusable modules with shared logic to minimize redundancy. Reusability not only saves development time but also makes your application more adaptable to changing requirements.
Document dependencies between modules: If your modules need to interact with one another, thoroughly document their dependencies and communication patterns. This ensures transparency, reduces potential errors, and simplifies debugging for the development team.
Following these practices contributes to the overall quality of your solution, with higher stability and maintainability.
How to Structure a Nuxt App with Modules and Pinia
A few years ago, I wrote an article on leveraging Modules and Pinia to structure your Nuxt application for greater extensibility and scalability. I will use it as a jumping-off point for this chapter. The example assumes the @pinia/nuxt module is installed.
After generating a simple Hello World Nuxt application, let's replace the content of the app.vue page with the following:
<template>
<div>
<NuxtPage/>
</div>
</template>This ensures that the page created in our module is rendered.
Next, let's create a simple BlogPost.vue component that displays a simple text passed to it via prop:
<script setup lang="ts">
const props = defineProps({
blog: {
type: String,
required: true
}
})
</script>
<template>
<div>
{{ blog }}
</div>
</template>Next, we will create a Pinia module store to store information about our blog:
// modules/blog/stores/store.ts
export const useBlogStore = defineStore('blog-store', {
state: () => ({
value: 1
}),
getters: {
valueWithName: state => `Value is ${state.value}`
},
actions: {
setNewValue(newValue: number) {
this.value = newValue
}
}
})Furthermore, let's create a page that renders the BlogPost.vue component and the information stored in the store:
<script setup lang="ts">
const { currentRoute } = useRouter()
const blogStore = useBlogStore()
</script>
<template>
<section>
<BlogPost :blog="`Test blog post ${currentRoute.params.id}`"/>
<span>Current value from blogStore: {{ blogStore.value }}</span>
</section>
</template>Finally, let's wrap everything up in a Nuxt module:
import {
defineNuxtModule,
createResolver,
addImportsDir,
} from '@nuxt/kit'
export default defineNuxtModule({
name: 'blog-module',
setup () {
const { resolve } = createResolver(import.meta.url)
addImportsDir(resolve('components'))
addImportsDir(resolve('pages'))
// Register the module's Pinia stores for auto-import
addImportsDir(resolve('stores'))
}
})Because the module registers its directories for auto-import, we can use them in our root project. By default, @pinia/nuxt only auto-imports stores from the app's own stores folder (app/stores in Nuxt 4), so stores living inside a module need to be registered like this. Combining Pinia with the modular Nuxt architecture lets developers build scalable Vue store configurations: you define modules in a Nuxt-compatible format and import them automatically into your project, streamlining the setup.
Key Takeaways
Split shared state into feature-focused modules (auth, cart, products) instead of one monolithic store.
Pinia is the official, recommended state management library for Vue 3, and every Pinia store is modular by design.
Vuex still works for legacy codebases, but always enable
namespaced: trueon its modules.Pinia drops mutations: actions change state directly, which cuts boilerplate.
Give each module a single responsibility and document any dependencies between modules.
Future-Proof Your Vue Apps with Modular State Management
Modular state management is essential for keeping state management in Vue.js scalable. It also makes apps easier to maintain as they become more complex. By introducing clarity, structure and flexibility to your codebase, it allows developers to efficiently manage and organize application state.
While both Vuex and Pinia are viable tools for state management, the official Vue documentation now recommends Pinia as the preferred choice. Pinia embraces modern Vue 3 principles, offering a more streamlined approach that requires less boilerplate code while achieving equal or superior results. Modular state management with Pinia can help you build future-proof solutions for handling state in Vue applications, ensuring your projects remain maintainable and scalable as they evolve.
Whether you choose Vuex for its mature ecosystem or Pinia for its modern simplicity, following Vue.js store configuration best practices is key to keeping your project stable as it evolves. By following this guide, you'll build applications that are easier to maintain, extend and debug.
If you're planning a new Vue or Nuxt application, or untangling state in an existing one, our Vue.js development team can help you design a store architecture that scales with your product.
For more in-depth guidance on this topic, be sure to explore the additional resources below:
blog.logrocket.com: Complex Vue 3 state management with Pinia
tighten.com: State management in Vue 3: why you should try out Pinia
digitalocean.com: How to manage state in a Vue.js application with Vuex
Vue.js State Management FAQ
)




