Vue.js Best Practices
Comprehensive performance optimization guide for Vue 3 and Nuxt 3 applications. Contains 50+ rules across 10 categories, prioritized by impact to guide automated refactoring and code generation.
When to Apply
Reference these guidelines when:
- Writing new Vue 3 components or Nuxt 3 pages
- Using Composition API and composables
- Implementing data fetching (client or server-side)
- Managing state with Pinia
- Reviewing code for performance issues
- Refactoring existing Vue/Nuxt code
- Optimizing bundle size or load times
Rule Categories by Priority
| Priority | Category | Impact | Prefix |
|---|---|---|---|
| 1 | Reactivity Optimization | CRITICAL | reactivity- |
| 2 | Bundle Size Optimization | CRITICAL | bundle- |
| 3 | Nuxt 3 Server Performance | HIGH | nuxt- |
| 4 | Component Design Patterns | HIGH | component- |
| 5 | Composition API Patterns | MEDIUM-HIGH | composable- |
| 6 | State Management (Pinia) | MEDIUM-HIGH | pinia- |
| 7 | Rendering Performance | MEDIUM | rendering- |
| 8 | TypeScript Integration | MEDIUM | typescript- |
| 9 | Testing Patterns | LOW-MEDIUM | testing- |
| 10 | Advanced Patterns | LOW | advanced- |
Quick Reference
1. Reactivity Optimization (CRITICAL)
reactivity-shallowref- Use shallowRef for large objects that don't need deep reactivityreactivity-shallowreactive- Use shallowReactive for flat objectsreactivity-computed-cache- Leverage computed for expensive calculations (auto-caching)reactivity-toraw- Use toRaw() when passing to external librariesreactivity-markraw- Use markRaw() for non-reactive objects (classes, third-party instances)reactivity-effectscope- Use effectScope() to batch cleanup of effects
2. Bundle Size Optimization (CRITICAL)
bundle-tree-shaking- Import from 'vue' not internal packages (@vue/reactivity, @vue/runtime-core)bundle-async-components- Use defineAsyncComponent for heavy componentsbundle-dynamic-imports- Dynamic import() for route-level code splittingbundle-icon-imports- Import icons directly, avoid icon libraries barrel filesbundle-lodash-es- Use lodash-es with specific importsbundle-analyze- Use rollup-plugin-visualizer to identify bloatbundle-external-cdn- Externalize large libraries to CDN when appropriate
3. Nuxt 3 Server Performance (HIGH)
nuxt-usefetch- Use useFetch/useAsyncData for SSR-friendly data fetchingnuxt-lazy-components- Prefix with Lazy for automatic lazy loadingnuxt-payload-reduce- Minimize payload with pick/transform optionsnuxt-cache-route- Use routeRules for caching strategiesnuxt-server-components- Use .server.vue for server-only componentsnuxt-prerender- Prerender static pages at build timenuxt-islands- Use Nuxt Islands for partial hydrationnuxt-nitro-cache- Leverage Nitro caching for API routes
4. Component Design Patterns (HIGH)
component-sfc-setup- Always use<script setup>syntaxcomponent-props-destructure- Use destructuring with defineProps for reactivitycomponent-emits-typed- Always define emits with TypeScriptcomponent-slots-typed- Type slots with defineSlotscomponent-expose- Use defineExpose sparingly, prefer props/emitscomponent-v-once- Use v-once for static contentcomponent-v-memo- Use v-memo for list item optimizationcomponent-teleport- Use Teleport for modals/tooltips
5. Composition API Patterns (MEDIUM-HIGH)
composable-naming- Prefix composables with 'use' (useAuth, useFetch)composable-return-object- Return object, not array, for better DXcomposable-cleanup- Always handle cleanup in onUnmountedcomposable-async- Handle async state properly (loading, error, data)composable-ssr-safe- Checkimport.meta.clientfor browser-only codecomposable-provide-inject- Use provide/inject with InjectionKey for type safetycomposable-vueuse- Leverage VueUse before writing custom composables
6. State Management - Pinia (MEDIUM-HIGH)
pinia-setup-syntax- Prefer setup stores over options storespinia-storetorefs- Use storeToRefs() to maintain reactivitypinia-actions-async- Use actions for async operations, not getterspinia-persist- Use pinia-plugin-persistedstate for persistencepinia-reset- Implement $reset for store cleanuppinia-subscribe- Use $subscribe for side effectspinia-ssr-hydration- Handle SSR hydration properly in Nuxt
7. Rendering Performance (MEDIUM)
rendering-v-show-v-if- v-show for frequent toggles, v-if for rare conditionsrendering-key-attribute- Always use unique :key in v-for (avoid index as key)rendering-virtual-scroll- Use virtual scrolling for long lists (100+ items)rendering-keep-alive- Cache component instances with KeepAliverendering-suspense- Use Suspense for async component loading states
8. TypeScript Integration (MEDIUM)
typescript-props-interface- Define props with interface, not typetypescript-ref-type- Explicitly type refs:ref<string>('')typescript-template-ref- Type template refs:ref<HTMLInputElement | null>(null)typescript-component-type- UseComponentPublicInstancefor component refstypescript-generic-components- Use generic components for reusable patternstypescript-strict-inject- Use InjectionKey for type-safe provide/injecttypescript-event-handlers- Type event handlers properly
9. Testing Patterns (LOW-MEDIUM)
testing-vitest- Use Vitest for unit testing (Vue ecosystem native)testing-vue-test-utils- Use @vue/test-utils for component testingtesting-composables- Test composables in isolationtesting-pinia- Use createTestingPinia for store testingtesting-msw- Use MSW for API mockingtesting-playwright- Use Playwright for E2E with Nuxt
10. Advanced Patterns (LOW)
advanced-render-function- Use render functions for dynamic component generationadvanced-custom-directive- Create custom directives for DOM manipulationadvanced-plugin-pattern- Structure plugins with proper install function
Decision Trees
When to use ref vs reactive
What type of data?
│
├── Primitives (string, number, boolean)
│ └── ref() ✓
│
├── Objects/Arrays (need deep reactivity)
│ └── reactive() ✓
│
├── Large objects (performance critical)
│ └── shallowRef() or shallowReactive() ✓
│
└── Non-reactive data (class instances, external libs)
└── markRaw() ✓
When to use useFetch vs $fetch (Nuxt)
Where is the code running?
│
├── Component/Page (need SSR + reactivity)
│ └── useFetch() or useAsyncData() ✓
│
├── Server API route
│ └── $fetch() ✓
│
├── Event handler (client-side only)
│ └── $fetch() ✓
│
└── Composable (reusable)
└── useFetch() with key ✓
Component communication pattern
What's the relationship?
│
├── Parent → Child
│ └── Props ✓
│
├── Child → Parent
│ └── Emits ✓
│
├── Siblings
│ └── Pinia store or provide/inject ✓
│
├── Deep nesting (prop drilling)
│ └── provide/inject ✓
│
└── Global state
└── Pinia store ✓
Anti-Patterns to Avoid
❌ DON'T:
- Use Options API in new Vue 3 projects
- Mutate props directly
- Use
thisin<script setup> - Create reactive() with primitives
- Use v-if and v-for on same element
- Forget :key in v-for loops
- Use index as :key for dynamic lists
- Access refs without .value in script
- Nest Pinia stores unnecessarily
- Use
$fetchin components (useuseFetch)
✅ DO:
- Use Composition API with
<script setup> - Define props and emits with TypeScript
- Use computed for derived state
- Use watchEffect for side effects
- Leverage VueUse composables
- Use Pinia for global state
- Handle loading/error states
- Clean up effects in onUnmounted
- Use Suspense for async components
Performance Checklist
Before shipping:
- Bundle analyzed? No unexpected large dependencies
- Lazy loading? Routes and heavy components code-split
- Reactivity optimized? Using shallowRef where appropriate
- SSR working? No hydration mismatches
- Images optimized? Using nuxt/image or similar
- API calls deduplicated? Using useFetch with proper keys
- State management efficient? Not over-fetching in stores
- List rendering optimized? Virtual scroll for long lists
Full Compiled Document
For detailed code examples, implementation patterns, and comprehensive explanations: AGENTS.md
Each section in AGENTS.md contains:
- Detailed explanations of why patterns matter
- Incorrect vs Correct code comparisons
- Copy-paste ready implementations
- Vue 3 and Nuxt 3 specific examples
- VueUse integration examples