All skills
vuejs-ai avatar

/vue-pinia-best-practices

@bc922e4 official
by Vue AIvuejs-ai/skills2.9k stars
167

Pinia stores, state management patterns, store setup, and reactivity with stores.

Use this Skill: https://skilld.dev/gh/vuejs-ai/skills/vue-pinia-best-practices

This session only. Nothing lands on disk.

referencepinia-setup-store-return-all-state.md

≈1.5k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Return All State Properties in Pinia Setup Stores

Impact: HIGH - When using Pinia's setup store syntax (Composition API style), you MUST return all state properties from the setup function. Private state that isn't returned will break Server-Side Rendering (SSR), Vue DevTools inspection, and Pinia plugins.

This is a critical gotcha that can cause silent failures in production.

Task Checklist

  • Return ALL reactive state properties from setup stores
  • Do not create "private" state by omitting it from the return
  • If you need private state, use a prefix convention instead (e.g., _internal)
  • Test stores with DevTools to verify all state is visible
  • Verify SSR hydration includes all necessary state

The Problem: Private State in Setup Stores

// stores/user.js - WRONG: Private state breaks SSR/DevTools
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // Public state
  const name = ref('')
  const email = ref('')

  // "Private" state - NOT returned
  const authToken = ref('')  // Won't be serialized for SSR!
  const lastFetchTime = ref(null)  // Won't appear in DevTools!

  const isLoggedIn = computed(() => !!authToken.value)

  async function login(credentials) {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify(credentials)
    })
    const data = await response.json()

    authToken.value = data.token  // This state won't transfer to client in SSR!
    name.value = data.name
    email.value = data.email
    lastFetchTime.value = Date.now()
  }

  // WRONG: Not returning authToken and lastFetchTime
  return {
    name,
    email,
    isLoggedIn,
    login
  }
})

What breaks:

  1. SSR Hydration: authToken and lastFetchTime won't be serialized and sent to the client
  2. DevTools: These properties won't appear in the store inspector
  3. Plugins: Persistence plugins won't save these properties
  4. Time-travel debugging: Can't track changes to hidden state

The Solution: Return Everything

// stores/user.js - CORRECT: All state returned
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // All state properties
  const name = ref('')
  const email = ref('')
  const authToken = ref('')
  const lastFetchTime = ref(null)

  // Getters
  const isLoggedIn = computed(() => !!authToken.value)

  // Actions
  async function login(credentials) {
    const response = await fetch('/api/login', {
      method: 'POST',
      body: JSON.stringify(credentials)
    })
    const data = await response.json()

    authToken.value = data.token
    name.value = data.name
    email.value = data.email
    lastFetchTime.value = Date.now()
  }

  function logout() {
    authToken.value = ''
    name.value = ''
    email.value = ''
  }

  // CORRECT: Return ALL state, getters, and actions
  return {
    // State
    name,
    email,
    authToken,
    lastFetchTime,
    // Getters
    isLoggedIn,
    // Actions
    login,
    logout
  }
})

If You Need "Private" State

Use naming conventions instead of actually hiding state:

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'

export const useUserStore = defineStore('user', () => {
  // Convention: underscore prefix for "internal" state
  // Still returned, but signals it's not for external use
  const _authToken = ref('')
  const _lastFetchTime = ref(null)

  // Public state
  const name = ref('')
  const email = ref('')

  const isLoggedIn = computed(() => !!_authToken.value)

  // Return everything - convention communicates intent
  return {
    // "Private" - use with caution
    _authToken,
    _lastFetchTime,
    // Public
    name,
    email,
    isLoggedIn
  }
})

Option Stores Don't Have This Problem

With the Options API syntax, all state is automatically tracked:

// stores/user.js - Options syntax: all state is tracked automatically
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    name: '',
    email: '',
    authToken: '',  // Automatically included
    lastFetchTime: null  // Automatically included
  }),

  getters: {
    isLoggedIn: (state) => !!state.authToken
  },

  actions: {
    async login(credentials) {
      // ...
    }
  }
})

How Setup Stores Map to State

Understanding the mapping helps avoid mistakes:

defineStore('example', () => {
  // ref() becomes state
  const count = ref(0)  // → state.count

  // computed() becomes getters
  const double = computed(() => count.value * 2)  // → getters.double

  // Regular functions become actions
  function increment() {  // → actions.increment
    count.value++
  }

  // CRITICAL: Must return all of them
  return { count, double, increment }
})

Debugging: Verify All State is Returned

// Test that DevTools can see all state
import { useUserStore } from '@/stores/user'

const userStore = useUserStore()

// In DevTools console or tests:
console.log(userStore.$state)
// Should include ALL reactive state properties

// Check what's returned
console.log(Object.keys(userStore))
// Should include: name, email, authToken, lastFetchTime, isLoggedIn, login, logout

Reference

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    This skill provides educational content and best practices for Pinia state management in Vue.js applications. It is a documentation-focused skill with no malicious patterns detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    7 files scanned · No issues

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at bc922e4. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Steadyupdated 8 months ago
version
1.0.0
author
github.com/vuejs-ai
  • Vue
  • pinia
  • state-management
  • reactivity
  • store-setup
  • devtools
  • ssr

README badge

README badge for vuejs-ai/skills/vue-pinia-best-practices

Provides Pinia store setup patterns, reactivity gotchas, and state management conventions for Vue apps. Covers destructuring pitfalls, DevTools integration, SSR considerations, and filter persistence strategies.

Generated from the current SKILL.md.

Does this skill cover setup stores and options stores?
Yes. The skill addresses both patterns, including common issues like setup stores missing state in DevTools or SSR, and provides guidance on when to use each.
What reactivity issues does this skill handle?
It covers store destructuring breaking reactivity, method binding losing context in templates, and how to maintain reactive updates when accessing store properties.
Does this skill address SSR or DevTools integration?
Yes. It includes troubleshooting for setup stores not exposing state correctly in DevTools and SSR environments.
Does this cover ephemeral state like filters or URL sync?
Yes. The skill includes patterns for handling filters that reset on refresh and guidance on syncing state with URLs for shareable application state.

Generated from the current SKILL.md. These answers refresh after source changes.