Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Enforces Vue 3 Composition API best practices with script setup, TypeScript, Pinia, and Vite.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/state-management.md
1---2title: State Management Strategy3impact: HIGH4impactDescription: Choosing the wrong store pattern can cause SSR request leaks, brittle mutation flows, and poor scaling5type: best-practice6tags: [vue3, state-management, pinia, composables, ssr, vueuse]7---89# State Management Strategy1011**Impact: HIGH** - Use the lightest state solution that fits your app architecture. SPA-only apps can use lightweight global composables, while SSR/Nuxt apps should default to Pinia for request-safe isolation and predictable tooling.1213## Task List1415- Keep state local first, then promote to shared/global only when needed16- Use singleton composables only in non-SSR applications17- Expose global state as readonly and mutate through explicit actions18- Prefer Pinia for SSR/Nuxt, large apps, and advanced debugging/plugin needs19- Avoid exporting mutable module-level reactive state directly2021## Choose the Lightest Store Approach2223- **Feature composable:** Default for reusable logic with local/feature-level state.24- **Singleton composable or VueUse `createGlobalState`:** Small non-SSR apps needing shared app state.25- **Pinia:** SSR/Nuxt apps, medium-to-large apps, and cases requiring DevTools, plugins, or action tracing.2627## Avoid Exporting Mutable Module State2829**BAD:**30```ts31// store/cart.ts32import { reactive } from 'vue'3334export const cart = reactive({35items: [] as Array<{ id: string; qty: number }>36})37```3839**GOOD:**40```ts41// composables/useCartStore.ts42import { reactive, readonly } from 'vue'4344let _store: ReturnType<typeof createCartStore> | null = null4546function createCartStore() {47const state = reactive({48items: [] as Array<{ id: string; qty: number }>49})5051function addItem(id: string, qty = 1) {52const existing = state.items.find((item) => item.id === id)53if (existing) {54existing.qty += qty55return56}57state.items.push({ id, qty })58}5960return {61state: readonly(state),62addItem63}64}6566export function useCartStore() {67if (!_store) _store = createCartStore()68return _store69}70```7172## Do Not Use Runtime Singletons in SSR7374Module singletons live for the runtime lifetime. In SSR this can leak state between requests.7576**BAD:**77```ts78// shared singleton reused across requests79const cartStore = useCartStore()8081export function useServerCart() {82return cartStore83}84```8586**GOOD:**8788> `pinia` dependency required.8990```ts91// stores/cart.ts92import { defineStore } from 'pinia'9394export const useCartStore = defineStore('cart', {95state: () => ({96items: [] as Array<{ id: string; qty: number }>97}),98actions: {99addItem(id: string, qty = 1) {100const existing = this.items.find((item) => item.id === id)101if (existing) {102existing.qty += qty103return104}105this.items.push({ id, qty })106}107}108})109```110111## Use `createGlobalState` for Small SPA Global State112113> `@vueuse/core` dependency required.114115If the app is non-SSR and already uses VueUse, `createGlobalState` removes singleton boilerplate.116117```ts118import { createGlobalState } from '@vueuse/core'119import { computed, ref } from 'vue'120121export const useAuthState = createGlobalState(() => {122const token = ref<string | null>(null)123const isAuthenticated = computed(() => token.value !== null)124125function setToken(next: string | null) {126token.value = next127}128129return {130token,131isAuthenticated,132setToken133}134})135```136