All skills
vuejs-ai avatar

/vue-debug-guides

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

Vue 3 debugging and error handling for runtime errors, warnings, async failures, and SSR/hydration issues. Use when diagnosing or fixing Vue issues.

Use this Skill: https://skilld.dev/gh/vuejs-ai/skills/vue-debug-guides

This session only. Nothing lands on disk.

referencestate-ssr-cross-request-pollution.md

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

Prevent Cross-Request State Pollution in SSR Applications

Impact: CRITICAL - In Server-Side Rendering (SSR) applications, a singleton store pattern creates a single instance that is shared across all server requests. This means data from one user's request could leak into another user's response, causing serious security and data integrity issues.

This is one of the most critical gotchas in Vue state management that can have severe production consequences.

Task Checklist

  • Never use a singleton store pattern in SSR applications
  • Create a fresh store instance per request when using SSR
  • Use Pinia which handles SSR state management correctly
  • Test SSR state isolation with concurrent requests
  • Review any global reactive state for SSR compatibility

The Problem: Singleton State in SSR

// store.js - DANGEROUS for SSR
import { reactive } from 'vue'

// This is a singleton - same instance for ALL requests
export const store = reactive({
  user: null,
  cart: [],
  preferences: {}
})

What happens in SSR:

  1. Request A comes in for User A
  2. Server sets store.user = userA
  3. Before response completes, Request B arrives for User B
  4. Request B sees store.user = userA (User A's data leaked!)
  5. Server sets store.user = userB
  6. Request A's response might now contain User B's data

This creates unpredictable behavior and potential security vulnerabilities.

Solution 1: Use Pinia (Recommended)

Pinia handles SSR correctly by creating fresh store instances per request:

// stores/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    user: null,
    preferences: {}
  }),
  actions: {
    setUser(user) {
      this.user = user
    }
  }
})
// main.js (or entry-server.js)
import { createPinia } from 'pinia'
import { createApp } from 'vue'
import App from './App.vue'

// For SSR: Create fresh instances per request
export function createAppInstance() {
  const app = createApp(App)
  const pinia = createPinia()

  app.use(pinia)

  return { app, pinia }
}
// entry-server.js
import { createAppInstance } from './main'
import { renderToString } from 'vue/server-renderer'

export async function render(url, context) {
  // Fresh app and store instance per request
  const { app, pinia } = createAppInstance()

  // ... setup router, fetch data, etc.

  const html = await renderToString(app)

  // Serialize state for client hydration
  const state = pinia.state.value

  return { html, state }
}
// entry-client.js - Hydrate from serialized state
import { createAppInstance } from './main'

const { app, pinia } = createAppInstance()

// Restore server state before mounting
if (window.__PINIA_STATE__) {
  pinia.state.value = window.__PINIA_STATE__
}

app.mount('#app')

Solution 2: Factory Pattern for Hand-Rolled State

If not using Pinia, create a factory function:

// store.js - SSR-safe with factory
import { reactive, readonly } from 'vue'

// Factory function creates fresh state per call
export function createStore() {
  const state = reactive({
    user: null,
    cart: [],
    preferences: {}
  })

  return {
    state: readonly(state),
    setUser(user) {
      state.user = user
    },
    addToCart(item) {
      state.cart.push(item)
    }
  }
}
// entry-server.js
import { createStore } from './store'
import { provide } from 'vue'

export async function render(url) {
  const app = createApp(App)

  // Fresh store instance for this request only
  const store = createStore()
  app.provide('store', store)

  // ... render
}

Solution 3: Context-Based State (Advanced)

For frameworks like Nuxt, use request context:

// composables/useRequestState.js
import { useSSRContext } from 'vue'

export function useRequestState(key, initialValue) {
  if (import.meta.env.SSR) {
    const ctx = useSSRContext()
    ctx.state = ctx.state || {}

    if (!(key in ctx.state)) {
      ctx.state[key] = initialValue()
    }

    return ctx.state[key]
  }

  // Client-side: use regular reactive state
  return reactive(initialValue())
}

Nuxt.js Handles This Automatically

In Nuxt 3, state isolation is handled automatically:

// Nuxt automatically creates fresh Pinia instance per request
// You can use stores normally

export default defineNuxtPlugin(async (nuxtApp) => {
  const userStore = useUserStore()
  await userStore.fetchUser()
})

Testing for State Pollution

// test/ssr-state-isolation.test.js
import { describe, it, expect } from 'vitest'
import { render } from './entry-server'

describe('SSR State Isolation', () => {
  it('should not leak state between concurrent requests', async () => {
    // Simulate concurrent requests
    const [result1, result2] = await Promise.all([
      render('/user/1', { userId: '1' }),
      render('/user/2', { userId: '2' })
    ])

    // Each should have their own user data
    expect(result1.html).toContain('User 1')
    expect(result2.html).toContain('User 2')

    // State should not be mixed
    expect(result1.html).not.toContain('User 2')
    expect(result2.html).not.toContain('User 1')
  })
})
// Alternative: Test store isolation directly
import { createApp } from './app.js'

test('requests do not share state', async () => {
  // Simulate two concurrent requests
  const { app: app1, store: store1 } = createApp()
  const { app: app2, store: store2 } = createApp()

  store1.user = { id: 1, name: 'Alice' }
  store2.user = { id: 2, name: 'Bob' }

  // Each should have its own state
  expect(store1.user.name).toBe('Alice')
  expect(store2.user.name).toBe('Bob')
})

Red Flags to Watch For

// ANY module-level reactive state is dangerous in SSR

// BAD: Module-level reactive
export const globalUser = ref(null)

// BAD: Module-level reactive object
export const appState = reactive({})

// BAD: Shared Map/Set
export const cache = new Map()

// BAD: Even plain objects can be problematic
let requestCount = 0  // Shared across requests

Why Pinia is Recommended for SSR

  1. Automatic request isolation - Fresh store instances per request
  2. Built-in state serialization - Easy hydration on client
  3. DevTools support - Debug state on both server and client
  4. TypeScript support - Type-safe state management
  5. Tested patterns - Battle-tested SSR handling

Reference

Source: SKILL.md on GitHub

1 warning17d5 checks · Risk SAFE
  • Gen Agent Trust Hub17d

    This skill is a comprehensive collection of educational debugging guides and best practices for Vue 3 developers. It contains no executable code or malicious instructions and focuses solely on diagnosing common runtime errors and performance issues.

  • Socket17d

    No alerts

  • Snyk17d

    Risk: LOW · No issues

  • Runlayer7mo

    140/140 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at a5fc891. 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

README badge

README badge for vuejs-ai/skills/vue-debug-guides

Guides for diagnosing Vue 3 runtime errors, reactivity bugs, component lifecycle issues, and SSR hydration problems. Covers reactivity traps (refs, computed, watchers), component mechanics (props, emits, slots), templates (v-if, v-for, refs), forms (v-model), and composition API gotchas.

Generated from the current SKILL.md.

Does this cover Vue 2 or only Vue 3?
This skill is for Vue 3 only. It addresses Vue 3-specific debugging patterns like reactivity proxies, script setup, and defineEmits/defineProps.
Does this skill help with performance optimization?
No. This skill focuses on runtime errors, warnings, and behavioral bugs. For development best practices and optimization, use the separate `vue-best-practices` skill.
What kinds of issues does this cover?
Reactivity traps (refs, computed, watchers), component lifecycle bugs, template directives, form binding edge cases, SSR/hydration mismatches, slot scoping, and async-related failures.
Does this include debugging for Nuxt or other Vue frameworks?
No. This skill focuses on core Vue 3 runtime issues. Framework-specific bugs are outside its scope.

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