Performance

By Syncfusion®, Inc. · Last updated

Measuring before optimising, stable props, computed stability, v-once and v-memo, virtual lists for huge data, shallow reactivity and markRaw, lazy loading and bundle size, tree shaking, and checking a production build with DevTools, Lighthouse and a bundle report, and accessibility: keyboard, labels, live regions and focus.

Fast Enough for Everyone

  • Vue is fast by default. Most apps never need special tricks.
  • Slow pages usually come from a few causes: huge lists, too many re-renders, or a big first download.
  • Many visitors use cheap phones and slow networks. Test for them, not only for your laptop.
  • Measure first. Change code only where the measurement says it is slow.

Simple lesson: measure, find the slow part, fix that part only.

A Real-World Example

  • Meera's order history has 20 000 orders.
  • Making all of them deeply reactive takes time on every load.
  • The list is only replaced as a whole, so deep tracking is wasted work.
  • Compare ref and shallowRef below.
  • What happened: reading every order through a deep ref was slower. Each nested object needed a proxy.
  • shallowRef skipped that, because only .value itself is watched.

What You Will Learn

  1. How to measure.
  2. Stable props, so fewer children re-render.
  3. Computed stability.
  4. v-once and v-memo.
  5. Virtual lists for very long lists.
  6. Shallow reactivity and markRaw.
  7. Lazy loading and bundle size.
  8. Checking a production build.
  9. Keyboard, labels, focus and screen readers.

Measure First

  • Vue DevTools has a Performance and a Timeline tab. They show which components render and how long they take.
  • The browser's Performance panel records everything the page does.
  • Lighthouse (in Chrome DevTools) scores load speed on a slow phone and suggests fixes.
  • performance.now() around a piece of code gives a quick timing, as in the first box.
  • Always measure a production build (npm run build then npm run preview). Development builds are slower on purpose.

Never judge speed in development mode. Dev builds include warnings and extra checks, so they can be several times slower than the real thing.

Fewer Re-Renders

Stable Props

  • A child re-renders when its props change.
  • Pass each child exactly what it needs. Prefer :active="item.id === activeId" over :active-id="activeId".
  • With activeId, every child gets a new prop on each change, so every child re-renders.
  • With a boolean, only the two children whose value really changed re-render.

Animation: when the active item changes, passing activeId makes all five rows flash as re-rendered, while passing an active boolean makes only the old and new active rows flash

:active-id="activeId" :active="id === activeId"

5 rows re-render 2 rows re-render

Pass each child only what it needs, so only the changed children render again.

Computed Stability

  • Since Vue 3.4, a computed value only triggers its readers when its result really changes.
  • isBusy = computed(() => orders.value > 5) triggers only when it flips between true and false.
  • If a computed returns a new object every time, it always looks changed. Return the old object when nothing changed.

v-once and v-memo

  • v-once renders an element once, then skips it forever. Good for truly fixed content.
  • v-memo="[a, b]" skips updating that element unless a or b changed.
  • v-memo helps in very long v-for lists where only a few rows change. Use it rarely, and only after measuring.

v-memo must list every value the row shows that can change. If you leave one out, that part of the row stops updating, which is a hard bug to spot.

Simple lesson: stable props, stable computed values, and v-memo only where measured.

Very Long Lists

  • Rendering 10 000 rows creates 10 000 elements. That is slow, even if Vue is fast.
  • A virtual list renders only the rows you can see, plus a few extra.
  • As you scroll, it swaps which rows are rendered. A tall spacer keeps the scrollbar right.
  • Libraries such as vue-virtual-scroller do this well. Here is the idea in a few lines.

Shallow Reactivity and markRaw

  • Big data that you only replace should live in shallowRef, as in the first box.
  • Big objects from other libraries (a map, a chart) should be wrapped in markRaw().
  • Both come from Advanced Reactivity.

Simple lesson: render only what is visible, and do not make big data deeply reactive.

A Smaller First Download

  • The first download is often the biggest delay on slow networks.
  • Tree shaking: the build removes code you never import. Import only what you use.
  • Lazy loading: split rarely used pages and heavy components with () => import() and defineAsyncComponent.
  • Check what is in your bundle before guessing.

Checking a Production Build

npm run build      # makes dist/, prints the size of each file
npm run preview    # serves dist/ like a real host
npx vite-bundle-visualizer   # opens a map of what fills your bundle
  • Look for unexpectedly big libraries, and for code that every page loads but few use.
  • Run Lighthouse on the preview, with a slow phone and network.

These commands run in a real project on your computer. The lab cannot build or measure network downloads, because every file is already in the preview.

Simple lesson: import less, lazy-load the rest, and read your bundle report.

Usable by Everyone

  • A fast app must also work with a keyboard and a screen reader (software that reads the page aloud).
  • Use real elements: <button> for actions, <a>/<RouterLink> for navigation. A clickable <div> cannot be reached with Tab.
  • Every input needs a <label>. Every image needs alt text (empty alt="" if it is only decoration).
  • When content changes without a page load, announce it in an aria-live region.
  • After a route change in an SPA, move focus to the new page's heading, so screen readers start there.
  • Keep focus visible, and respect prefers-reduced-motion (see Transitions and Animations).
  • Press Tab in the preview: it stops on the input and the buttons, but never on the dashed "div button".
  • The aria-live paragraph is read aloud each time its text changes. tabindex="-1" lets code focus the heading.
CheckTool
Keyboard onlyUnplug the mouse: Tab, Shift+Tab, Enter, Space, Esc
Automatic rulesLighthouse or the axe DevTools extension
Linting while you typeeslint-plugin-vuejs-accessibility
A real screen readerNVDA (Windows), VoiceOver (Mac, iPhone), TalkBack (Android)

Simple lesson: real buttons and labels, visible focus, and announce changes that happen without a reload.

TL;DR

  • Measure first = DevTools, Lighthouse, performance.now(), on a production build.
  • Stable props = pass each child only the value it needs, such as a boolean.
  • Computed stability = computed triggers only when its result changes (3.4+).
  • v-once = render once, skip forever.
  • v-memo="[deps]" = skip updates unless the listed values change.
  • Virtual list = render only the visible rows of a huge list.
  • shallowRef / markRaw = skip deep tracking for big or foreign data.
  • Lazy loading = () => import() and defineAsyncComponent.
  • Bundle report = npm run build and a visualizer show what is big.

Common mistakes

MistakeWrong ideaCorrect ideaWhat goes wrong
Optimising without measuringEvery trick helpsMeasure, then fix the slow partComplex code with no gain
Measuring the dev buildDev speed = real speedMeasure build + previewWrong conclusions
Passing shared ids to every rowProps cost nothingPass a computed booleanEvery row re-renders
v-memo missing a valueIt updates when neededList every changing valueRows stop updating
Rendering 10 000 rowsVue is fast enoughUse a virtual list or pagesSlow scrolling and memory use
Deep ref for huge read-only dataDeep is always fineshallowRefSlow loads
Importing a whole library for one functionUnused code is removed anywayImport only what you useA bigger bundle

Check Yourself

Exercises

What to Remember

  • Measure a production build before you optimise.
  • Pass stable, minimal props so fewer children re-render.
  • Use v-once and v-memo only where measurement shows a need.
  • Render huge lists with a virtual list. Keep big data in shallowRef.
  • Lazy-load heavy parts and read your bundle report.

Next: Errors, Security and Deployment