Back to Home

Clean Pinia store: structure and practices

Article describes practices for creating clean stores in Pinia: mutations through actions, setup syntax, avoiding watch and persistence with plugins. Suitable for refactoring large Vue projects. Includes code examples and checklist.

Ideal store structure in Pinia for Vue
Advertisement 728x90

Clean Pinia Store Structure: Proven Best Practices

In large Vue projects, Pinia stores often balloon in size, making maintenance a nightmare. A solid structure fixes this: mutations only through actions, setup syntax, and no side effects inside the store. This keeps things predictable and testable for mid-to-senior developers.

Mutations Only Through Actions

Directly mutating state from components blurs responsibilities. Here's problematic code:

const docStore = useDocumentStore()
const { client } = storeToRefs(docStore)
client.value = null // Scattered mutation logic

Over time, debugging becomes impossible as changes are spread across components. The fix? Centralized actions:

Google AdInline article slot
export const useDocumentStore = defineStore('documents', () => {
  const client = ref(null)
  const resetClient = () => { client.value = null }
  return { client, resetClient }
})

// In component
 docStore.resetClient()

A single entry point makes it easy to add validation, logging, or analytics without touching code everywhere.

Setup Stores Over Options API

Options API is outdated for stores. Setup style (defineStore(() => { ... })) delivers:

  • Transparent reactivity without this.
  • Easier testing.
  • Seamless composable integration.

Less boilerplate, reactivity just like in components. For new stores, switching is a must—the difference is immediate.

Google AdInline article slot

Ditch Watchers in Stores

Watchers inside stores trigger hidden side effects on state changes, complicating debugging. Stores should just hold data, no event dispatching.

Replace watchers with explicit calls from components or composables. Handle route changes, URL parsing, or external events outside, passing ready data via actions.

Persistence via Plugins

Syncing with localStorage shouldn't mix with business logic. Use pinia-plugin-persistedstate or a custom tested wrapper.

Google AdInline article slot

Avoid direct localStorage.setItem in actions—it breaks store purity and hinders refactoring.

Stores as Pure Data Holders

Stores handle only reading, saving, and updating data. Move out:

  • Router subscriptions.
  • Token parsing.
  • WebSocket logic.
  • Cross-store calculations.

Pre-commit checklist:

  • Could I replace this store with a plain file of constants and functions without breaking architecture?
  • If not, it's overloaded—extract the logic.

Key Takeaways

  • Centralized mutations: Actions over direct ref access.
  • Setup syntax: Easier testing, no this.
  • No watchers: Side effects out, store stays predictable.
  • Separate persistence: Plugins, not in actions.
  • Single responsibility: Complex logic to composables.

This approach keeps stores lean, mockable, and refactor-friendly. Projects stay maintainable, and the habit sticks fast.

— Editorial Team

Advertisement 728x90

Read Next