The DevTools in Anger

Inspect a component's props, refs and computeds, edit state live, and read the timeline to find which update fired.

Syntaximport vueDevTools from 'vite-plugin-vue-devtools'

Vue DevTools is a browser panel that shows a running app as a tree of components rather than a tree of DOM nodes. You select a component and see the values it is holding: its props, its refs, its computed results, and anything handed down to it.

Getting it

There are two routes. The browser extension works on any Vue 3 page, including one you did not build. The Vite plugin, vite-plugin-vue-devtools, adds a panel to your own dev server with extra tabs, including an inspector that opens the matching file in your editor. Plugin version 9 requires Vite 8.3 or newer.

Reading a component

State is grouped by where it came from. Props are the values the parent passed. Setup holds the refs and computeds declared in <script setup>. Provided and injected show values travelling down the tree. Most of these are editable: type a new number into a ref and the component re-renders at once, which beats editing code to see a layout at an awkward value.

The timeline

The timeline tab records mounts, updates and emitted events in order. When something updated twice, or updated when nothing should have, the timeline names the component and the event. Setting app.config.performance = true adds Vue's own init, render and patch marks to the browser's performance panel.

What trips people up

A computed appears as a value with no dependency list, so DevTools cannot tell you why it recomputed. Edits you make in the panel are real writes, not overrides: the next render that sets the same value wins, and a hot reload discards them.

Example

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

const props = defineProps<{
  unitPrice: number
  quantity: number
}>()

const discountPercent = ref(0)

const subtotal = computed(() => props.unitPrice * props.quantity)

const total = computed(() =>
  Math.round(subtotal.value * (1 - discountPercent.value / 100))
)

function applyStaffDiscount(): void {
  discountPercent.value = 20
}
</script>

<template>
  <div>
    <p>Subtotal: {{ subtotal }}</p>
    <p>Discount: {{ discountPercent }}%</p>
    <p>Total: {{ total }}</p>
    <button type="button" @click="applyStaffDiscount">Staff discount</button>
  </div>
</template>

<style scoped>
div {
  display: grid;
  gap: 0.25rem;
}
</style>

When to use it

  • Check why a child is rendering with a stale prop by reading the value the parent actually passed.
  • Push a ref to an extreme value in the panel to see how a layout copes with a long string or a zero count.
  • Use the timeline to find which emitted event caused a component to update twice on one click.

More examples

Adding the DevTools Vite plugin

The plugin only runs on the dev server, so it adds nothing to a production bundle. Reach for it over the bare extension when you want the component inspector and open-in-editor on your own project; the extension is still what you use on a deployed page.

Example · ts
// vite.config.ts
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'

export default defineConfig({
  plugins: [vue(), vueDevTools()],
  resolve: {
    alias: {
      '@': fileURLToPath(new URL('./src', import.meta.url))
    }
  }
})

Turning on Vue's performance marks

app.config.performance is false by default and works in development only. With it on, component init, compile, render and patch appear as marks in the browser performance timeline, which is how you find out whether a slow interaction was Vue rendering or your own code. The import.meta.env.DEV guard keeps it out of the built app.

Example · ts
// src/main.ts
import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App)

if (import.meta.env.DEV) {
  app.config.performance = true
}

app.mount('#app')

Naming a component for the tree

DevTools labels a component from its filename, so a tree full of index.vue or Item.vue entries is hard to read. defineOptions (available since Vue 3.3) sets the name explicitly without leaving script setup. The name is also what appears in warning traces.

Example · vue
<script setup lang="ts">
import { ref } from 'vue'

defineOptions({ name: 'CartTotal' })

const note = ref('')
</script>

<template>
  <label>
    Gift note
    <input v-model="note" type="text" />
  </label>
</template>

<style scoped>
label {
  display: block;
}
</style>

Discussion

  • Be the first to comment on this lesson.