Best practices
Patterns for vue@3.6 with citations to the official documentation.
Props and composables
- Use reactive props destructure with native default syntax instead of
withDefaults(). Destructured variables stay reactive; the compiler rewrites accesses toprops.x. When passing towatch()or composables, wrap in a getter:watch(() => count, ...). (script setup docs) - Accept
MaybeRefOrGetter<T>in composables and normalize withtoValue(). Callers can then pass a plain value, a ref, or a getter. (reactivity utilities) - Prefer
onWatcherCleanup()over theonCleanupcallback parameter; it works from nested helper functions in the same synchronous stack. (reactivity core) - Use
useTemplateRef('key')instead of a plain ref whose variable name matchesref="key"; it supports dynamic IDs and better type inference. (composition helpers)
IDs and SSR
- Generate form and accessibility IDs with
useId(); IDs match between server and client, so they cannot cause hydration mismatches. Do not call it insidecomputed(). (composition helpers) - Defer hydration of below-the-fold async components with a hydration strategy; the strategy helpers are tree-shakable. (async components)
import { defineAsyncComponent, hydrateOnVisible } from 'vue'
const Chart = defineAsyncComponent({
loader: () => import('./Chart.vue'),
hydrate: hydrateOnVisible(),
})Performance
From the performance guide:
- Use
shallowRef()/shallowReactive()for large immutable structures; deep reactivity pays proxy overhead on every property access, shallow variants react only to root replacement. - Pass computed booleans as props instead of IDs that children compare; a stable prop avoids re-rendering every list item when one item's state changes.
- When a
computedreturns a fresh object each run, return the unchangedoldValuewhen data is equivalent; since 3.4 effects trigger only on reference change. - Use
v-once/v-memofor truly static or rarely-changing subtrees; profile before adding them.
Vapor-specific
- Reach for
<script setup vapor>on performance-sensitive pages or small new apps; keep the rest of an existing app on VDOM and bridge withvaporInteropPlugin. Details and constraints in Vapor Mode. - In Vapor, never call
slots.default()to probe content; it renders as a side effect. - Vapor custom directives are functions receiving a reactive getter, optionally returning a cleanup function; effects created with
watchEffectinside them are auto-disposed.
Style and safety
- Trust the compiler: plain
ref,computed, and reactive props destructure cover everything Reactivity Transform did, without a macro layer. - Keep components on the latest API (
defineModel,useTemplateRef,useId) so upgrades stay mechanical. - This skill targets a beta; before adopting a Vapor or 3.6-only API in production code, check the changelog for changes after beta.17.