Why Vue and Nuxt Need Specific AI Rules
Vue has undergone a generational shift from Vue 2 (Options API, Vuex, Vue CLI) to Vue 3 (Composition API, Pinia, Vite). AI assistants trained on years of Vue 2 content generate the wrong API, the wrong state management library, and the wrong project structure. In a Vue 3 project, Options API code is technically valid but stylistically wrong — like writing class components in a hooks-based React project.
Nuxt adds another layer: Nuxt 3 is a complete rewrite of Nuxt 2, with different directory conventions, different data fetching patterns, and a different server engine (Nitro). AI generates Nuxt 2 patterns (asyncData, fetch hook, nuxt.config.js) in Nuxt 3 projects where the equivalents are completely different.
These rules cover Vue 3 with Composition API and optionally Nuxt 3. Specify whether your project uses plain Vue (Vite) or Nuxt — the framework-specific patterns differ significantly.
Rule 1: Composition API with script setup
The rule: 'Use <script setup lang="ts"> for all components. Never use Options API (data(), methods, computed, watch options). Never use the setup() function — use <script setup> directly. Use ref() for reactive primitives, reactive() for objects, computed() for derived values, watch()/watchEffect() for side effects. Use defineProps<T>() and defineEmits<T>() for component interfaces.'
For TypeScript: 'All components use TypeScript. Type props with defineProps<{ name: string; age?: number }>(). Type emits with defineEmits<{ (e: "update", value: string): void }>(). Use generic components with <script setup lang="ts" generic="T"> for reusable typed components.'
The script setup syntax is Vue 3's recommended approach. AI generates Options API because it dominates training data (Vue 2 was popular for 5+ years). One rule redirects all code to the modern syntax.
- <script setup lang="ts"> on every component — never Options API
- ref() for primitives, reactive() for objects, computed() for derived
- defineProps<T>() and defineEmits<T>() — typed component interfaces
- watch() for explicit dependencies, watchEffect() for automatic tracking
- No setup() function — script setup is the replacement
AI generates data(), methods, computed options from 5+ years of Vue 2 training data. <script setup lang="ts"> is Vue 3's recommended syntax — one line in your rules prevents every Options API generation.
Rule 2: Composables for Logic Reuse
The rule: 'Extract reusable logic into composables: functions that start with use and contain reactive state. Place composables in composables/ directory: useAuth.ts, useApi.ts, useDebounce.ts. Composables return reactive refs and functions. Use VueUse for common utilities (useLocalStorage, useFetch, useBreakpoints) — never reimplement what VueUse provides.'
For composable design: 'Composables accept arguments and return reactive state: export function useUser(id: Ref<string>) { const user = ref<User | null>(null); ... return { user, loading, error } }. Return refs (not .value) so consumers can use them reactively in templates. Accept Ref parameters for reactive arguments.'
Composables are Vue's equivalent of React hooks. AI assistants generate mixins (Vue 2 pattern, explicitly discouraged in Vue 3) or duplicate logic across components. The composable rule channels all reuse into the correct pattern.
Rule 3: State Management with Pinia
The rule: 'Use Pinia for all shared state. Never use Vuex — it's the Vue 2 state manager. Define stores with defineStore using the setup syntax: export const useUserStore = defineStore("user", () => { const user = ref<User | null>(null); ... return { user, login, logout } }). Use storeToRefs() for destructuring store state reactively.'
For store design: 'One store per domain: useAuthStore, useCartStore, useSettingsStore. Keep stores focused — if a store has more than 10 state properties, split it. Use getters (computed) for derived state. Use actions (functions) for mutations. Never mutate store state from outside the store — always through store actions.'
For persistence: 'Use pinia-plugin-persistedstate for state that survives page refreshes: cart contents, user preferences, theme selection. Configure persistence per store, not globally. Use localStorage for non-sensitive state, sessionStorage for session-scoped state.'
Pinia replaced Vuex as Vue's official state manager. AI still generates Vuex stores from training data. 'Use Pinia, never Vuex' is a one-line rule that prevents the wrong state library in every component.
Rule 4: Nuxt 3 Conventions
The rule: 'This project uses Nuxt 3. Use auto-imports — don't manually import ref, computed, useRoute, useFetch, etc. (Nuxt auto-imports them). Use useFetch for server-side data fetching (replaces Nuxt 2's asyncData). Use useAsyncData for custom async operations. Use server/ directory for API routes (Nitro server). Use app.vue as root, pages/ for file-based routing, layouts/ for shared layouts.'
For data fetching: 'useFetch() for simple API calls — it handles SSR, caching, and deduplication. useAsyncData() when you need more control over the fetch logic. Use lazy: true for non-blocking fetches. Use $fetch for client-side-only requests (form submissions, mutations). Use server routes (server/api/) instead of external API calls when the data source is on the same server.'
For Nitro server: 'Server routes in server/api/ and server/routes/. Use defineEventHandler for route handlers. Access request with getQuery(event), readBody(event). Return data directly — Nitro handles serialization. Use server/middleware/ for server-level middleware (auth, logging).'
- Auto-imports: no manual import for ref, computed, useRoute, useFetch
- useFetch for SSR data — useAsyncData for custom logic — $fetch for client mutations
- server/api/ for server routes — defineEventHandler — Nitro engine
- pages/ for file routing — layouts/ for shared layouts — app.vue as root
- nuxt.config.ts (not .js) — modules for plugins — runtimeConfig for env vars
Nuxt 3 auto-imports ref, computed, useRoute, useFetch — don't import them manually. AI adds explicit imports because that's what Vue (without Nuxt) requires. For Nuxt, the auto-import rule keeps code clean.
Rule 5: Template and Component Conventions
The rule: 'Use single-file components (.vue) with <script setup>, <template>, <style scoped> order. Use PascalCase for component names in templates: <UserCard /> not <user-card />. Use v-bind shorthand: :prop not v-bind:prop. Use v-on shorthand: @click not v-on:click. Use v-model with typed emits for two-way binding. Never use v-html with user content — XSS risk.'
For styling: 'Use <style scoped> for component-specific styles. Use CSS Modules (<style module>) for complex styling needs. Use Tailwind if the project uses it — never mix approaches. Use :deep() for styling child components from a parent (when unavoidable). Use defineExpose() to expose template refs to parent components — never access child internals directly.'
For template complexity: 'Keep templates under 50 lines. Extract complex template logic into computed properties. Extract repeated template sections into child components. Use <template v-if> for conditional groups — not repeated v-if on siblings. Use <component :is> for dynamic component rendering.'
Complete Vue/Nuxt Rules Template
Consolidated rules for Vue 3 and Nuxt 3 projects.
- <script setup lang="ts"> on every component — never Options API or Vuex
- Composables in composables/ — use prefix, return refs, VueUse for common utils
- Pinia setup stores — one per domain, storeToRefs for destructuring, actions for mutations
- Nuxt: auto-imports, useFetch for SSR, server/api/ for Nitro routes, nuxt.config.ts
- PascalCase components in templates — :shorthand — @shorthand — v-model with types
- <style scoped> by default — Tailwind or CSS Modules — never global CSS leaks
- Templates under 50 lines — computed for logic — child components for repeated sections
- Vitest + Vue Test Utils — @nuxt/test-utils for Nuxt — msw for API mocking