Vue + Nuxt Skill
When to use
- Creating or modifying Nuxt pages, layouts, components, or server routes
- Choosing between SSR, SSG, ISR (SWR in Nuxt), or SPA mode per route
- Setting up data fetching with
useFetch, useAsyncData, or $fetch
- Configuring
nuxt.config.ts, Nitro presets, modules, or middleware
- Integrating Pinia stores, i18n (
@nuxtjs/i18n), or auth (nuxt-auth-utils)
- Resolving hydration mismatches, FOUC, or build-time type errors
Workflow
- Confirm Nuxt version — Nuxt 3 (Vue 3, Composition API) is the baseline. Do not mix Options API pages with Composition API composables without explicit justification.
- Classify rendering per route using
routeRules in nuxt.config.ts:
- Static content →
{ prerender: true }
- Dynamic, revalidated on a schedule →
{ swr: 3600 } (stale-while-revalidate seconds)
- Always-fresh server-rendered →
{ ssr: true }
- Client-only (auth-gated dashboards) →
{ ssr: false } or wrap with <ClientOnly>
- Scaffold the feature:
- Page:
pages/<segment>.vue (auto-registered via file-system routing)
- Layout:
layouts/<name>.vue, applied with definePageMeta({ layout: '<name>' })
- Components:
components/ (auto-imported, no explicit import needed)
- Composables:
composables/use<Name>.ts (auto-imported)
- Server routes:
server/api/<endpoint>.ts using defineEventHandler
- Data fetching:
- Inside
<script setup> on a page: const { data, error } = await useFetch('/api/endpoint', { key: 'unique-key' })
- For complex transforms:
useAsyncData('key', () => $fetch('/api/endpoint'))
- Client-only after mount:
onMounted + $fetch or a reactive watch
- Server routes:
$fetch within defineEventHandler; use readBody, getQuery, getCookie from h3
- State management:
- Local reactive state:
ref / reactive / computed in <script setup>
- Shared cross-component state: Pinia store in
stores/<name>.ts; use storeToRefs to destructure without losing reactivity
- Server-to-client state:
useState('key', () => defaultValue) for SSR-safe shared state
- Styling: Scoped
<style scoped> per component, or Tailwind CSS. Use @nuxtjs/color-mode for dark mode; avoid direct document access in SSR context.
- Auto-import awareness: components, composables, and utils in their conventional folders are auto-imported. Do not re-import them manually — it creates duplicate module instances.
- Bundle audit: run
nuxi analyze before merging; watch for unintended client-bundle growth from server-only modules.
- Deployment: set the correct Nitro preset (
vercel, netlify, node-server, static) in nuxt.config.ts. The static preset disables server routes entirely.
Standards
| Area |
Do |
Do not |
| Reactivity |
Use ref/computed for primitives, reactive for objects; unwrap with .value consistently |
Mix Options API data() with Composition API setup() in the same component |
| Fetching |
Pass a unique key to every useFetch / useAsyncData call |
Omit key — it causes duplicate requests and cache collisions |
| Server routes |
Keep business logic in server/services/; handler files only parse input and call services |
Put DB queries directly inside defineEventHandler |
| Secrets |
Access useRuntimeConfig().mySecret only in server context |
Expose secrets in runtimeConfig.public |
| Pinia stores |
Define with defineStore using the Setup Store syntax; export typed refs |
Use the Options Store syntax when the team has agreed on Setup syntax |
| Plugins |
Register one concern per plugin file in plugins/; use provide / inject for DI |
Do DOM manipulation in plugins without import.meta.client guard |
| Types |
Run nuxi typecheck; all composable return types should be explicit |
Rely solely on Nuxt's auto-generated .nuxt/types without checking |
Common mistakes to avoid
- Accessing
window or document at module top-level — Nuxt SSR runs on Node.js; guard with import.meta.client or onMounted.
- Missing
key on useFetch — Nuxt deduplicates fetches by key; omitting it or reusing a key across unrelated calls serves stale data to the wrong component.
- Mutating Pinia state outside an action — breaks Vue DevTools tracking and can cause SSR state leakage between requests (each SSR request must have its own store instance via
defineStore + Pinia's auto-injection).
- Using
<ClientOnly> as a hydration escape hatch — it hides SSR/CSR mismatches instead of fixing them. Fix the root cause (usually a Date.now() or Math.random() call differing between server and client).
- Importing a heavy library into a composable used on every page — use dynamic
import() inside the function body, or move it to a Nitro server route.
routeRules set to { prerender: true } on a route with user-specific content — prerenders one user's data for all users.
- Forgetting
server: false on Nitro plugins that use Node-only APIs — causes build failures on edge runtimes.
Output format
Typical deliverables for a Nuxt feature:
pages/
<feature>/
index.vue # Page component with definePageMeta + useFetch
[id].vue # Dynamic segment
components/
<Feature>/
Card.vue # Auto-imported, scoped styles
composables/
use<Feature>.ts # Reusable data + logic
stores/
<feature>.ts # Pinia store (Setup Store syntax)
server/
api/
<feature>/
index.get.ts # GET handler
index.post.ts # POST handler
services/
<feature>.ts # Business logic, DB calls
Each Vue SFC includes <script setup lang="ts"> with explicit prop types via defineProps<{...}>() and emits via defineEmits<{...}>().
Related checklists
.claude/checklists/security.md
.claude/checklists/performance.md
.claude/checklists/accessibility.md
.claude/checklists/production.md
Related agents
.claude/agents/stack/web/vue-nuxt-engineer.md
.claude/agents/engineering/frontend-engineer.md
.claude/agents/engineering/fullstack-engineer.md
.claude/agents/quality/performance-engineer.md
.claude/agents/quality/security-auditor.md
1---2name: vue-nuxt3description: Use for Vue + Nuxt apps — Nuxt 3/Vue 3 Composition API, rendering modes, auto-imports, server routes, Nitro, Pinia, deployment. Triggers — Nuxt config, Vue components, SSR/SSG decisions.4---56# Vue + Nuxt Skill78## When to use910- Creating or modifying Nuxt pages, layouts, components, or server routes11- Choosing between SSR, SSG, ISR (SWR in Nuxt), or SPA mode per route12- Setting up data fetching with `useFetch`, `useAsyncData`, or `$fetch`13- Configuring `nuxt.config.ts`, Nitro presets, modules, or middleware14- Integrating Pinia stores, i18n (`@nuxtjs/i18n`), or auth (`nuxt-auth-utils`)15- Resolving hydration mismatches, FOUC, or build-time type errors1617## Workflow18191. **Confirm Nuxt version** — Nuxt 3 (Vue 3, Composition API) is the baseline. Do not mix Options API pages with Composition API composables without explicit justification.202. **Classify rendering per route** using `routeRules` in `nuxt.config.ts`:21 - Static content → `{ prerender: true }`22 - Dynamic, revalidated on a schedule → `{ swr: 3600 }` (stale-while-revalidate seconds)23 - Always-fresh server-rendered → `{ ssr: true }`24 - Client-only (auth-gated dashboards) → `{ ssr: false }` or wrap with `<ClientOnly>`253. **Scaffold the feature**:26 - Page: `pages/<segment>.vue` (auto-registered via file-system routing)27 - Layout: `layouts/<name>.vue`, applied with `definePageMeta({ layout: '<name>' })`28 - Components: `components/` (auto-imported, no explicit `import` needed)29 - Composables: `composables/use<Name>.ts` (auto-imported)30 - Server routes: `server/api/<endpoint>.ts` using `defineEventHandler`314. **Data fetching**:32 - Inside `<script setup>` on a page: `const { data, error } = await useFetch('/api/endpoint', { key: 'unique-key' })`33 - For complex transforms: `useAsyncData('key', () => $fetch('/api/endpoint'))`34 - Client-only after mount: `onMounted` + `$fetch` or a reactive `watch`35 - Server routes: `$fetch` within `defineEventHandler`; use `readBody`, `getQuery`, `getCookie` from `h3`365. **State management**:37 - Local reactive state: `ref` / `reactive` / `computed` in `<script setup>`38 - Shared cross-component state: Pinia store in `stores/<name>.ts`; use `storeToRefs` to destructure without losing reactivity39 - Server-to-client state: `useState('key', () => defaultValue)` for SSR-safe shared state406. **Styling**: Scoped `<style scoped>` per component, or Tailwind CSS. Use `@nuxtjs/color-mode` for dark mode; avoid direct `document` access in SSR context.417. **Auto-import awareness**: components, composables, and utils in their conventional folders are auto-imported. Do not re-import them manually — it creates duplicate module instances.428. **Bundle audit**: run `nuxi analyze` before merging; watch for unintended client-bundle growth from server-only modules.439. **Deployment**: set the correct Nitro preset (`vercel`, `netlify`, `node-server`, `static`) in `nuxt.config.ts`. The `static` preset disables server routes entirely.4445## Standards4647| Area | Do | Do not |48|---|---|---|49| Reactivity | Use `ref`/`computed` for primitives, `reactive` for objects; unwrap with `.value` consistently | Mix Options API `data()` with Composition API `setup()` in the same component |50| Fetching | Pass a unique `key` to every `useFetch` / `useAsyncData` call | Omit `key` — it causes duplicate requests and cache collisions |51| Server routes | Keep business logic in `server/services/`; handler files only parse input and call services | Put DB queries directly inside `defineEventHandler` |52| Secrets | Access `useRuntimeConfig().mySecret` only in server context | Expose secrets in `runtimeConfig.public` |53| Pinia stores | Define with `defineStore` using the Setup Store syntax; export typed refs | Use the Options Store syntax when the team has agreed on Setup syntax |54| Plugins | Register one concern per plugin file in `plugins/`; use `provide` / `inject` for DI | Do DOM manipulation in plugins without `import.meta.client` guard |55| Types | Run `nuxi typecheck`; all composable return types should be explicit | Rely solely on Nuxt's auto-generated `.nuxt/types` without checking |5657## Common mistakes to avoid5859- **Accessing `window` or `document` at module top-level** — Nuxt SSR runs on Node.js; guard with `import.meta.client` or `onMounted`.60- **Missing `key` on `useFetch`** — Nuxt deduplicates fetches by key; omitting it or reusing a key across unrelated calls serves stale data to the wrong component.61- **Mutating Pinia state outside an action** — breaks Vue DevTools tracking and can cause SSR state leakage between requests (each SSR request must have its own store instance via `defineStore` + Pinia's auto-injection).62- **Using `<ClientOnly>` as a hydration escape hatch** — it hides SSR/CSR mismatches instead of fixing them. Fix the root cause (usually a `Date.now()` or `Math.random()` call differing between server and client).63- **Importing a heavy library into a composable used on every page** — use dynamic `import()` inside the function body, or move it to a Nitro server route.64- **`routeRules` set to `{ prerender: true }` on a route with user-specific content** — prerenders one user's data for all users.65- **Forgetting `server: false` on Nitro plugins that use Node-only APIs** — causes build failures on edge runtimes.6667## Output format6869Typical deliverables for a Nuxt feature:7071```72pages/73 <feature>/74 index.vue # Page component with definePageMeta + useFetch75 [id].vue # Dynamic segment76components/77 <Feature>/78 Card.vue # Auto-imported, scoped styles79composables/80 use<Feature>.ts # Reusable data + logic81stores/82 <feature>.ts # Pinia store (Setup Store syntax)83server/84 api/85 <feature>/86 index.get.ts # GET handler87 index.post.ts # POST handler88 services/89 <feature>.ts # Business logic, DB calls90```9192Each Vue SFC includes `<script setup lang="ts">` with explicit prop types via `defineProps<{...}>()` and emits via `defineEmits<{...}>()`.9394## Related checklists9596- `.claude/checklists/security.md`97- `.claude/checklists/performance.md`98- `.claude/checklists/accessibility.md`99- `.claude/checklists/production.md`100101## Related agents102103- `.claude/agents/stack/web/vue-nuxt-engineer.md`104- `.claude/agents/engineering/frontend-engineer.md`105- `.claude/agents/engineering/fullstack-engineer.md`106- `.claude/agents/quality/performance-engineer.md`107- `.claude/agents/quality/security-auditor.md`