Breaking Changes
The default prop and event used by v-model on custom components changed from value and input to modelValue and update:modelValue.
<script>
export default {
props: ['value'],
methods: {
updateValue(value) {
this.$emit('input', value)
}
}
}
</script>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
methods: {
updateValue(value) {
this.$emit('update:modelValue', value)
}
}
}
</script>
Migration Replace the value prop with modelValue and emit update:modelValue instead of input. Replace .sync usage with v-model arguments where applicable.
Vue 3 removed the filters syntax from templates. Expressions inside template interpolations and v-bind should use JavaScript expressions instead.
<template>
<p>{{ accountBalance | currencyUSD }}</p>
</template>
<template>
<p>{{ accountBalanceUSD }}</p>
</template>
<script>
export default {
computed: {
accountBalanceUSD() {
return '$' + this.accountBalance
}
}
}
</script>
Migration Replace template filters with methods or computed properties. If filters were registered globally, move the shared formatting logic to regular JavaScript functions or another shared utility.
Vue 3 introduces createApp() and moves APIs that globally mutate Vue behavior to an application instance. Root applications are no longer created with new Vue().
import Vue from 'vue'
import App from './App.vue'
Vue.use(MyPlugin)
Vue.component('MyComponent', MyComponent)
new Vue({
render: h => h(App)
}).$mount('#app')
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.use(MyPlugin)
app.component('MyComponent', MyComponent)
app.mount('#app')
Migration Create the root application with createApp(). Move globally mutating APIs such as Vue.use(), Vue.component(), Vue.directive(), Vue.mixin() and Vue.config to the returned application instance.
Vue 3 automatically generates unique keys for conditional branches. When using <template v-for>, the key should be placed on the <template> element instead of its children.
<template v-for="item in items">
<div :key="item.id">
{{ item.name }}
</div>
</template>
<template
v-for="item in items"
:key="item.id"
>
<div>
{{ item.name }}
</div>
</template>
Migration Remove unnecessary manual keys from v-if/v-else branches where possible. For <template v-for>, move the key to the <template> element.
When v-if and v-for are used on the same element, Vue 3 evaluates v-if before v-for. Vue 2 used the opposite precedence.
<template>
<li
v-for="user in users"
v-if="user.isActive"
:key="user.id"
>
{{ user.name }}
</li>
</template>
<script>
export default {
computed: {
activeUsers() {
return this.users.filter(
user => user.isActive
)
}
}
}
</script>
<template>
<li
v-for="user in activeUsers"
:key="user.id"
>
{{ user.name }}
</li>
</template>
Migration Avoid using v-if and v-for on the same element. Prefer filtering the collection with a computed property before rendering it with v-for.
When an element has both individual attributes and v-bind="object", Vue 3 determines which value wins based on their declaration order.
<div
id="red"
v-bind="{ id: 'blue' }"
></div>
<div
v-bind="{ id: 'blue' }"
id="red"
></div>
Migration Review elements that combine v-bind="object" with individual attributes. In Vue 3, place the binding that should take precedence later in the attribute list.
Vue 3 removes the .native modifier from v-on. Component-emitted events should be declared with the emits option, while undeclared listeners can fall through to the component root element.
<template>
<MyComponent
@close="handleClose"
@click.native="handleClick"
/>
</template>
<template>
<MyComponent
@close="handleClose"
@click="handleClick"
/>
</template>
<script>
export default {
emits: ['close']
}
</script>
Migration Remove .native modifiers and explicitly declare component-emitted events with the emits option. Review components that re-emit native events, because undeclared listeners can fall through to the root element.
Vue 3 removes the functional component option and the <template functional> SFC syntax. Functional components are now created as plain functions receiving props and context.
export default {
functional: true,
props: ['level'],
render(h, { props, data, children }) {
return h(
`h${props.level}`,
data,
children
)
}
}
import { h } from 'vue'
const DynamicHeading = (props, context) => {
return h(
`h${props.level}`,
context.attrs,
context.slots
)
}
DynamicHeading.props = ['level']
export default DynamicHeading
Migration Remove functional: true and convert JavaScript functional components to plain functions. For SFCs using <template functional>, remove the functional attribute and migrate props/attrs access to the Vue 3 syntax. Consider using a normal stateful component when functional rendering is not specifically needed.
Vue 3 introduces defineAsyncComponent() for explicitly creating async components. The component option is renamed to loader when using the advanced configuration format.
const AsyncComponent = () =>
import('./MyComponent.vue')
export default {
components: {
AsyncComponent
}
}
import { defineAsyncComponent } from 'vue'
const AsyncComponent = defineAsyncComponent(() =>
import('./MyComponent.vue')
)
export default {
components: {
AsyncComponent
}
}
Migration Wrap async component loader functions with defineAsyncComponent(). If using the advanced async component configuration, rename the component option to loader and review loading/error component behavior.
Vue 3 changes the render function API. The h function must be imported from Vue, VNode props use a flat structure, and registered components are resolved explicitly.
export default {
render(h) {
return h('div', {
attrs: {
id: 'app'
},
on: {
click: this.handleClick
}
})
}
}
import { h } from 'vue'
export default {
render() {
return h('div', {
id: 'app',
onClick: this.handleClick
})
}
}
Migration Import h from vue instead of receiving it as a render argument. Update VNode props to the flat Vue 3 structure and use resolveComponent when resolving registered components by name.
Vue 3 removes $scopedSlots. All slots are exposed through $slots and are accessed as functions.
this.$scopedSlots.header
this.$slots.default
this.$slots.header()
this.$slots.default()
Migration Replace $scopedSlots references with $slots and invoke slots as functions when accessing them programmatically.
Vue 3 removes the $listeners instance property. Event listeners are now included in $attrs using onXxx properties.
<input
v-bind="$attrs"
v-on="$listeners"
/>
<input v-bind="$attrs" />
Migration Remove usages of $listeners. Component listeners that are not declared as emitted events are available through $attrs.
Vue 3 includes class and style attributes in $attrs, including when inheritAttrs is false.
Migration Review components that manually forward $attrs or use inheritAttrs: false. Class and style are now included and may be forwarded together with other attributes.
Vue 3 aligns custom directive lifecycle hooks with component lifecycle hooks and removes binding.expression.
Vue.directive('highlight', {
bind(el, binding) {
el.style.background = binding.value
},
inserted(el) {},
componentUpdated(el) {},
unbind(el) {}
})
app.directive('highlight', {
beforeMount(el, binding) {
el.style.background = binding.value
},
mounted(el) {},
updated(el) {},
unmounted(el) {}
})
Migration Rename directive lifecycle hooks to the Vue 3 lifecycle equivalents. Replace bind with beforeMount, inserted with mounted, componentUpdated with updated, and unbind with unmounted.
Vue 3 renames beforeDestroy to beforeUnmount and destroyed to unmounted.
export default {
beforeDestroy() {
// cleanup
},
destroyed() {
// destroyed
}
}
export default {
beforeUnmount() {
// cleanup
},
unmounted() {
// unmounted
}
}
Migration Rename beforeDestroy to beforeUnmount and destroyed to unmounted.
Vue 3 requires the data option to be a function for component definitions.
const Component = {
data: {
count: 0
}
}
const Component = {
data() {
return {
count: 0
}
}
}
Migration Convert object-based data options to functions that return the component state.
Vue 3 changes data merging between components, mixins and extensions to a shallow merge instead of recursively merging nested objects.
Migration Review components that rely on nested data objects being recursively merged from mixins or extends. Explicitly combine nested state where required.
In Vue 3, watching an array triggers when the array is replaced. Watching mutations requires the deep option.
watch: {
items() {
this.handleItemsChanged()
}
}
watch: {
items: {
handler() {
this.handleItemsChanged()
},
deep: true
}
}
Migration Add the deep option to array watchers when the application needs to react to array mutations rather than only array replacement.
Vue 3 renames the initial transition classes to make the transition state names consistent.
.v-enter {
opacity: 0;
}
.v-leave {
opacity: 1;
}
.v-enter-from {
opacity: 0;
}
.v-leave-from {
opacity: 1;
}
Migration Replace *-enter with *-enter-from and *-leave with *-leave-from. Update the related enter-class and leave-class component props as well.
Vue 3 changes TransitionGroup so it does not render a wrapper element by default.
Migration If the Vue 2 wrapper element is required for layout or styling, explicitly provide the tag prop on TransitionGroup.
Vue 3 removes numeric keyCode modifiers and the config.keyCodes API.
<input @keyup.13="submit" />
<input @keyup.enter="submit" />
Migration Replace numeric keyCode modifiers with named key modifiers. Remove custom config.keyCodes mappings and use KeyboardEvent key names or explicit event handling where necessary.
Vue 3 removes the component instance event-emitter methods $on, $off and $once.
Migration Replace Vue instances used as event buses with an external event emitter or another explicit state/event communication pattern.
Vue 3 removes the $children instance property.
Migration Replace code that depends on $children with explicit template refs, props, provide/inject, or another explicit parent-child communication mechanism.
Vue 3 removes propsData when creating component instances. Root props can instead be passed as the second argument to createApp.
new Comp({
propsData: {
username: 'Evan'
}
})
createApp(
Comp,
{
username: 'Evan'
}
)
Migration Remove propsData usage. Pass root component props as the second argument to createApp or use normal component rendering APIs for child components.
Vue 3 proxy-based reactivity no longer requires special APIs for adding or deleting reactive object properties.
this.$set(user, 'name', 'Volkan')
this.$delete(user, 'legacy')
user.name = 'Volkan'
delete user.legacy
Migration Replace Vue.set/$set with normal property assignment and Vue.delete/$delete with the JavaScript delete operator.
Vue 3 mounts the rendered application inside the target container instead of replacing the target element itself.
<div id="app"></div>
<!-- Vue 2 may replace #app with the rendered root -->
<div id="app">
<!-- Vue 3 renders the application here -->
</div>
Migration Review CSS, DOM selectors and integrations that assume the mount container itself is replaced by the root component.
Vue 3 performs custom element checks during template compilation. The special is attribute is restricted to the reserved <component> element for component switching.
Migration Move custom element detection to compilerOptions.isCustomElement in the compiler or build configuration. Review usages of is on native elements and use <component :is="..."> for dynamic Vue components.
Vue 3 removes component instance access through this inside prop default factory functions. Raw incoming props are provided as an argument instead.
export default {
props: {
theme: {
default() {
return this.defaultTheme
}
}
}
}
import { inject } from 'vue'
export default {
props: {
theme: {
default(props) {
return inject(
'theme',
'default-theme'
)
}
}
}
}
Migration Remove component instance access from prop default factories. Use the raw props argument when another incoming prop is needed, or inject() when injected values are required.
Vue 3 removes the special enumerated-attribute coercion behavior. For non-boolean attributes, false is serialized as "false" instead of removing the attribute.
<div :aria-hidden="false"></div>
<!-- Vue 2 could remove some
non-boolean attributes
when bound to false -->
<div :aria-hidden="false"></div>
<!-- Vue 3 renders:
aria-hidden="false"
Use null or undefined
to remove the attribute. -->
Migration Review bindings that depend on false removing a non-boolean attribute. Use null or undefined when the attribute should be removed.
A <template> element without a Vue special directive is treated as a native template element in Vue 3 instead of transparently rendering only its inner content.
Migration Review plain <template> elements that do not use v-if, v-else, v-for or v-slot. Remove unnecessary template wrappers when the Vue 2 transparent-wrapper behavior was intended.
Vue 3 replaces the hook: prefix used for VNode lifecycle events with vue:. These events can also be used on HTML elements.
<ChildComponent
@hook:updated="onUpdated"
/>
<ChildComponent
@vue:updated="onUpdated"
/>
Migration Replace hook: lifecycle event prefixes with vue:. Also account for the beforeDestroy/beforeUnmount and destroyed/unmounted lifecycle renames where applicable.
Vue 3 removes the inline-template attribute that allowed child component content to act as the component template.
<MyComponent inline-template>
<div>
{{ message }}
</div>
</MyComponent>
<MyComponent v-slot="{ message }">
<div>
{{ message }}
</div>
</MyComponent>
Migration Replace inline-template usage with regular component templates, external template sources, or scoped/default slots depending on the original use case.
Vue 3 removes the $destroy component instance method. Applications should not manually manage the lifecycle of individual component instances.
Migration Remove manual $destroy calls and control component lifetime through normal declarative rendering and application unmounting.
Vue 3 restructures a number of global APIs as named ES module exports so unused APIs can be removed by tree-shaking.
import Vue from 'vue'
Vue.nextTick(() => {
// DOM update complete
})
const state = Vue.observable({
count: 0
})
import {
nextTick,
reactive
} from 'vue'
nextTick(() => {
// DOM update complete
})
const state = reactive({
count: 0
})
Migration Replace affected Vue.* global API calls with named imports from vue. For example, import nextTick directly and replace Vue.observable with reactive.
Vue 3.3 repurposes the rarely used defineComponent() function overload to support improved generic component typing, changing its TypeScript type behavior.
Migration Review code that passes a function directly to defineComponent(). This overload was rarely used, but its type signature changed in Vue 3.3.
Vue 3.4 no longer registers the global JSX namespace by default, avoiding global type collisions with other JSX ecosystems such as React.
{
"compilerOptions": {
"jsx": "preserve"
}
}
{
"compilerOptions": {
"jsx": "preserve",
"jsxImportSource": "vue"
}
}
Migration TSX users should set jsxImportSource to vue in tsconfig.json or add an @jsxImportSource vue pragma per file. Projects that depend on the old global JSX namespace can explicitly reference vue/jsx.
Vue 3.4 removes the experimental Reactivity Transform feature, including compile-time macros such as $ref(), from Vue core.
<script setup>
let count = $ref(0)
count++
</script>
<script setup>
import { ref } from "vue"
const count = ref(0)
count.value++
</script>
Migration Replace Reactivity Transform macros with standard Vue reactivity APIs, or migrate to the Vue Macros plugin if the transform syntax should be retained.
Vue 3.4 removes app.config.unwrapInjectedRef. Injected refs are unwrapped according to the behavior that had already become the default in Vue 3.3.
app.config.unwrapInjectedRef = false
Migration Remove usages of app.config.unwrapInjectedRef. Vue 3.4 no longer allows opting out of the injected-ref unwrapping behavior.
Vue 3.4 removes the deprecated @vnodeXXX template event syntax. The replacement @vue:XXX lifecycle listener syntax must be used.
<MyComponent
@vnodeMounted="onMounted"
/>
<MyComponent
@vue:mounted="onMounted"
/>
Migration Replace deprecated @vnodeXXX lifecycle event listeners with the corresponding @vue:XXX syntax.
Vue 3.4 removes the deprecated v-is directive.
<tr v-is="'vue:my-row'"></tr>
<tr is="vue:my-row"></tr>
Migration Replace v-is with the is attribute and the vue: prefix where Vue component resolution on native elements is required.
Vue 3.5 removes the previously deprecated parseExpressions compiler option.
Migration Remove usage of the deprecated parseExpressions compiler option from custom compiler integrations.
New Features
Vue 3 introduces fragment support, allowing components to render multiple root nodes instead of requiring a single root element.
<template>
<div>
<header>...</header>
<main>...</main>
</div>
</template>
<template>
<header>...</header>
<main>...</main>
</template>
Migration The wrapper element can be removed when it only existed to satisfy the Vue 2 single-root requirement. For multi-root components, review attribute inheritance because Vue cannot automatically determine which root should receive fallthrough attributes.
Vue 3 introduces the emits option, allowing components to explicitly declare the events they emit, similar to how props declares accepted properties.
<script>
export default {
props: ['text'],
methods: {
accept() {
this.$emit('accepted')
}
}
}
</script>
<script>
export default {
props: ['text'],
emits: ['accepted'],
methods: {
accept() {
this.$emit('accepted')
}
}
}
</script>
Migration Declare the events emitted by each component using the emits option. This is especially important when migrating components that previously relied on the .native modifier or re-emitted native DOM events.
Vue 3 introduces the Composition API, providing APIs such as setup(), ref(), reactive(), computed() and lifecycle hooks for organizing component logic by feature instead of option type.
<script>
export default {
data() {
return {
count: 0
}
},
computed: {
doubled() {
return this.count * 2
}
},
methods: {
increment() {
this.count++
}
}
}
</script>
<script>
import {
ref,
computed
} from 'vue'
export default {
setup() {
const count = ref(0)
const doubled = computed(
() => count.value * 2
)
const increment = () => {
count.value++
}
return {
count,
doubled,
increment
}
}
}
</script>
Migration Existing Options API components do not need to be rewritten. Composition API is an additional way to author components and can be adopted incrementally where it improves logic reuse or organization.
Vue 3 introduces the built-in <Teleport> component, allowing part of a component template to be rendered into a DOM node outside of the component’s own DOM hierarchy.
<template>
<div class="page">
<MyModal v-if="showModal" />
</div>
</template>
<template>
<div class="page">
<!-- page content -->
</div>
<Teleport to="body">
<MyModal v-if="showModal" />
</Teleport>
</template>
Migration No migration is required. Teleport can replace custom portal-style solutions when UI such as modals, overlays or notifications needs to render outside the component DOM hierarchy.
Vue 3 introduces the built-in <Suspense> component for coordinating asynchronous dependencies in a component tree and displaying fallback content while they resolve.
<template>
<div v-if="loading">
Loading...
</div>
<AsyncComponent v-else />
</template>
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
Loading...
</template>
</Suspense>
</template>
Migration No migration is required. Suspense can coordinate loading states for supported asynchronous dependencies, but it should be adopted with care because the API is experimental.
Vue 3 exposes a custom renderer API that allows Vue components and its reactivity system to target rendering environments other than the browser DOM.
Migration No migration is required for normal Vue applications. The API is intended primarily for authors building custom renderers for non-DOM environments.
Vue 3 supports the v-bind() CSS function in Single-File Component style blocks, allowing CSS values to react to component state.
<template>
<div :style="{ color: themeColor }">
Hello
</div>
</template>
<script>
export default {
data() {
return {
themeColor: 'red'
}
}
}
</script>
<template>
<div class="message">
Hello
</div>
</template>
<script>
export default {
data() {
return {
themeColor: 'red'
}
}
}
</script>
<style>
.message {
color: v-bind(themeColor);
}
</style>
Migration No migration is required. CSS v-bind() can be adopted when reactive component state should be consumed directly inside an SFC style block.
Vue 3.1 adds onServerPrefetch(), allowing Composition API components to register asynchronous work that should be resolved during server-side rendering.
<script>
import {
ref,
onServerPrefetch
} from 'vue'
export default {
setup() {
const data = ref(null)
onServerPrefetch(async () => {
data.value = await fetchData()
})
return {
data
}
}
}
</script>
Migration No migration is required. Use onServerPrefetch() when Composition API logic needs to fetch or prepare asynchronous data during server-side rendering.
Vue 3.2 stabilizes <script setup>, a compile-time syntax for using the Composition API inside Single-File Components with less boilerplate.
<script>
import { ref } from "vue"
export default {
setup() {
const count = ref(0)
const increment = () => {
count.value++
}
return {
count,
increment
}
}
}
</script>
<script setup>
import { ref } from "vue"
const count = ref(0)
const increment = () => {
count.value++
}
</script>
Migration No migration is required. Existing setup() components remain valid. <script setup> can be adopted incrementally to reduce Composition API boilerplate in Single-File Components.
Vue 3.2 stabilizes the :slotted() pseudo-class for scoped SFC styles, allowing a component to explicitly style content passed through slots.
<template>
<slot />
</template>
<style scoped>
:slotted(.message) {
color: red;
}
</style>
Migration No migration is required. Use :slotted() when scoped component styles need to target elements provided by a parent through slots.
Vue 3.2 introduces defineCustomElement(), allowing Vue components to be packaged and registered as native Custom Elements.
import {
defineCustomElement
} from "vue"
import MyElement from "./MyElement.ce.vue"
const MyCustomElement =
defineCustomElement(MyElement)
customElements.define(
"my-element",
MyCustomElement
)
Migration No migration is required. Use defineCustomElement() when Vue components need to be distributed or consumed as native Web Components.
Vue 3.2 introduces effectScope() for grouping reactive effects such as computed values and watchers so they can be disposed together.
import {
effectScope,
computed,
watch
} from "vue"
const scope = effectScope()
scope.run(() => {
const doubled = computed(
() => state.count * 2
)
watch(
() => state.count,
() => {
// react to changes
}
)
})
scope.stop()
Migration No migration is required. effectScope() is primarily useful for reusable composables and libraries that need explicit control over groups of reactive effects.
Vue 3.2 expands server-side rendering APIs with renderToWebStream() for environments that support the Web Streams API.
import {
renderToWebStream
} from "@vue/server-renderer"
const stream =
renderToWebStream(app)
Migration No migration is required. The streaming API can be adopted by SSR runtimes that support Web Streams.
Vue 3.3 introduces defineOptions(), allowing additional component options such as inheritAttrs to be declared directly inside <script setup>.
<script>
export default {
inheritAttrs: false
}
</script>
<script setup>
// component logic
</script>
<script setup>
defineOptions({
inheritAttrs: false
})
</script>
Migration No migration is required. Components that previously needed a second normal <script> block solely for supported component options can use defineOptions().
Vue 3.3 introduces experimental support for destructuring defineProps() while preserving reactivity and allowing native default-value syntax.
Migration No migration is required. This feature was experimental in Vue 3.3 and should be treated according to the behavior and stability of the target Vue version.
Vue 3.3 introduces the experimental defineModel() compiler macro for declaring a component v-model prop and its corresponding update event with less boilerplate.
<script setup>
const props = defineProps({
modelValue: String
})
const emit = defineEmits([
"update:modelValue"
])
</script>
<script setup>
const modelValue =
defineModel<string>()
</script>
Migration No migration is required. In Vue 3.3 defineModel() was experimental, so adoption should account for the stability of the target Vue release.
Vue 3.3 enhances toRef() so it can normalize values, getters and existing refs, and introduces toValue() for normalizing values, refs and getters into values.
import {
ref,
toRef,
toValue
} from "vue"
const existing = ref(1)
toRef(1)
toRef(() => props.count)
toRef(existing)
toValue(1)
toValue(existing)
toValue(() => props.count)
Migration No migration is required. Composables can use toValue() when accepting values, refs or getters, and the normalization form of toRef() when a ref representation is needed.
Vue 3.3 improves getter handling in reactivity utilities, making getter-based reactive inputs easier and more efficient to consume in composables.
useFeature(
computed(() => props.id)
)
useFeature(
() => props.id
)
Migration No migration is required. Composables can increasingly accept getters directly instead of requiring callers to allocate intermediate computed refs.
Vue 3.4 promotes defineModel() from experimental to stable, providing a concise way to declare a component v-model prop and its corresponding update event.
<script setup lang="ts">
const props = defineProps<{
modelValue: string
}>()
const emit = defineEmits<{
"update:modelValue": [value: string]
}>()
</script>
<script setup lang="ts">
const model = defineModel<string>()
</script>
Migration No migration is required. Projects that avoided defineModel() while it was experimental can now adopt the stable API. Vue 3.4 also removes the previous local option and supports local mutation by default.
Vue 3.4 supports a shorthand for bindings where the attribute name and JavaScript variable name are identical.
<img
:id="id"
:src="src"
:alt="alt"
/>
Migration No migration is required. The shorthand can be adopted where a v-bind argument has the same name as the bound variable.
Vue 3.4 adds built-in recognition and rendering support for MathML elements.
<template>
<math>
<mfrac>
<mi>a</mi>
<mi>b</mi>
</mfrac>
</math>
</template>
Migration No migration is required. MathML can be used directly in Vue templates.
Vue 3.5 enables Reactive Props Destructure by default. Variables destructured from defineProps() remain reactive and can use native JavaScript default-value syntax.
<script setup lang="ts">
const props = withDefaults(
defineProps<{
count?: number
}>(),
{
count: 0
}
)
watchEffect(() => {
console.log(props.count)
})
</script>
<script setup lang="ts">
const {
count = 0
} = defineProps<{
count?: number
}>()
watchEffect(() => {
console.log(count)
})
</script>
Migration No migration is required. Reactive Props Destructure is enabled by default in Vue 3.5 and can be adopted incrementally.
Vue 3.5 introduces useTemplateRef(), providing a cleaner Composition API for accessing template refs without manually declaring matching ref variables.
<script setup>
import {
ref,
onMounted
} from "vue"
const input = ref(null)
onMounted(() => {
input.value.focus()
})
</script>
<template>
<input ref="input" />
</template>
<script setup>
import {
useTemplateRef,
onMounted
} from "vue"
const input =
useTemplateRef("input")
onMounted(() => {
input.value?.focus()
})
</script>
<template>
<input ref="input" />
</template>
Migration No migration is required. Existing template refs remain valid. useTemplateRef() can be adopted for clearer template-ref access and improved tooling support.
Vue 3.5 introduces onWatcherCleanup(), allowing cleanup logic to be registered from inside watch and watchEffect callbacks.
import {
watch,
onWatcherCleanup
} from "vue"
watch(id, async (newId) => {
const controller =
new AbortController()
onWatcherCleanup(() => {
controller.abort()
})
await fetch(
`/api/users/${newId}`,
{
signal: controller.signal
}
)
})
Migration No migration is required. onWatcherCleanup() provides an alternative cleanup API for watchers and is particularly useful for cancelling stale asynchronous work.
Vue 3.5 extends watcher handles with pause() and resume(), allowing watcher execution to be temporarily suspended without stopping the watcher permanently.
const {
stop,
pause,
resume
} = watch(source, callback)
pause()
// watcher temporarily suspended
resume()
// watcher active again
stop()
Migration No migration is required. Existing watcher handles continue to work and can optionally use pause() and resume().
Vue 3.5 allows the deep option on watchers to be a number, limiting how many object levels Vue traverses when tracking nested changes.
watch(
state,
callback,
{
deep: true
}
)
watch(
state,
callback,
{
deep: 2
}
)
Migration No migration is required. Numeric deep values can be used to reduce traversal costs when full deep observation is unnecessary.
Vue 3.5 allows async components to control when server-rendered markup is hydrated using built-in strategies such as idle, visibility, interaction and media-query hydration.
import {
defineAsyncComponent,
hydrateOnVisible
} from "vue"
const AsyncCard =
defineAsyncComponent({
loader: () =>
import("./Card.vue"),
hydrate:
hydrateOnVisible()
})
Migration No migration is required. SSR applications can adopt lazy hydration strategies for async components where immediate hydration is unnecessary.
Vue 3.5 introduces useId() for generating application-unique IDs that remain stable across server rendering and client hydration.
<script setup>
import { useId } from "vue"
const id = useId()
</script>
<template>
<label :for="id">
Name
</label>
<input :id="id" />
</template>
Migration No migration is required. useId() is useful for accessible form relationships and reusable SSR-safe components that require unique identifiers.
Vue 3.5 adds the defer prop to Teleport, allowing its target to be resolved after other parts of the same update cycle have mounted.
<Teleport to="#target">
<Modal />
</Teleport>
<!-- target generally needs
to already exist -->
<Teleport
defer
to="#target"
>
<Modal />
</Teleport>
<div id="target"></div>
Migration No migration is required. Use defer when the Teleport target is rendered later in the same component tree.
Vue 3.5 supports directly nesting Teleport inside Transition, simplifying transition handling for teleported content.
Migration No migration is required. Existing Teleport and Transition usage remains valid.
Vue 3.5 introduces the data-allow-mismatch attribute for intentionally suppressing specific SSR hydration mismatch warnings.
<span
data-allow-mismatch="text"
>
{{ new Date().toLocaleString() }}
</span>
Migration No migration is required. Use data-allow-mismatch only for intentional server/client differences rather than hiding actual hydration bugs.
Vue 3.5 introduces app.onUnmount(), allowing plugins and application-level integrations to register cleanup callbacks that run when the application is unmounted.
const app = createApp(App)
app.onUnmount(() => {
cleanupExternalResources()
})
app.mount("#app")
Migration No migration is required. Plugin and integration authors can use app.onUnmount() for application-level cleanup.
Vue 3.5 improves Vue Custom Elements with APIs and behavior for host access, shadow-root configuration, CSP nonces and other integration scenarios.
Migration No migration is required. Applications and libraries using Vue Custom Elements can adopt the expanded configuration and host APIs as needed.
Vue 3.5 adds app.config.throwUnhandledErrorInProduction for applications that want unhandled framework errors to be thrown in production instead of only being logged.
const app = createApp(App)
app.config
.throwUnhandledErrorInProduction =
true
Migration No migration is required. Enable this option only when production error handling and monitoring are prepared for thrown unhandled errors.