What's new in Vue 3.4?

Vue 3.4.0 is here!

Vue logo

The last minor release was v3.3.0 in May. Since then, we have seen a few patch releases, some coming with new features.

Let’s see what we have in this release!

v-bind shorthand syntax

It is now possible to use a shorthand syntax for v-bind when the key and value have the same name!

<!-- before -->
<div v-bind:id="id"></div>
<-- or -->
<div :id="id"></div>

<!-- after -->
<div v-bind:id></div>
<-- or -->
<div :id></div>

Performances improvements for the reactivity system

Johnson Chu, the author of Volar, has done massive work to improve the performance of the reactivity system.

Let’s consider a scenario where you have a computed A that depends on a computed B. In Vue v3.3, if B is re-evaluated, A is also re-evaluated, even if B has the same value as before. In Vue v3.4, A is not re-evaluated if B has the same value as before. This is also true for watch functions.

Other improvements have been made for Arrays mutations, for watchers that depend on multiple computed values, and more (as you can see in the PR description).

This should avoid a whole lot of unnecessary re-renders! 🚀 (and hopefully, don’t introduce any regression 🤞).

computed previous value

You can now get the previous value in a computed, as the first parameter of the getter function.

const count = ref(0);
const double = computed((prev) => {
  return count.value * 2
// logs 0

This can be useful if you want to manually compare object values. (computed internally uses Object.is to compare the previous and current values, which is not always what you want, see the PR description). This is especially useful with the new reactivity system improvements. In v3.4, a computed property will only trigger effects when its computed value has changed from the previous one. But in the case of a computed that return new objects, Vue thinks that the previous and current values are different. If you want to avoid triggering effects in that case, you can compare the previous and current values manually.

Performances improvements for the compiler

The Vue compiler has been improved to be faster. Evan rewrote the parser entirely, to avoid using regexes. The code generation has also been improved. They are now nearly 2 times faster!

This should not have a huge impact on your build times, as the Vue compiler is not the only step in the build process (you usually have the TypeScript compiler, the CSS preprocessor, etc.).

Support for import attributes

It is now possible to use import attributes in SFC (both in JS and TS):

import json from "./foo.json" with { type: "json" }

The support for using has also been added (new feature for explicit resource management in JS, see the proposal here).

watch once

The watch function gained a new option called once. When set to true, the watcher is removed after the first call.

watch('foo', () => {
  console.log('foo changed');
}, { once: true });

It was previously possible to achieve the same result by using the returned stop function:

const stop = watch('foo', () => {
  console.log('foo changed');

Props validation

As you probably know, Vue provides a mechanism to validate props.

  min: {
    type: Number,
    required: true,
    validator: (value) => value >= 0,
  max: {
    type: Number,
    required: true,
    validator: (value) => value >= 0,

In the above example, the min and max props must be positive numbers. In Vue v3.4, the validator function is now called with a second argument containing all the props, allowing to validate the value against other props.

  min: {
    type: Number,
    required: true,
    validator: (value) => value >= 0,
  max: {
    type: Number,
    required: true,
    validator: (value, props) => value >= props.min,

Then if you try to use the component with max being lower than min, you will get a warning.

Invalid prop: custom validator check failed for prop "max".

SSR hydration mismatch warnings

When using SSR, the client-side hydration will now warn you with a message that includes the mismatching element. It was sometimes hard to find the mismatching element in the DOM, as the warning was in v3.3:

[Vue warn]: Hydration completed but contains mismatches.

In v3.4, the warning now contains the actual element (not only its tag name but the actual DOM element, so you can click on it), allowing us to see where it is in the DOM and why it failed.

[Vue warn]: Hydration text content mismatch in <h2>:
- Server rendered: Hello server
- Client rendered: Hello client

Vue now also warns you if you have a mismatch in classes, styles, or attributes!

You can enable this feature in production as well by using a feature flag in your Vite config called __VUE_PROD_HYDRATION_MISMATCH_DETAILS__:

export default defineConfig({
  plugins: [vue()],
  define: {

MathML support

It is now possible to write templates using MathML (in addition to HTML and SVG)!

The template below displays a beautiful :



The defineModel function was introduced as an experimental API in v3.3, is now a stable API. It is now the recommended way to define custom v-models.

Compared to what we explained in our previous blog post, the local option has been removed (see this discussion if you want to know why). The model now automatically adapts based on whether the parent provides a v-model or not.

Another change it that it is now possible to handle modifiers. For example, if you want to handle the .number modifier, you can do:

const [count, countModifiers] = defineModel({
  set(value) {
    if (countModifiers?.number) {
      return Number(value);
    return value;
console.log(countModifiers?.number); // true if the .number modifier is used

You can type the modifiers by using defineModel<number, 'number'>({ ... }). The first type parameter is the type of the model value, and the second one is the type of the modifiers (which can be a union type if you want to handle several ones).

You can play with this demo to see how it works.

TypeScript improvements

An effort has been made to sanitize the types (which will be helpful for all libraries in the ecosystem).

A notable improvement for developers is that app.directive, used to register global directives, can now be properly typed:

app.directive<HTMLElement, string>('custom', {
  mounted(el, binding) {
    // el is correctly typed as HTMLElement
    // binding is correctly typed as string

Deprecated features removed

The reactivity transform experiment has been removed. It had been deprecated in v3.3.0 (see our previous blog post).

Vnode hook events written like vnodeMounted have been deprecated in v3.3 (see our previous blog post) and they are now no longer supported. You should use the @vue: syntax instead, like @vue:mounted.

The v-is directive has also been removed.

News from the ecosystem

Vue 2 End of Life

Vue 2 has reached its end of life, and Evan wrote a blog post about it:

👉 https://blog.vuejs.org/posts/vue-2-eol

Vapor mode

Vapor (@vue/vapor) is making progress with a new repository.

For now, it introduces two work-in-progress packages: a new compiler and a new runtime. They only support the most basic features at the moment, and aren’t easily usable. There is a playground in the repository if you want to try it out.

The biggest difference with Vue 3 is that Vapor generates a rendering function that does not rely on virtual DOM.

For example, the following component:

<script setup lang="ts">
import { ref, computed } from 'vue/vapor'

const count = ref(1)
const double = computed(() => count.value * 2)

const inc = () => count.value++

    <h1 class="red">Counter</h1>
    <div>8 * 2 = </div>
    <button @click="inc">inc</button>

generates the following render function:

function _sfc_render(_ctx) {
  const t0 = _template('<div><h1 class="red">Counter</h1><div> * 2 = </div><button>inc</button></div>');
  const n0 = t0();
  const { 0: [, { 1: [n3], 2: [n4] }] } = _children(n0);
  const n1 = _createTextNode(_ctx.count);
  const n2 = _createTextNode(_ctx.double);
  _prepend(n3, n1);
  _append(n3, n2);
  _on(n4, "click", (...args) => _ctx.inc && _ctx.inc(...args));
  _watchEffect(() => {
    _setText(n1, void 0, _ctx.count);
  _watchEffect(() => {
    _setText(n2, void 0, _ctx.double);
  return n0;

As you can see the render function is using a different strategy than Vue 3: it creates the static elements, then it creates the dynamic elements, and finally it updates the dynamic elements when needed using watchEffect.

You can check in the project’s README the features that are supported and the ones that are not.

Vue Test Utils

VTU should now have better type-checking support for TypeScript users. For example wrapper.setProps({ foo: 'bar' }) will now correctly error if the component has no foo prop.


create-vue now generates projects using Vite v5, which was recently released.


Nuxt v3.9 is out as well, with the support of Vue 3.4. It brings a lot of new features and experiments: you can read more in the official blog post.

That’s all for this release. Stay tuned for the next one!

Our ebook, online training and training) are up-to-date with these changes if you want to learn more!

blog comments powered by Disqus