pinia-colada — quality + safety report

In the Skillier index (secondsky__pinia-colada) · scanned 2026-06-03 · engine: builtin+triage

A
Quality
90/100
Safety

1 heuristic flag to review

Heuristic flags from the builtin scanner, which is known to over-flag (it trips on legitimate env-reading integrations, security skills, and library .eval calls). This is NOT an authoritative malicious verdict — re-scan with SkillSpector for the authoritative result. Run the authoritative scan →

Skillproof quality grade A

📇 This skill is in the Skillier index (curated · deduped · quality-filtered). Install Skillier to route & load it into your AI client.

Quality notes

Skill is large (~3575 tokens)
medium · quality · body
→ Tighten to the essential procedure; move long reference material to linked files.
No explicit trigger / 'when to use'
low · quality · body
→ Add a 'When to use' section or 'Use this when …' line listing trigger conditions.

About this skill

Pinia Colada data fetching for Vue/Nuxt with useQuery, useMutation. Use for async state, query cache, SSR, or encountering invalidation, hydration, TanStack Vue Query migration errors.

📄 Read the SKILL.md
---
name: pinia-colada
description: "Pinia Colada data fetching for Vue/Nuxt with useQuery, useMutation. Use for async state, query cache, SSR, or encountering invalidation, hydration, TanStack Vue Query migration errors."
license: MIT
metadata:
  version: "2.0.0"
  pinia_colada_version: "0.17.9"
  pinia_version: "3.0.4"
  vue_version: "3.5.25"
  last_verified: "2025-11-28"
  production_tested: true
  token_savings: "~65%"
  errors_prevented: 12
  references_included: 4
  keywords:
    - Pinia Colada
    - "@pinia/colada"
    - useQuery
    - useMutation
    - useQueryCache
    - data fetching
    - async state
    - Vue 3
    - Nuxt
    - Pinia
    - server state
    - caching
    - staleTime
    - gcTime
    - query invalidation
    - prefetching
    - optimistic updates
    - mutations
    - query keys
    - paginated queries
    - SSR
    - server-side rendering
    - Nuxt module
    - "@pinia/colada-nuxt"
    - query cache
    - auto-refetch
    - cache invalidation
    - request deduplication
    - loading states
    - error handling
    - onSettled
    - onSuccess
    - onError
    - defineColadaLoader
---
# Pinia Colada - Smart Data Fetching for Vue

**Status**: Production Ready ✅ | **Last Updated**: 2025-11-28
**Latest Version**: @pinia/colada@0.17.9 | **Dependencies**: Vue 3.5.17+, Pinia 2.2.6+ or 3.0+

---

## Quick Start (5 Minutes)

### 1. Install Dependencies

**For Vue Projects:**
```bash
bun add @pinia/colada pinia  # preferred
# or: bun add @pinia/colada pinia
```

**For Nuxt Projects:**
```bash
bun add @pinia/nuxt @pinia/colada-nuxt  # install both Pinia and Pinia Colada modules
# or: bun add @pinia/nuxt @pinia/colada-nuxt
```

**Why this matters:**
- Pinia Colada requires Pinia 2.2.6+ or 3.0+ as peer dependency
- Nuxt module handles SSR serialization automatically
- Vue 3.5.17+ required for optimal reactivity

### 2. Set Up Pinia Colada Plugin

**For Vue Projects:**
```typescript
// src/main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import { PiniaColada } from '@pinia/colada'
import App from './App.vue'

const app = createApp(App)
const pinia = createPinia()

app.use(pinia)
app.use(PiniaColada, {
  // Optional: Configure defaults
  query: {
    staleTime: 5000,        // 5 seconds
    gcTime: 5 * 60 * 1000,  // 5 minutes (garbage collection)
    refetchOnMount: true,
    refetchOnWindowFocus: false,
  },
})

app.mount('#app')
```

**For Nuxt Projects:**
```typescript
// nuxt.config.ts
export default defineNuxtConfig({
  modules: [
    '@pinia/nuxt',           // Must be before @pinia/colada-nuxt
    '@pinia/colada-nuxt',
  ],

  // Optional: Configure Pinia Colada
  piniaColada: {
    query: {
      staleTime: 5000,
      gcTime: 5 * 60 * 1000,
    },
  },
})
```

**CRITICAL:**
- For Nuxt: `@pinia/nuxt` must be listed before `@pinia/colada-nuxt`
- Plugin must be registered after Pinia instance
- Configuration is optional - sensible defaults provided

### 3. Create First Query

```vue
<script setup lang="ts">
import { useQuery } from '@pinia/colada'

interface Todo {
  id: number
  title: string
  completed: boolean
}

async function fetchTodos(): Promise<Todo[]> {
  const response = await fetch('/api/todos')
  if (!response.ok) {
    throw new Error('Failed to fetch todos')
  }
  return response.json()
}

const {
  data,       // Ref<Todo[] | undefined>
  isPending,  // Ref<boolean> - initial loading
  isLoading,  // Ref<boolean> - any loading (including refetch)
  error,      // Ref<Error | null>
  refresh,    // () => Promise<void> - manual refetch
} = useQuery({
  key: ['todos'],
  query: fetchTodos,
})
</script>

<template>
  <div>
    <div v-if="isPending">Loading todos...</div>
    <div v-else-if="error">Error: {{ error.message }}</div>
    <ul v-else-if="data">
      <li v-for="todo in data" :key="todo.id">
        {{ todo.title }}
      </li>
    </ul>
  </div>
</template>
```

**CRITICAL:**
- Query `key` must be an array (or getter returning array) for consistent caching
- Query `query` is the async function that fetches data
- Throw errors in query function for proper error handling
- `isPending` is `true` only on initial load, `isLoading` includes refetches

### 4. Create First Mutation

```vue
<script setup lang="ts">
import { useMutation, useQueryCache } from '@pinia/colada'

interface NewTodo {
  title: string
}

async function createTodo(newTodo: NewTodo) {
  const response = await fetch('/api/todos', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(newTodo),
  })
  if (!response.ok) throw new Error('Failed to create todo')
  return response.json()
}

const queryCache = useQueryCache()

const {
  mutate,        // (variables: NewTodo) => Promise<void>
  mutateAsync,   // (variables: NewTodo) => Promise<Result>
  isPending,     // Ref<boolean>
  error,         // Ref<Error | null>
  data,          // Ref<Result | undefined>
} = useMutation({
  mutation: createTodo,

  // Invalidate todos query after mutation succeeds
  async onSettled({ id }) {
    await queryCache.invalidateQueries({ key: ['todos'] })
  },
})

function handleAddTodo(title: string) {
  mutate({ title })
}
</script>

<template>
  <form @submit.prevent="handleAddTodo(newTitle)">
    <input v-model="newTitle" required />
    <button type="submit" :disabled="isPending">
      {{ isPending ? 'Adding...' : 'Add Todo' }}
    </button>
    <div v-if="error">Error: {{ error.message }}</div>
  </form>
</template>
```

**Why this works:**
- `onSettled` runs after success or error, perfect for invalidation
- `invalidateQueries` marks matching queries as stale and refetches active ones
- `mutate` is fire-and-forget, `mutateAsync` returns Promise for await
- Mutations don't cache by default (correct behavior for writes)

---

## Critical Rules

### Always Do

✅ Include all variables used in query function in the key
✅ Throw errors in query/mutation functions for proper error handling
✅ Use `useQueryCache()` for invalidation in mutations
✅ Use `isPending` for initial load, `isLoading` for any loading state
✅ Await `invalidateQueries()` in `onSettled` when you need data fresh before continuing
✅ Use `placeholderData` for paginated queries to avoid flashing
✅ Snapshot cache with `getQueryData` before optimistic updates
✅ Return context from `onMutate` for rollback in `onError`
✅ Configure `staleTime` and `gcTime` at plugin level for app-wide defaults
✅ Use reusable composables for queries instead of inline useQuery

### Never Do

❌ Never use plain strings as keys - always use arrays
❌ Never return undefined from query function - throw errors instead
❌ Never mutate `data.value` directly - it's readonly
❌ Never forget to invalidate related queries after mutations
❌ Never use `onSuccess` in queries (not available, use watch instead)
❌ Never forget to await `mutateAsync()` - it returns a Promise
❌ Never skip `cancelQueries` before optimistic updates (causes race conditions)
❌ Never use `getQueryData` without checking for undefined
❌ Never invalidate queries in `onMutate` (do it in `onSettled`)
❌ Never hardcode URLs - use environment variables for API base URLs

---

## Top 5 Errors Prevention

This skill prevents **12 documented errors**. Here are the top 5:

### Error #1: Query Not Refetching After Mutation
**Error**: Data doesn't update in UI after successful mutation
**Prevention**: Always use `invalidateQueries` in `onSettled`:
```typescript
useMutation({
  mutation: createTodo,
  async onSettled() {
    await queryCache.invalidateQueries({ key: ['todos'] })
  },
})
```
**See**: `references/error-catalog.md` #1

### Error #2: Race Condition with Optimistic Updates
**Error**: Optimistic update gets overwritten by in-flight request
**Prevention**: Always call `cancelQueries` in `onMutate`:
```typescript
onMutate(id) {
  cache.cancelQueries({ key: ['todos'] })
  // Then do optimistic update
}
```
**See**: `references/error-catalog.md` #2

### Error #3: SSR Hydration Mismatch
**Error**: `Hydration completed but contains mismatches`
**Prevention**: Set `refetchOnMount: false` for SSR queries
```typescript
useQuery({
  key: ['todos'],
  query: fetchTodos,
  refetchOnMount: false, // Prevents SSR hydration mismatch
})
```
**See**: `references/error-catalog.md` #3

### Error #4: Query Key Not Reactive
**Error**: Query doesn't refetch when variable changes
**Prevention**: Use function for reactive keys:
```typescript
// ❌ Wrong - static key
key: ['todos', id.value]

// ✅ Correct - reactive key
key: () => ['todos', id.value]
```
**See**: `references/error-catalog.md` #4

### Error #5: Nuxt Module Order Wrong
**Error**: `PiniaColada plugin not found` or SSR errors
**Prevention**: Always put `@pinia/nuxt` first:
```typescript
export default defineNuxtConfig({
  modules: [
    '@pinia/nuxt',           // MUST be first
    '@pinia/colada-nuxt',    // Then Colada
  ],
})
```
**See**: `references/error-catalog.md` #10

**For complete error catalog** (all 12 errors): See `references/error-catalog.md`

---

## Using Bundled Resources

### References (references/)

Detailed guides loaded when needed:

- **`references/setup-guide.md`** - Complete 8-step setup process
  - Install and configure plugin
  - Create reusable query composables
  - Understanding query keys
  - Implementing mutations
  - Optimistic updates
  - Query invalidation strategies
  - Paginated queries
  - SSR and Nuxt integration
  - **Load when**: User needs detailed setup instructions or advanced patterns

- **`references/common-patterns.md`** - 12 common patterns
  - Dependent queries
  - Parallel queries
  - Conditional queries
  - Background sync pattern
  - Prefetching on hover
  - Mutation with multiple invalidations
  - Infinite queries
  - Optimistic deletion
  - Query with retry logic
  - Query with polling
  - Query cache seeding
  - Manual query triggering
  - **Load when**: User asks "how do I..." or needs specific pattern

- **`references/error-catalog.md`** - All 12 documented errors
  - Complete error messages and solutions
  - Prevention strategies
  - Official sources cited
  - Prevention checklist
  - **Load when**: User encounters error or wants to prevent issues

- **`references/configuration.md`** - Full configuration reference
  - Plugin options (Vue and Nuxt)
  - Per-query options
  - Mutation options
  - Query cache methods
  - Advanced patterns (env-specific, error handling, devtools)
  - TypeScript configuration
  - Performance optimization
  - **Load when**: User needs configuration details or advanced setup

- **`references/migration-from-tanstack-vue-query.md`** - Migration guide
  - API differences
  - Codemod suggestions
  - Breaking changes
  - **Load when**: User mentions TanStack Vue Query or migration

---

## Common Use Cases

1. **Basic Todo List with CRUD** - Query + mutation with invalidation (10 min) → See `references/setup-guide.md` Steps 2-4
2. **Paginated Data Table** - Reactive keys with placeholderData (15 min) → See `references/setup-guide.md` Step 7
3. **Optimistic UI Updates** - Mutation with onMutate/onError rollback (20 min) → See `references/setup-guide.md` Step 5
4. **Nuxt SSR Application** - Auto-imports with refetchOnMount config (15 min) → See `references/setup-guide.md` Step 8
5. **Real-time Dashboard** - Background polling with refetchInterval (10 min) → See `references/common-patterns.md` Pattern 4

**For complete code examples** of all 5 use cases, see `references/setup-guide.md` and `references/common-patterns.md`.

---

## When to Load Detailed References

**Load `references/setup-guide.md` when:**
- User needs complete 8-step setup process
- User asks about query keys or reactive keys
- User needs optimistic updates implementation
- User asks about SSR/Nuxt setup
- User needs pagination implementation

**Load `references/common-patterns.md` when:**
- User asks "how do I..." followed by specific pattern
- User needs dependent queries
- User asks about prefetching
- User needs infinite scroll
- User a

… (truncated)
Scan or optimize your own skill →

Want a live grade + an embeddable README badge? Run your skill through the free scanner.

Graded independently by Skillproof — nothing to sell the author. Quality is mechanical + corpus-grounded; safety flags are heuristic (builtin+triage), not a malicious verdict.