Vue 3.5: The 'Minor' Release That Rewrote the Rules of Frontend Performance

Vue 3.5: The 'Minor' Release That Rewrote the Rules of Frontend Performance

lschvn10 min read

Vue 3.5 dropped in September 2024 with what Evan You called a minor release — and a refactored reactivity system that delivers 56% lower memory usage and up to 10× faster operations on large, deeply reactive arrays. The developer community's response was roughly: "This does not feel like a minor release."

The numbers back that instinct up. Vue 3.5's refactored reactivity system delivers 56% lower memory usage and up to 10× faster operations on large, deeply reactive arrays. Those aren't incremental gains — they're the kind of improvements that change what "large-scale Vue" means in practice.

This article is a look at what actually changed, what it means for your applications, and where Vue is headed next.

What Made Vue 3.5 Worth Upgrading

A Rewritten Reactivity System

The core change is a full internal refactor of how Vue tracks reactive state. The goal was eliminating stale computed values and memory leaks that could accumulate during server-side rendering — a class of bug that tends to surface in production under sustained load.

The result was a net win across the board: lower memory usage, better performance on deeply nested reactive structures, and resolution of long-standing issues with "hanging computeds" in SSR contexts. Critically, the refactor had no behavior changes — everything that worked before still works. It is purely an internal improvement.

For applications that maintain large reactive data structures — think dashboards with real-time data, complex forms, or collaborative editing surfaces — these gains compound into meaningfully snappier interactions.

Reactive Props Destructure, Now Stabilized

One of the most-wanted features from the Composition API RFC process landed in 3.5 with its stabilization. Previously, destructuring props in <script setup> would break reactivity. The workaround was withDefaults() and explicit prop typing, which worked but felt verbose.

// Before 3.5 — the only reliable way
const props = withDefaults(
  defineProps<{
    count?: number
    message?: string
  }>(),
  { count: 0, message: 'hello' }
)

// After 3.5 — native destructuring works reactively
const { count = 0, message = 'hello' } = defineProps<{
  count?: number
  message?: string
}>()

The catch: computed properties and composables that consume destructured props still need a getter wrapper to maintain reactivity tracking. watch(() => count) works; watch(count) raises a compile error. This is a deliberate guard rail, not a bug.

Lazy Hydration for SSR

Server-side rendering performance has been a known pain point in the Vue ecosystem. The traditional SSR hydration model hydrates the entire page at once, which creates a waterfall of work on the client that users experience as slow Time-to-Interactive.

Vue 3.5 introduces a lower-level API for controlling hydration strategy. defineAsyncComponent() now accepts a hydrate option that lets you specify when a component should be hydrated:

import { defineAsyncComponent, hydrateOnVisible } from 'vue'

const AsyncComp = defineAsyncComponent({
  loader: () => import('./HeavyChart.vue'),
  hydrate: hydrateOnVisible()
})

Components can now be deferred until they scroll into view, become interactive, or match other conditions. The Nuxt team immediately began building higher-level syntax on top of this API, which tells you how long this capability has been needed.

useId(): Stable IDs Across Server and Client

Form accessibility requires unique for/id pairs. In SSR applications, generating these on the server and matching them on the client is a common source of hydration mismatches — the server generates one ID, the client generates another.

useId() solves this by generating IDs that are stable across the server/client boundary:

<script setup>
import { useId } from 'vue'
const id = useId()
</script>

<template>
  <form>
    <label :for="id">Email:</label>
    <input :id="id" type="email" />
  </form>
</template>

The same component rendered on the server or client produces the same ID. No more hydration warnings from mismatched form field associations.

data-allow-mismatch

SSR applications frequently produce content that legitimately differs between server and client renders — timestamps displayed in the user's local timezone, dates formatted by Intl, content that depends on client-side state available only after hydration.

Previously, these differences would produce console warnings that were noise rather than signal. data-allow-mismatch lets you explicitly suppress warnings for known, acceptable discrepancies:

<span data-allow-mismatch>{{ user.localBirthday }}</span>

You can scope it to specific mismatch types: text, children, class, style, or attribute. This is a quiet quality-of-life improvement that makes SSR debugging actually tractable in complex applications.

useTemplateRef()

Template refs in Vue 3 required ref attributes to be statically analyzable by the compiler — meaning they had to be static strings, not dynamic bindings. If you wanted a ref whose name came from a variable, you were out of luck.

useTemplateRef() resolves this by matching refs by runtime string ID rather than compile-time analysis:

<script setup>
import { useTemplateRef } from 'vue'

const inputRef = useTemplateRef('input')
// works with dynamic ref names
</script>

<template>
  <input :ref="dynamicRefName" />
</template>

Other Notable Additions

  • Deferred Teleport: <Teleport> previously required its target to exist at mount time. The defer prop in 3.5 lets you teleport to elements that don't exist yet but will be rendered later in the same cycle.
  • onWatcherCleanup(): A globally-imported API for registering cleanup callbacks inside watchers — the Vue-native answer to the AbortController pattern for canceling stale async operations.
  • Custom Elements improvements: useHost(), useShadowRoot(), this.$host, shadowRoot: false mounting option, and nonce injection for security-sensitive CSP environments.

TypeScript: Quietly Getting Better

Vue 3.5 also improved TypeScript inference in ways that matter for large codebases. Better inference for generic component types, improved typing for exposed template refs, and utility type fixes that reduce the need for manual type assertions.

If you've been using Vue with TypeScript and fighting with inference issues in <script setup>, 3.5 will make some of those fights disappear.

Vue 3.6: What's Coming

While 3.5 was cleaning up the present, Vue 3.6 is aimed at the future — and the headline feature is Vapor Mode.

Vapor Mode is a compilation strategy that eliminates the virtual DOM entirely. Instead of diffing a virtual DOM tree on every update, it compiles Vue templates to direct DOM operations — the same strategy that Solid.js uses to achieve its benchmark-topping performance.

The compelling claim from Evan You: Vapor Mode allows Vue to reach Solid.js-level rendering performance while keeping the exact same Vue API. You don't rewrite your components. You opt individual sub-trees into Vapor mode, and the compiler handles the rest.

The performance target is striking: 100,000 component mounts in 100ms. For context, Vue 3's virtual DOM handles roughly 10,000-20,000 component mounts in the same timeframe. This compilation approach sits within a broader shift in the JavaScript toolchain — see our coverage of Vite+ and the Rust-based toolchain that's reshaping how frameworks build and ship.

Vapor Mode is currently in beta (3.6.0-beta versions are on npm now). The integration into the core Vue repository is underway. A stable release is expected in 2026.

Also notable in the 3.6 pipeline:

  • Alien Signals: A reactivity optimization that reduces memory usage by a further 14% compared to 3.5, developed by Johnson Chu
  • Vue base bundle under 10KB: The runtime footprint is shrinking significantly
  • Rolldown 1.0: The Rust-based Rollup replacement is now in production, which is the bundler underpinning Vite — faster builds for everyone using Vite in 2026

Why It Matters

Vue's evolution has followed an interesting pattern. Each version has taken the framework further from "simple tool for small projects" and closer to "serious infrastructure for large applications" — without abandoning the developer experience that made Vue appealing in the first place.

Vue 3.5 is a case study in that balance. The memory and performance improvements are the kind that make production systems measurably better — not cosmetic, not theoretical, but real. The new APIs (lazy hydration, useId, useTemplateRef) address problems that developers have been working around for years.

The trajectory toward 3.6 and Vapor Mode suggests Vue is not satisfied with matching the competition. It wants to set the performance bar. That is an interesting ambition for a framework that has always defined itself by accessibility and ergonomics rather than raw speed.

Whether Vapor Mode delivers on its promise — and whether it can maintain compatibility with the existing ecosystem during the transition — will determine whether Vue 3.6 is remembered as a pivot point or just another release. The early signals are promising.

Related articles

More coverage with overlapping topics and tags.

Oxc Is Quietly Building the Fastest JavaScript Toolchain in Rust — And It's Almost Ready
javascript

Oxc Is Quietly Building the Fastest JavaScript Toolchain in Rust — And It's Almost Ready

While ESLint v10 was wrestling with legacy cleanup, the Oxc project shipped a linter 100x faster, a formatter 30x faster than Prettier, and a parser that leaves SWC in the dust. Here's what the JavaScript oxidation compiler actually is.
Knip v6 Lands oxc Parser for 2-4x Performance Gains Across the Board
typescript

Knip v6 Lands oxc Parser for 2-4x Performance Gains Across the Board

The popular dependency and unused-code scanner for JavaScript and TypeScript gets a major overhaul, replacing its TypeScript backend with the Rust-based oxc-parser — and the results are dramatic.
Pretext: The DOM-Free Text Measurement Library That AI Coding Agents Are Already Using
javascript

Pretext: The DOM-Free Text Measurement Library That AI Coding Agents Are Already Using

Cheng Lou just released Pretext, a pure JavaScript library that measures and lays out multiline text without touching the DOM. Here's why that matters for virtualization, layout control, and AI agents that write UI code.

Comments

Log in Log in to join the conversation.

No comments yet. Be the first to share your thoughts.