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
refandshallowRefbelow.
- What happened: reading every order through a deep
refwas slower. Each nested object needed a proxy. shallowRefskipped that, because only.valueitself is watched.
What You Will Learn
- How to measure.
- Stable props, so fewer children re-render.
- Computed stability.
v-onceandv-memo.- Virtual lists for very long lists.
- Shallow reactivity and
markRaw. - Lazy loading and bundle size.
- Checking a production build.
- 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 buildthennpm 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-oncerenders an element once, then skips it forever. Good for truly fixed content.v-memo="[a, b]"skips updating that element unlessaorbchanged.v-memohelps in very longv-forlists 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-scrollerdo 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()anddefineAsyncComponent. - 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 needsalttext (emptyalt=""if it is only decoration). - When content changes without a page load, announce it in an
aria-liveregion. - 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-liveparagraph is read aloud each time its text changes.tabindex="-1"lets code focus the heading.
| Check | Tool |
|---|---|
| Keyboard only | Unplug the mouse: Tab, Shift+Tab, Enter, Space, Esc |
| Automatic rules | Lighthouse or the axe DevTools extension |
| Linting while you type | eslint-plugin-vuejs-accessibility |
| A real screen reader | NVDA (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()anddefineAsyncComponent. - Bundle report =
npm run buildand a visualizer show what is big.
Common mistakes
| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| Optimising without measuring | Every trick helps | Measure, then fix the slow part | Complex code with no gain |
| Measuring the dev build | Dev speed = real speed | Measure build + preview | Wrong conclusions |
| Passing shared ids to every row | Props cost nothing | Pass a computed boolean | Every row re-renders |
v-memo missing a value | It updates when needed | List every changing value | Rows stop updating |
| Rendering 10 000 rows | Vue is fast enough | Use a virtual list or pages | Slow scrolling and memory use |
Deep ref for huge read-only data | Deep is always fine | shallowRef | Slow loads |
| Importing a whole library for one function | Unused code is removed anyway | Import only what you use | A 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-onceandv-memoonly 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