FrontendIntermediate
15 min readNov 24, 2025
State Management Showdown: Vuex vs Pinia vs Nuxt State
Complete guide to choosing the right state management solution for your Vue and Nuxt applications with real-world examples.
R
Rithy Tep
Author
Introduction
State management is crucial for large Vue applications. Let's compare the three main solutions and when to use each.
Vuex: The Classic Choice
Vuex has been the official state management solution for years:
// store/index.js import { createStore } from 'vuex' export default createStore({ state: { user: null, cart: [] }, mutations: { SET_USER(state, user) { state.user = user }, ADD_TO_CART(state, item) { state.cart.push(item) } }, actions: { async fetchUser({ commit }) { const user = await api.getUser() commit('SET_USER', user) } }, getters: { cartTotal: state => state.cart.reduce((sum, item) => sum + item.price, 0) } })
When to use Vuex:
- •Legacy Vue 2 applications
- •Large teams familiar with Vuex patterns
- •Complex state with namespaced modules
Pinia: The Modern Alternative
Pinia is now the official recommendation for Vue 3:
// stores/user.js import { defineStore } from 'pinia' export const useUserStore = defineStore('user', { state: () => ({ user: null, cart: [] }), actions: { async fetchUser() { this.user = await api.getUser() }, addToCart(item) { this.cart.push(item) } }, getters: { cartTotal: (state) => state.cart.reduce((sum, item) => sum + item.price, 0) } })
Benefits of Pinia:
- •TypeScript support out of the box
- •Simpler API (no mutations)
- •Better DevTools integration
- •Smaller bundle size
Nuxt State: Built-in Solution
Nuxt 3 offers useState for server-side reactive state:
// composables/useAuth.js export const useAuth = () => { const user = useState('user', () => null) const login = async (credentials) => { user.value = await $fetch('/api/login', { method: 'POST', body: credentials }) } return { user, login } }
When to use Nuxt State:
- •Simple state needs
- •SSR-specific state management
- •Composable-based architecture
Real-World Example: E-commerce App
// stores/cart.js (Pinia) export const useCartStore = defineStore('cart', { state: () => ({ items: [], checkoutSession: null }), actions: { addItem(product, quantity = 1) { const existing = this.items.find(item => item.id === product.id) if (existing) { existing.quantity += quantity } else { this.items.push({ ...product, quantity }) } }, async checkout() { this.checkoutSession = await $fetch('/api/checkout', { method: 'POST', body: { items: this.items } }) return this.checkoutSession } }, getters: { total: (state) => state.items.reduce( (sum, item) => sum + (item.price * item.quantity), 0 ), itemCount: (state) => state.items.reduce( (sum, item) => sum + item.quantity, 0 ) }, persist: true // Auto-persist to localStorage })
Conclusion
Choose Pinia for new Vue 3 projects - it's modern, type-safe, and officially recommended.
Use Vuex only if maintaining legacy Vue 2 apps or migrating gradually.
Leverage Nuxt State for simple SSR state needs alongside Pinia for complex client state.
#Vue.js#Nuxt#Vuex#Pinia#State Management