Vue 3 in depth

Vue 3.6 and Vapor mode

What Vue 3.5 added, what the 3.6 release candidate changes (Vapor mode, the alien-signals reactivity core), what stays the same, and how to talk about it without overclaiming.

"What is coming in Vue?" is a warm-up question with a trap in it: Vue 3.6 with Vapor mode has been "almost here" for two years, and candidates who say it has shipped get corrected. As of September 2026 the stable release is 3.5.43 and 3.6 is at release candidate 9. Know what each version does, and say "release candidate" until you have checked the tags.

Know — Vue 3.5 (what Nuxt 4 runs on)

  • Reactivity refactor: less memory, faster tracking, no behaviour change. The version that made deep reactivity on large arrays noticeably cheaper.
  • Reactive props destructure: const { size = 'md' } = defineProps<Props>() keeps reactivity.
  • useTemplateRef(), useId(), onWatcherCleanup(), deep: number on watchers, pause()/resume() on watcher handles.
  • Lazy hydration strategies for async components (hydrateOnVisible, hydrateOnIdle, hydrateOnInteraction, hydrateOnMediaQuery), surfaced in Nuxt as hydrate-on-* props on Lazy components.
  • SSR: data-allow-mismatch, useId stability, and Teleport defer.
  • app.config.throwUnhandledErrorInProduction and Trusted Types support.

Know — Vue 3.6 (release candidate)

  • Vapor mode is an opt-in compilation strategy that skips the virtual DOM: a <script setup vapor> component compiles to code that creates DOM nodes once and binds reactive expressions to them directly, in the style of Solid or Svelte 5. It is opt-in per component, interoperates with regular virtual-DOM components through an interop layer, supports a subset of the API (<script setup> and Composition API; no Options API), and shrinks the baseline bundle because the runtime it needs is smaller.
  • New reactivity core based on alien-signals, a push-pull signal algorithm. It applies to every component, not only Vapor ones, and is the source of the "faster and less memory again" headline. Semantics are meant to be unchanged; edge cases around effect ordering are the thing to test in a big app.
  • What does not change: SFCs, templates, the Composition API, Pinia, Nuxt usage. Vapor is an optimisation you can adopt component by component, not a new framework.
  • What it means for Nuxt: Nuxt 4 already supports Vapor in interop mode behind the experimental vue: { vapor: true } option (it installs Vue's vaporInteropPlugin), which needs a Vue 3.6 release candidate installed. The application root stays virtual DOM and you opt in per component with <script setup vapor>. Documented limitations: no full-Vapor apps yet, template refs on Vapor components expose no $el, the Options API (and therefore defineNuxtComponent's asyncData) is unavailable inside them, some built-ins cannot inspect Vapor slot children, and Nuxt composables called after an await may lose the Nuxt context. Try it in a fresh project, not by flipping the flag on a production app.
  • Ecosystem context: every major framework has converged on signals (Solid, Svelte 5 runes, Angular signals, Preact signals, and a TC39 Signals proposal at stage 1). Being able to explain why (fine-grained updates without a diff, compiler-friendly) is the senior version of this answer.
  • Around Vue: Vue Router 5 ships with Nuxt 4.5, Pinia 4 is ESM-only and needs @vue/devtools-api alongside it, and @nuxt/test-utils 4 runs on Vitest 4 or 5 (Vitest 5 shipped in September 2026).

How it works

A component in the two modes. The template is identical; only the compile target differs:

<script setup lang="ts">
const count = ref(0)
const double = computed(() => count.value * 2)
</script>

<template>
  <button type="button" @click="count++">
    {{ count }} × 2 = {{ double }}
  </button>
</template>

What the compiler produces conceptually for the Vapor version: create the button once, then bind each expression to the text node that displays it.

conceptual output
const button = template('<button type="button"> </button>')()
const text = button.firstChild!
renderEffect(() => { text.data = `${count.value} × 2 = ${double.value}` })
button.addEventListener('click', () => count.value++)

No vnode tree, no diff: when count changes, the one effect that reads it updates the one text node.

And the 3.5 APIs a component written today should already use:

app/components/Team/SearchField.vue
<script setup lang="ts">
const { placeholder = 'Search' } = defineProps<{ placeholder?: string }>()
const query = defineModel<string>({ default: '' })
const input = useTemplateRef<HTMLInputElement>('input')
const id = useId()

watch(query, (q) => {
  const controller = new AbortController()
  onWatcherCleanup(() => controller.abort())
  void $fetch('/api/search', { query: { q }, signal: controller.signal })
})

defineExpose({ focus: () => input.value?.focus() })
</script>

<template>
  <label :for="id">{{ placeholder }}</label>
  <input :id="id" ref="input" v-model="query" type="search">
</template>
Gotcha· Overclaiming

"We could switch the app to Vapor and cut the bundle in half" is not a sentence to say in an interview in 2026. Vapor is per component, opt-in, in release candidate, and Nuxt integration follows Nuxt's own schedule. The credible version: "I would try 3.6's reactivity core first, because everyone gets it, then evaluate Vapor on isolated leaf components in Nuxt's interop mode, which is experimental and needs the 3.6 release candidate."

Verify before the interview:

Whether 3.6.0 stable has shipped, the exact Vapor API surface (vapor attribute, the interop plugin) and the current list of limitations of Nuxt's vue.vapor interop mode. Read the release notes and this docs section the week of the interview.

docs ↗

Exercise

Exercise
  • Install vue@3.6.0-rc.x in a scratch Vite project, write the Counter above in Vapor mode, and inspect the compiled output in the Vite dev server. Compare bundle sizes of a Vapor app and a virtual-DOM app with one component each.
  • Upgrade a non-trivial Vue 3.5 playground to the 3.6 release candidate and run its tests; note anything that changed in effect ordering.
  • Write one paragraph explaining signals to a colleague who knows React, without using the word "reactive".

Be able to say

Be able to say· What is Vapor mode?

"An opt-in compilation strategy in Vue 3.6 that compiles a component's template to direct DOM operations bound to reactive expressions, instead of producing virtual DOM that gets diffed. You enable it per component with <script setup vapor>, it uses the Composition API subset, and it interoperates with normal components, so you can adopt it on leaf components first. The win is smaller baseline runtime and fine-grained updates. Underneath, 3.6 also swaps the reactivity core for an alien-signals-based push-pull algorithm, and that part every component gets. As of this month 3.6 is a release candidate, so I would describe it as imminent, not shipped; Nuxt 4 can already run Vapor components in interop mode behind an experimental flag."

Be able to say· Which Vue 3.5 features do you use every day?

"Reactive props destructure for defaults without withDefaults, useTemplateRef for explicit template refs, useId for SSR-stable ids in forms and ARIA wiring, onWatcherCleanup for cancelling in-flight requests inside watchers, and the lazy hydration strategies through Nuxt's Lazy components with hydrate-on-visible. Behind the scenes the 3.5 reactivity refactor made deep tracking of large data cheaper, which changed how often I reach for shallowRef."

Your notes

Open in Notes

Nothing yet. Notes filed here show up on this page and the section overview.