| |

Vue.js 81 🟢 Performance Profiling with Vue DevTools (Timeline, Component Render Metrics)

⏱️ Estimated read: 12 min | |

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

ViewWhat It Shows
TimelineEvents over time, including interactions
Performance / Flame ChartHierarchical render durations
Commit GraphRenders grouped by DOM flush
Components → DependenciesReactive fields tracked by a component

Reading the Metrics

SignalInterpretation
Wide barSlow single render
Many barsFrequent re-renders
Large commit blockOver-rendering across components
Wide bar + many barsHighest-priority target
Component renders but dependencies unchangedParent rendered; needs memoization or state colocation

Key DevTools Settings

SettingPurpose
Record component render timingMust be enabled for profiling
Highlight updatesReal-time flash on render
Reactivity DependenciesShows 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

PitfallWhy It HappensFix
Profiler shows no dataRecording not enabledClick record button; check settings
Component re-renders but dependencies unchangedParent re-renderedMemoize child or colocate state
Slow interaction not visible in Vue profilerBottleneck is layout or networkUse Chrome DevTools Performance
Profiler overhead distorts resultsRecording enabled during measurementAccept the overhead for relative comparisons
Cannot correlate render with actionInteraction events not recordedRecord 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

ItemValue
Profiler locationVue DevTools → Timeline / Performance tab
Recording defaultOff; must be enabled
Timeline bar widthDuration of render
Timeline bar repetitionFrequency of re-render
Commit graphRenders grouped by DOM flush
Over-rendering signalLarge commit block on small interaction
Reactivity DependenciesSubscribed fields for selected component
Highlight updatesReal-time render flash indicator
Profiler limitationDoes 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.

Was this helpful?


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!