Course outline

Vue / Advanced: architecture, hydration, and production evidence / Lesson 14

Performance, rendering cost, and measured optimization

Locate the user-visible bottleneck before changing reactivity, code loading, or list rendering.

About 4 min readContent reviewed
On this page

Lesson goals

  • Separate startup, update, layout, and data-processing costs.
  • Use stable inputs and bounded rendering without hiding state changes.

Define a reproducible interaction

A slow first page, delayed search result, janky list, and growing memory use need different evidence. Define a representative task, dataset, device class, build mode, and observation method. Development instrumentation can help diagnosis while distorting final timings, so compare equivalent release-like conditions where appropriate. Measure before caching or marking data shallow. A performance improvement must preserve correct state, accessibility, and error behavior. This course does not run a server, browser benchmark, or load test; it explains the measurement design and validates only harmless local computations where available.

Avoid unnecessary source mutation during derivation

An expensive derivation belongs outside repeated ad hoc template expressions, but its algorithm and mutation behavior still matter. The standalone example sorts a copy of synthetic records and verifies the source order remains unchanged. This correctness check is not a benchmark or proof that sorting is the actual bottleneck. In Vue, a computed derivation can cache according to reactive dependencies; it cannot make an inefficient algorithm free. Avoid sorting a shared source array accidentally inside a getter. Review stable ids and actual consumer needs before adding another layer of cached state.

javascript A nonmutating synthetic sort contract
const notes = [{ id: "n2", title: "Zebra" }, { id: "n1", title: "Alpha" }]
const sorted = [...notes].sort((a, b) => a.title.localeCompare(b.title))
if (sorted[0].id !== "n1" || notes[0].id !== "n2") {
  throw new Error("SORT_OWNERSHIP_MISMATCH")
}

Runs as standalone JavaScript under Node. The sorted view begins with n1 while the source still begins with n2. Locale-specific ordering needs its own product policy; this example proves ownership behavior for the supplied simple data.

Reduce work at the boundary that actually changes

Stable props can prevent unnecessary child updates when only one item’s active state changes. Keep the value passed to each child as specific as the child needs instead of making every child depend on a broad changing object. Stable keys preserve identity but do not automatically make every render cheap. Large lists may need virtualization so only relevant items are materialized, with accessibility and focus behavior checked. v-once and v-memo are specialized controls whose dependency assumptions can create stale content if used incorrectly. Adopt them only after measurement and explicit correctness review.

Load a heavy optional component on demand

Code splitting can reduce initial work when an optional feature is not needed immediately. defineAsyncComponent wraps a component loader, and a bundler can turn the dynamic import into a separate chunk. The example renders the optional panel only after a user action. This does not automatically reduce server data cost or make a slow component fast once loaded. Provide meaningful loading and error behavior in the real feature. Router route-component lazy loading has its own documented pattern and should not be confused with wrapping every route in defineAsyncComponent. Keep chunk and network failure scenarios in the user contract.

vue An optional asynchronously loaded SFC
<script setup>
import { ref, defineAsyncComponent } from "vue"
const showDetails = ref(false)
const NoteAnalytics = defineAsyncComponent(() => import("./NoteAnalytics.vue"))
</script>

<template>
  <button type="button" @click="showDetails = true">Show note details</button>
  <NoteAnalytics v-if="showDetails" />
</template>

Requires the app-defined NoteAnalytics.vue and a compatible bundler. The conditional controls first rendering and loader demand. No build, chunk download, or browser performance measurement was executed for this fragment.

Keep optimization promises truthful

shallowRef can reduce deep conversion overhead for large immutable structures or external systems, but nested changes do not trigger the same behavior as ordinary deep state. Replace the outer value according to the documented contract or use the appropriate explicit trigger only when justified. markRaw and other escape hatches affect proxy identity and should not become blanket fixes. Profile image decoding, layout, network, and JavaScript separately because low reactive-update counts can coexist with slow frames. Compare the original task after each focused change and retain bounded evidence. A faster result that loses updates or keyboard access is not an acceptable optimization.

Common errors and fixes

A computed sort changes the shared list

Why it happens
A mutating array operation is used during derivation.
How to fix it
Derive from a copy or a supported nonmutating method.
Prevent it next time
Test source identity and ordering contracts.

A memoized row displays stale content

Why it happens
The dependency list omits a value that affects rendering.
How to fix it
Restore complete dependencies or remove the premature optimization.
Prevent it next time
Pair performance work with correctness cases.

A shallow structure is mutated deeply with no update

Why it happens
Deep-reactivity behavior was assumed for a shallow API.
How to fix it
Follow the immutable replacement or explicit trigger contract.
Prevent it next time
Document shallow state to every consumer.

Check your understanding

1. A list remains slow even with few component updates. What should be investigated?

Show worked solution

Inspect layout, drawing, image decoding, JavaScript work, memory pressure, and data loading under a representative trace. Update counts are one signal, not the complete frame cost. Optimize the measured dominant phase and recheck the same task.

2. Should every route component be wrapped in defineAsyncComponent?

Show worked solution

No. Vue Router documents route lazy loading through dynamic component imports with its own integration. Use the API suited to the boundary and verify loading, errors, navigation, and bundle behavior instead of applying one wrapper universally.

Key takeaways

  • Measure the user task before choosing an optimization.
  • Stable inputs and bounded rendering need truthful state contracts.
  • Code splitting, reactivity, and browser rendering solve different costs.

Official references