The most common Pinia mistake is using it too much. Global stores full of things only one component reads are Vuex habits wearing new clothes. The modern Vue rule of thumb: state lives as locally as possible, and it earns promotion to a store only by being genuinely shared.
The escalation ladder
- Component state (
refin setup) for anything one component owns: form inputs, toggles, local UI. - Props and events for parent-child coordination;
provide/injectfor a subtree. - Composables with module-level state for lightweight sharing without store ceremony.
- Pinia when state is read and written across distant parts of the app: the cart, the session, feature flags, notifications.
When you do reach for Pinia
Prefer the setup-store syntax, where a store is just a composable with refs and functions returned; it matches everything else you write. Keep stores small and domain-shaped, a useCartStore, a useSessionStore, rather than one grand app store. Getters are computeds; actions are where mutations and async work live, which keeps devtools traces meaningful.
Server state is not app state
The data you fetched from an API is a cache of someone else's truth, and caches want tooling: deduplication, refresh, invalidation. In Nuxt, useFetch and useAsyncData already handle that layer, so copying their results into Pinia usually adds a second source of truth that drifts. Store the client's own state, selections, drafts, UI, and let the data layer own the server's.
A useful audit: for each thing in your stores, ask who besides the original component actually reads this. The honest answers usually shrink the store by half, and the app gets easier to reason about, not harder.