Vue.js Upgrade Guide

Compare Vue.js versions and see exactly what changes, what requires action, and what you can start using.

16Changes
1Breaking
1Actions
13Features
0Migration tools

Breaking Changes

Deprecated parseExpressions option was removed

Action required
breakinglow impact

Vue 3.5 removes the previously deprecated parseExpressions compiler option.

Migration

Remove usage of the deprecated parseExpressions compiler option from custom compiler integrations.

Official sources

New Features

Reactive Props Destructure became stable

featurehigh impact

Vue 3.5 enables Reactive Props Destructure by default. Variables destructured from defineProps() remain reactive and can use native JavaScript default-value syntax.

Beforevue
<script setup lang="ts">
const props = withDefaults(
  defineProps<{
    count?: number
  }>(),
  {
    count: 0
  }
)

watchEffect(() => {
  console.log(props.count)
})
</script>
Aftervue
<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.

useTemplateRef() introduced

featurehigh impact

Vue 3.5 introduces useTemplateRef(), providing a cleaner Composition API for accessing template refs without manually declaring matching ref variables.

Beforevue
<script setup>
import {
  ref,
  onMounted
} from "vue"

const input = ref(null)

onMounted(() => {
  input.value.focus()
})
</script>

<template>
  <input ref="input" />
</template>
Aftervue
<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.

onWatcherCleanup() introduced

featurehigh impact

Vue 3.5 introduces onWatcherCleanup(), allowing cleanup logic to be registered from inside watch and watchEffect callbacks.

Aftervue
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.

Watchers gained pause and resume controls

featuremedium impact

Vue 3.5 extends watcher handles with pause() and resume(), allowing watcher execution to be temporarily suspended without stopping the watcher permanently.

Aftervue
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().

Official sources

Watcher deep option accepts a maximum traversal depth

featuremedium impact

Vue 3.5 allows the deep option on watchers to be a number, limiting how many object levels Vue traverses when tracking nested changes.

Beforevue
watch(
  state,
  callback,
  {
    deep: true
  }
)
Aftervue
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.

Official sources

Lazy hydration strategies introduced

featurehigh impact

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.

Aftervue
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.

useId() introduced

featuremedium impact

Vue 3.5 introduces useId() for generating application-unique IDs that remain stable across server rendering and client hydration.

Aftervue
<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.

Teleport can be deferred

featuremedium impact

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.

Beforevue
<Teleport to="#target">
  <Modal />
</Teleport>

<!-- target generally needs
     to already exist -->
Aftervue
<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.

Teleport can be nested directly inside Transition

featurelow impact

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.

Official sources

Hydration mismatches can be selectively suppressed

featuremedium impact

Vue 3.5 introduces the data-allow-mismatch attribute for intentionally suppressing specific SSR hydration mismatch warnings.

Aftervue
<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.

Applications can register unmount cleanup callbacks

featurelow impact

Vue 3.5 introduces app.onUnmount(), allowing plugins and application-level integrations to register cleanup callbacks that run when the application is unmounted.

Aftervue
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.

Official sources

Custom Element support significantly improved

featuremedium impact

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.

Unhandled production errors can be configured to throw

featuremedium impact

Vue 3.5 adds app.config.throwUnhandledErrorInProduction for applications that want unhandled framework errors to be thrown in production instead of only being logged.

Aftervue
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.

Official sources

Performance

Reactivity system was significantly optimized

performancehigh impact

Vue 3.5 refactors the reactivity system using version counting and doubly-linked dependency tracking, improving performance and reducing memory usage without changing expected application behavior.

Migration

No migration is required. Existing applications automatically benefit after upgrading.

Reactive array tracking was optimized

performancemedium impact

Vue 3.5 improves reactive array tracking, significantly reducing overhead for large reactive arrays and common array operations.

Migration

No migration is required. Applications using large reactive arrays receive the optimization automatically.

Official sources