Vue.js 81 🟢 Performance Profiling with Vue DevTools (Timeline, Component Render Metrics)
Vue DevTools includes a performance profiling feature that records component render activity and displays it on a timeline. The tool exists to replace guesswork with measurement: instead of assuming a component is slow, you record an interaction, inspect the flame chart, and see exactly which components rendered, how long each render took, and whether a render produced any visible change . The profiler is not enabled by default because recording adds overhead, particularly in large applications, so it must be turned on deliberately when a performance question arises .
This chapter covers the two views that Vue DevTools provides for performance work—the Timeline and the component render metrics—and the workflow that connects them to a diagnosis. It examines how to start and stop a recording, how to read the flame chart and commit graph, how to identify wasted renders, and how to use the Components panel to trace a slow render back to its reactive dependencies.
Key point: The Vue DevTools profiler answers “which component rendered and for how long.” It does not answer “why did the browser take so long to paint.” A slow layout or a long-running network request will not appear as a wide component bar. The profiler is the first step, not the only step.
Why the Vue DevTools profiler exists
The component-level blind spot. Chrome DevTools Performance panel shows JavaScript execution, style recalculation, and layout. It does not know what a Vue component is. A long patch function in the flame chart might correspond to a single component re-rendering or to a hundred small components updating. The Vue DevTools profiler bridges this gap by recording events that are tagged with component names, UIDs, and lifecycle phases . It translates the browser’s generic JavaScript timeline into a Vue-specific view.
The wasted render problem. A component that re-renders without changing its output is wasted work. The profiler makes this visible: a component that appears repeatedly on the timeline during an interaction that should not affect it is a candidate for optimization. The common cause is an unstable prop—an object or function created fresh on every parent render—which defeats the child’s memoization . The profiler shows the symptom; the Components panel shows the prop.
The reactivity scope problem. A component that depends on a large reactive object will re-render whenever any property of that object changes, even if the component only uses one property. The Components panel’s “Reactivity Dependencies” view lists the reactive fields that the selected component tracks . If the list contains dozens of fields for a component that displays one value, the dependency scope is too wide, and the fix is to narrow what the component reads.
a. Recording and Reading the Timeline
The Timeline tab in Vue DevTools is where a recording is started and stopped. The recording captures component events—mount, update, unmount—along with mouse and keyboard interactions that occurred during the recording window . The interaction events are essential for correlation: they mark when the user did something, which allows the component renders that follow to be attributed to that action.
Timeline recording workflow:
1. Open Vue DevTools → Timeline tab
2. Click the record button (may need to enable recording first)
3. Perform the interaction to measure (click, type, scroll)
4. Stop the recording
5. Inspect the events on the timeline
Each event on the timeline is a horizontal bar. The width of the bar represents the duration of the event, and hovering over it reveals the component name, UID, event type, and duration in milliseconds . A bar that is both wide and present in many successive rows indicates a component that is both slow and frequently re-rendering—the highest-priority optimization target.
The timeline also includes a “Routes” view, which records navigation events separately from component renders. This is useful for measuring route transition time independently of the components that render on the destination route .
b. The Flame Chart and Commit Graph
The Performance tab (or the flame chart view within the Timeline) organizes the recorded events hierarchically. Parent components appear above their children, and the horizontal width of each bar represents the time spent rendering that component and its subtree . A wide bar near the top of the chart indicates that the entire subtree beneath it is slow; a wide bar near the bottom indicates a leaf component that is doing expensive work itself.
The commit graph is a separate view that groups renders by commit. A commit is one flush of updates to the DOM. The graph shows each commit as a block, with the number of components that rendered in that commit . A commit that touches far more components than expected—for example, a keystroke in a search input that causes the entire product list to re-render—is the signature of a reactivity scope problem or an unstable prop.
Commit graph interpretation:
- Each block = one DOM flush
- Width/height = number of components rendered
- Unexpectedly large block = over-rendering
- Compare with the interaction that triggered it
The flame chart and the commit graph answer different questions. The flame chart answers “which component is slow?” The commit graph answers “how many components are rendering when they should not?” A performance investigation often starts with the commit graph, because over-rendering is more common than a single slow component, and it is easier to fix.
c. From Render Metrics to Reactive Dependencies
The Components panel is where the diagnosis moves from “this component rendered” to “why did it render.” Selecting a component in the panel shows its props, state, and hooks. The “Reactivity Dependencies” section lists the reactive fields that the component tracks . If a component re-renders on every keystroke in an unrelated input, checking its dependencies reveals whether it is subscribed to a store field that the input updates, or whether a prop is being recreated by the parent.
The fix depends on what the dependencies show. If the component depends on an entire store object when it only uses one field, the fix is to use a more specific selector or to move the store read closer to the component that needs it. If the component receives a prop that is a new object on every parent render, the fix is to hoist the object to a constant or wrap it in a computed so the reference is stable . The profiler showed the symptom; the dependency view shows the source.
A related tool is the “Highlight updates” setting in the Components panel. When enabled, components flash a colored border whenever they render. A component that flashes during interactions that should not affect it is a candidate for further investigation . This is faster than recording a full profile and is useful for a first pass before committing to the overhead of recording.
Complete Example Session
// ============================================
// PART 1: THE COMPONENT UNDER INVESTIGATION
// ============================================
// A search input and a results list
// Typing in the input should not re-render the list
<script setup lang="ts">
import { ref } from 'vue'
const query = ref('')
const results = ref<string[]>([])
function handleSearch(q: string) {
query.value = q
// fetch results...
}
</script>
<template>
<div>
<SearchInput :value="query" @update="handleSearch" />
<ResultsList :results="results" />
</div>
</template>
// ============================================
// PART 2: ENABLE THE PROFILER
// ============================================
// Vue DevTools → Timeline tab
// Click the record button (disabled by default)
// Recording adds overhead; enable only when needed
// ============================================
// PART 3: RECORD THE INTERACTION
// ============================================
// Start recording
// Type "hello" in the search input
// Stop recording
// The timeline shows SearchInput updates and,
// unexpectedly, ResultsList updates
// ============================================
// PART 4: READ THE FLAME CHART
// ============================================
// SearchInput events are narrow (fast)
// ResultsList events are also narrow but numerous
// The problem is frequency, not duration
// Hover over a ResultsList bar to confirm the component name
// ============================================
// PART 5: CHECK THE COMMIT GRAPH
// ============================================
// Each keystroke produces a commit block
// Each block includes ResultsList
// ResultsList renders on every keystroke
// even though results did not change
// ============================================
// PART 6: INSPECT REACTIVITY DEPENDENCIES
// ============================================
// Switch to Components panel
// Select ResultsList
// Check "Reactivity Dependencies"
// The list shows only "results" — not "query"
// So why is it re-rendering?
// ============================================
// PART 7: IDENTIFY THE CAUSE
// ============================================
// The parent App component re-renders when query changes
// ResultsList is a child of App
// ResultsList re-renders because its PARENT rendered,
// not because its own dependencies changed
// The prop "results" is stable, but the child is not memoized
// ============================================
// PART 8: APPLY THE FIX
// ============================================
// Wrap ResultsList in a memoization boundary
// In Vue, this is done with v-memo or by making it a separate
// component that receives stable props
// Alternatively, move the query state into SearchInput
// so App does not re-render on keystrokes
// ============================================
// PART 9: VERIFY WITH A SECOND RECORDING
// ============================================
// Record the same interaction again
// ResultsList should now be absent or gray
// during keystroke commits
// ============================================
// PART 10: CHECK WITH CHROME DEVTOOLS
// ============================================
// If the Vue profiler shows no slow component
// but the interaction still feels slow,
// switch to Chrome DevTools Performance panel
// Look for layout, paint, or long JS tasks outside Vue
The ten parts trace the investigation from the initial recording through the diagnosis and verification. The critical step is Part 6: the Reactivity Dependencies view shows that ResultsList does not depend on query, which points to the parent-render cause in Part 7.
Quick Reference
Profiler Entry Points
| View | What It Shows |
|---|---|
| Timeline | Events over time, including interactions |
| Performance / Flame Chart | Hierarchical render durations |
| Commit Graph | Renders grouped by DOM flush |
| Components → Dependencies | Reactive fields tracked by a component |
Reading the Metrics
| Signal | Interpretation |
|---|---|
| Wide bar | Slow single render |
| Many bars | Frequent re-renders |
| Large commit block | Over-rendering across components |
| Wide bar + many bars | Highest-priority target |
| Component renders but dependencies unchanged | Parent rendered; needs memoization or state colocation |
Key DevTools Settings
| Setting | Purpose |
|---|---|
| Record component render timing | Must be enabled for profiling |
| Highlight updates | Real-time flash on render |
| Reactivity Dependencies | Shows subscribed fields |
Best Practices
✅ Do This:
-- Enable recording only when investigating a specific interaction
-- Overhead is significant in large apps -- ✅
-- Record a clean baseline before changing code
-- Compare the second recording after the fix -- ✅
-- Check Reactivity Dependencies when a component re-renders unexpectedly
-- The dependency list reveals whether the cause is internal or parent-driven -- ✅
-- Use Highlight updates for a quick first pass
-- Record a full profile only when the problem is confirmed -- ✅
❌ Don’t Do This:
-- Leave the profiler recording during normal development
-- It adds overhead and distorts timings -- ❌
-- Assume a slow interaction is a Vue rendering problem
-- Check Chrome DevTools for layout, paint, and network first -- ❌
-- Optimize a component that renders once but takes 200ms
-- The problem is the component's work, not its frequency -- ❌
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Profiler shows no data | Recording not enabled | Click record button; check settings |
| Component re-renders but dependencies unchanged | Parent re-rendered | Memoize child or colocate state |
| Slow interaction not visible in Vue profiler | Bottleneck is layout or network | Use Chrome DevTools Performance |
| Profiler overhead distorts results | Recording enabled during measurement | Accept the overhead for relative comparisons |
| Cannot correlate render with action | Interaction events not recorded | Record the full interaction, not just the render |
Real-World Examples
1. Start Recording
Vue DevTools → Timeline → Record
2. Record a Keystroke Interaction
Record → type in input → Stop
3. Read a Wide Bar
Hover over bar → component name, duration
4. Check Commit Graph
Count components per commit block
5. Inspect Dependencies
Components → select component → Reactivity Dependencies
6. Enable Highlight Updates
DevTools settings → Highlight updates
7. Compare Before and After
Record baseline → fix → record again → compare
8. Use Chrome DevTools for Layout
Performance panel → look for Layout/Paint tasks
9. Check Route Timing
Timeline → Routes view → record navigation
10. Verify No Wasted Render
Component absent from commit graph after fix
Visual
The Timeline Recording
┌──────────────────────────────────────────────────────────────┐
│ TIMELINE │
│ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Recorded window ││
│ │ ││
│ │ Interaction: click / type / scroll ││
│ │ │ ││
│ │ ▼ ││
│ │ ┌─────────────────────────────────────────────────────┐││
│ │ │ SearchInput ██ │││
│ │ │ ResultsList ██████████████████████████████████████ │││
│ │ │ ██████████████████████████████████████ │││
│ │ │ ██████████████████████████████████████ │││
│ │ └─────────────────────────────────────────────────────┘││
│ │ ││
│ │ Width = duration. Repetition = frequency. ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ Hover for component name, UID, duration. │
│ │
└──────────────────────────────────────────────────────────────┘
Flame Chart Hierarchy
┌──────────────────────────────────────────────────────────────┐
│ FLAME CHART │
│ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ App (15ms) ██████████████████████████████████████████████││
│ │ ├── Header (2ms) ███ ││
│ │ ├── SearchInput (1ms) █ ││
│ │ └── ResultsList (12ms) ████████████████████████████████ ││
│ │ ├── ResultItem (4ms) ████████████ ││
│ │ ├── ResultItem (4ms) ████████████ ││
│ │ └── ResultItem (4ms) ████████████ ││
│ └─────────────────────────────────────────────────────────┘│
│ │
│ Wide bar near top = entire subtree is slow. │
│ Wide bar near bottom = leaf component is slow. │
│ │
└──────────────────────────────────────────────────────────────┘
Commit Graph Over-Rendering
┌──────────────────────────────────────────────────────────────┐
│ COMMIT GRAPH │
│ │
│ Keystroke 1: ┌─────────────────────────────┐ │
│ │ App, SearchInput, ResultsList│ │
│ └─────────────────────────────┘ │
│ │
│ Keystroke 2: ┌─────────────────────────────┐ │
│ │ App, SearchInput, ResultsList│ │
│ └─────────────────────────────┘ │
│ │
│ Keystroke 3: ┌─────────────────────────────┐ │
│ │ App, SearchInput, ResultsList│ │
│ └─────────────────────────────┘ │
│ │
│ ResultsList renders on every keystroke │
│ even though results did not change. │
│ This is the over-rendering signature. │
│ │
└──────────────────────────────────────────────────────────────┘
Dependency Inspection
┌──────────────────────────────────────────────────────────────┐
│ COMPONENTS PANEL → ResultsList │
│ │
│ ┌─────────────────────────────────────────────────────────┐│
│ │ Props: ││
│ │ results: [...] ││
│ │ ││
│ │ Reactivity Dependencies: ││
│ │ results (ref) ││
│ │ ││
│ │ Note: query is NOT in the dependency list. ││
│ │ ResultsList re-renders because App re-renders, ││
│ │ not because ResultsList depends on query. ││
│ │ ││
│ │ Fix: memoize ResultsList or move query state down. ││
│ └─────────────────────────────────────────────────────────┘│
│ │
└──────────────────────────────────────────────────────────────┘
Summary
| Item | Value |
|---|---|
| Profiler location | Vue DevTools → Timeline / Performance tab |
| Recording default | Off; must be enabled |
| Timeline bar width | Duration of render |
| Timeline bar repetition | Frequency of re-render |
| Commit graph | Renders grouped by DOM flush |
| Over-rendering signal | Large commit block on small interaction |
| Reactivity Dependencies | Subscribed fields for selected component |
| Highlight updates | Real-time render flash indicator |
| Profiler limitation | Does not show layout, paint, or network |
Key takeaways:
- The Vue DevTools profiler measures component render activity, not browser performance. It answers which component rendered, how long it took, and how often it rendered. It does not show style recalculation, layout, paint, or network time. A slow interaction that shows no slow component in the Vue profiler is a browser-level or network-level problem, and Chrome DevTools is the next tool .
- The commit graph reveals over-rendering before the flame chart reveals slow components. A small interaction that produces a large commit block—many components rendering for one keystroke—is the signature of a reactivity scope problem. The flame chart may show that each render is fast, but the frequency is the problem .
- Reactivity Dependencies distinguishes parent-driven from self-driven renders. If a component re-renders and its dependency list does not include the value that changed, the cause is a parent render. The fix is memoization at the child or state colocation at the parent .
- Recording adds overhead and must be enabled deliberately. The profiler is not a background monitor. It is a measurement instrument that changes the system it measures. Record a baseline, apply a fix, record again, and compare .
- Highlight updates is the fastest first pass. Before recording a full profile, enable the highlight setting and interact with the app. A component that flashes when it should be static is the candidate for investigation .
Remember: The Vue DevTools profiler is a component-level instrument in a browser-level system. It tells you what Vue did, not what the browser did after Vue finished. The workflow is: record the interaction, read the commit graph for over-rendering, read the flame chart for slow components, and check Reactivity Dependencies for the cause. If the profile looks clean but the interaction still feels slow, the bottleneck is outside Vue—in layout, paint, or network—and Chrome DevTools is the correct next step. The profiler is the first question, not the final answer.
Stop using slow, ad-bloated tool sites! 🤮
🔎 Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)
⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free
🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!