---
name: vue-best-practices
description: >-
  Vue 3 Composition API, script setup macros, reactivity system (ref, shallowRef, computed, watch, watchEffect), built-in components (Transition, Teleport, Suspense, KeepAlive), SSR, Volar, vue-tsc, and best practices. Load for any Vue, .vue files, Vue Router, Pinia, or Vite with Vue work. Uses Composition API with `<script setup>` and TypeScript unless the project explicitly requires Options API.
license: MIT
metadata:
  author: github.com/vuejs-ai
  version: "18.0.0"
enabled: false
source: github:JuanJoseGonGi/skills
imported-from: github:JuanJoseGonGi/skills
---

Vue 3 best practices, Composition API patterns, and performance optimization.

> Based on Vue 3.5. Always use Composition API with `<script setup lang="ts">`.

## Preferences

- Prefer TypeScript over JavaScript
- Prefer `<script setup lang="ts">` over `<script>`
- For performance, prefer `shallowRef` over `ref` if deep reactivity is not needed
- Always use Composition API over Options API
- Discourage using Reactive Props Destructure

## Composition API & Script Setup

| Topic | Description | Reference |
|-------|-------------|-----------|
| Script Setup & Macros | `<script setup>`, defineProps, defineEmits, defineModel, defineExpose, defineOptions, defineSlots, generics | [script-setup-macros](references/script-setup-macros.md) |
| Reactivity & Lifecycle | ref, shallowRef, computed, watch, watchEffect, effectScope, lifecycle hooks, composables | [core-new-apis](references/core-new-apis.md) |
| Built-in Components | Transition, Teleport, Suspense, KeepAlive, v-memo, custom directives | [advanced-patterns](references/advanced-patterns.md) |

### Quick Component Template

```vue
<script setup lang="ts">
import { ref, computed, watch, onMounted } from 'vue'

const props = defineProps<{
  title: string
  count?: number
}>()

const emit = defineEmits<{
  update: [value: string]
}>()

const model = defineModel<string>()

const doubled = computed(() => (props.count ?? 0) * 2)

watch(() => props.title, (newVal) => {
  console.log('Title changed:', newVal)
})

onMounted(() => {
  console.log('Component mounted')
})
</script>

<template>
  <div>{{ title }} - {{ doubled }}</div>
</template>
```

### Key Imports

```ts
// Reactivity
import { ref, shallowRef, computed, reactive, readonly, toRef, toRefs, toValue } from 'vue'

// Watchers
import { watch, watchEffect, watchPostEffect, onWatcherCleanup } from 'vue'

// Lifecycle
import { onMounted, onUpdated, onUnmounted, onBeforeMount, onBeforeUpdate, onBeforeUnmount } from 'vue'

// Utilities
import { nextTick, defineComponent, defineAsyncComponent } from 'vue'
```

## Best Practices by Topic

### Reactivity
- Accessing ref() values without .value in scripts → See [ref-value-access](reference/ref-value-access.md)
- Destructuring reactive() objects, losing reactivity → See [reactive-destructuring](reference/reactive-destructuring.md)
- Choosing between ref() and reactive() for state → See [prefer-ref-over-reactive](reference/prefer-ref-over-reactive.md)
- Large objects or external library data overhead → See [shallow-ref-for-performance](reference/shallow-ref-for-performance.md)
- Comparing reactive objects with === operator → See [reactivity-proxy-identity-hazard](reference/reactivity-proxy-identity-hazard.md)
- Library instances breaking in reactive state → See [reactivity-markraw-for-non-reactive](reference/reactivity-markraw-for-non-reactive.md)
- Deriving state with watchEffect instead of computed → See [reactivity-computed-over-watcheffect-mutations](reference/reactivity-computed-over-watcheffect-mutations.md)

### Computed
- Computed getter is making API calls or mutations → See [computed-no-side-effects](reference/computed-no-side-effects.md)
- Computed property doesn't update when expected → See [computed-conditional-dependencies](reference/computed-conditional-dependencies.md)
- Expensive operations running too frequently → See [computed-vs-methods-caching](reference/computed-vs-methods-caching.md)

### Watchers
- Need to watch a reactive object property → See [watch-reactive-property-getter](reference/watch-reactive-property-getter.md)
- Large nested data structures causing perf issues → See [watch-deep-performance](reference/watch-deep-performance.md)
- Uncertain whether to use watch or watchEffect → See [watch-vs-watcheffect](reference/watch-vs-watcheffect.md)
- Template refs appearing null or stale → See [watcheffect-flush-post-for-refs](reference/watcheffect-flush-post-for-refs.md)

### Components
- Prop values being changed from a child → See [props-are-read-only](reference/props-are-read-only.md)
- Bundle includes components that aren't used → See [prefer-local-component-registration](reference/prefer-local-component-registration.md)
- Tight coupling through component ref access → See [prefer-props-emit-over-component-refs](reference/prefer-props-emit-over-component-refs.md)

### Props & Emits
- Composable doesn't update when props change → See [prop-composable-reactivity-loss](reference/prop-composable-reactivity-loss.md)
- Destructured props not updating watchers → See [prop-destructured-watch-getter](reference/prop-destructured-watch-getter.md)

### Templates
- Rendering untrusted user content as HTML → See [v-html-xss-security](reference/v-html-xss-security.md)
- Filtering or conditionally hiding list items → See [no-v-if-with-v-for](reference/no-v-if-with-v-for.md)
- Deciding between v-if and v-show → See [v-if-vs-v-show-performance](reference/v-if-vs-v-show-performance.md)

### Forms & v-model
- Need to handle v-model modifiers in child → See [definemodel-hidden-modifier-props](reference/definemodel-hidden-modifier-props.md)
- Migrating Vue 2 components to Vue 3 → See [v-model-vue3-breaking-changes](reference/v-model-vue3-breaking-changes.md)

### Composables
- Building complex logic from smaller composables → See [composable-composition-pattern](reference/composable-composition-pattern.md)
- Inconsistent naming or destructuring loses reactivity → See [composable-naming-return-pattern](reference/composable-naming-return-pattern.md)
- Composable has many optional parameters → See [composable-options-object-pattern](reference/composable-options-object-pattern.md)
- Unsure if logic belongs in composable or utility → See [composable-vs-utility-functions](reference/composable-vs-utility-functions.md)

### Composition API
- Code becoming scattered and hard to maintain → See [composition-api-code-organization](reference/composition-api-code-organization.md)
- Fixing naming conflicts in mixins → See [composition-api-mixins-replacement](reference/composition-api-mixins-replacement.md)
- Gradually migrating large Options API codebase → See [composition-api-options-api-coexistence](reference/composition-api-options-api-coexistence.md)

### Slots
- Accessing child component data in slot content → See [slot-render-scope-parent-only](reference/slot-render-scope-parent-only.md)
- Scoped slot props lack TypeScript type safety → See [slot-define-slots-for-typescript](reference/slot-define-slots-for-typescript.md)
- Choosing between renderless components and composables → See [slot-renderless-components-vs-composables](reference/slot-renderless-components-vs-composables.md)

### Provide/Inject
- String keys collide in large applications → See [provide-inject-symbol-keys](reference/provide-inject-symbol-keys.md)
- Passing props through many component layers → See [avoid-prop-drilling-use-provide-inject](reference/avoid-prop-drilling-use-provide-inject.md)

### Transitions & Animation
- Wrapping multiple elements in transitions → See [transition-single-element-slot](reference/transition-single-element-slot.md)
- Overlapping elements during transitions → See [transition-mode-out-in](reference/transition-mode-out-in.md)
- Animating lists with TransitionGroup → See [transition-group-key-requirement](reference/transition-group-key-requirement.md)

### Teleport
- Modal breaks with parent CSS transforms → See [teleport-css-positioning-issues](reference/teleport-css-positioning-issues.md)
- Scoped styles not applying to teleported content → See [teleport-scoped-styles-limitation](reference/teleport-scoped-styles-limitation.md)

### Suspense
- Planning Suspense usage in production → See [suspense-experimental-api-stability](reference/suspense-experimental-api-stability.md)
- Combining Suspense with Router, Transition, KeepAlive → See [suspense-nesting-order-with-router](reference/suspense-nesting-order-with-router.md)

### KeepAlive
- Using KeepAlive without proper cache limits → See [keepalive-memory-management](reference/keepalive-memory-management.md)
- Dynamic component state resets when switching → See [dynamic-components-with-keepalive](reference/dynamic-components-with-keepalive.md)

### TypeScript
- Declaring props with TypeScript → See [ts-defineprops-type-based-declaration](reference/ts-defineprops-type-based-declaration.md)
- Accessing DOM elements after mount → See [ts-template-ref-null-handling](reference/ts-template-ref-null-handling.md)
- Declaring component events with full type safety → See [ts-defineemits-type-based-syntax](reference/ts-defineemits-type-based-syntax.md)
- Sharing data between components with type safety → See [ts-provide-inject-injection-key](reference/ts-provide-inject-injection-key.md)

### Performance
- Many list items re-rendering unnecessarily → See [perf-props-stability-update-optimization](reference/perf-props-stability-update-optimization.md)
- Rendering hundreds of items causing DOM issues → See [perf-virtualize-large-lists](reference/perf-virtualize-large-lists.md)
- Static content re-evaluated every update → See [perf-v-once-v-memo-directives](reference/perf-v-once-v-memo-directives.md)

### SSR
- User data leaking between server requests → See [state-ssr-cross-request-pollution](reference/state-ssr-cross-request-pollution.md)
- Code runs on both server and browser → See [ssr-platform-specific-apis](reference/ssr-platform-specific-apis.md)

### SFC & Styling
- Styling child component elements with scoped CSS → See [sfc-scoped-css-child-component-styling](reference/sfc-scoped-css-child-component-styling.md)
- Organizing component template, logic, and styles → See [sfc-separation-of-concerns-colocate](reference/sfc-separation-of-concerns-colocate.md)

### Plugins & App Configuration
- Global properties not available in setup → See [plugin-prefer-provide-inject-over-global-properties](reference/plugin-prefer-provide-inject-over-global-properties.md)
- Global properties missing TypeScript autocomplete → See [plugin-typescript-type-augmentation](reference/plugin-typescript-type-augmentation.md)
