Vue docs, chapter by chapter

Best practices — deployment, performance, accessibility, security

The four Best Practices chapters as checklists — production builds and error tracking, the guide's page-load and update-performance techniques, its accessibility guidance, and the security rules Vue enforces and the ones it cannot.

These four chapters are short and dense, and they are quoted almost verbatim in interviews: "what does Vue escape for you", "how do you optimise a large list", "what is props stability". Learn them as lists.

Know — Production Deployment

  • Development vs production: the dev build includes warnings and devtools hooks and skips optimisations; production builds strip warnings and enable them. Without a build tool, load the .prod.js files; with Vite the build command and import.meta.env.MODE handle it; with webpack set NODE_ENV=production.
  • Runtime error tracking: app.config.errorHandler receives component errors; Sentry and Bugsnag offer official Vue integrations.

Know — Performance

  • Two categories: page load (FCP, LCP, INP) and update performance.
  • Profiling: PageSpeed Insights and WebPageTest for load; Chrome DevTools Performance and the Vue DevTools performance panel (enable app.config.performance = true, which uses performance.mark) for updates.
  • Page load: choose the right architecture (SSR or SSG for content sites), keep the bundle small (a build step for tree-shaking, import only what is used, audit dependencies), code-split with dynamic import(), defineAsyncComponent, and route-level splitting.
  • Update optimisations: props stability (pass a computed boolean active to each list item rather than the selected id, so only two items re-render), v-once for never-changing content, v-memo to skip a subtree unless listed values changed, and computed stability (since 3.4 a computed only triggers when its value changes, so return primitives or stable references).
  • General: virtualise large lists (vue-virtual-scroller, vue-virtual-scroll-grid, vueuc/VVirtualList), reduce reactivity overhead for large immutable data with shallowRef/shallowReactive, and avoid unnecessary component abstractions (renderless wrappers and higher-order components cost an instance each, which hurts inside big lists).

Know — Accessibility

  • Skip link as the first element in App.vue, targeting #main (tabindex="-1"), focused on route change.
  • Content structure: headings in order with no skipped levels and one per section; landmarks (header/banner, nav, main, footer/contentinfo, aside/complementary, form, search, role="region") with role attributes for older assistive technology.
  • Semantic forms: every input has a <label for> or aria-label/aria-labelledby; aria-describedby for instructions; a placeholder is not a label; hide content with a .visually-hidden class rather than display: none when screen readers should still read it; aria-hidden="true" for decorative content; <button type="button"> unless it submits; functional images get meaningful alt.
  • Standards: WCAG 2.1 principles (perceivable, operable, understandable, robust) and WAI-ARIA; tools: NVDA, VoiceOver, JAWS, axe, WAVE, Lighthouse, and eslint-plugin-vuejs-accessibility.

Know — Security

  • Report vulnerabilities to security@vuejs.org. Rule number one: never use non-trusted templates; compiling a user-provided template string is arbitrary code execution.
  • What Vue does: {{ }} interpolation is HTML-escaped; attribute bindings are escaped too (including quotes), so :title="userInput" is safe.
  • Potential dangers: HTML injection through v-html or <component :is> with user templates (sanitise, or avoid); URL injection via :href="userUrl" with javascript: schemes (sanitise on the backend or with a library such as sanitize-url); style injection via :style="userStyles" enabling clickjacking with position: fixed (bind only specific properties through an object); JavaScript injection via @click="userProvided" or :onclick (never bind user strings as handlers). SSR adds the risk of leaking server data into the rendered HTML.
  • Best practices the guide points to: HTML sanitisation libraries, backend coordination, and general web security resources (CSP, security headers).

How it works

Props stability and v-memo, the two update-performance techniques people misremember:

app/components/Docs/List.vue
<script setup lang="ts">
const items = ref<{ id: number, name: string }[]>([])
const activeId = ref<number>()
</script>

<template>
  <!-- ✗ every item re-renders when activeId changes: each receives a new activeId prop -->
  <!-- <ListItem v-for="item in items" :key="item.id" :item="item" :active-id="activeId" /> -->

  <!-- ✓ props stability: only the two affected items get a changed `active` boolean -->
  <ListItem v-for="item in items" :key="item.id" :item="item" :active="item.id === activeId" />

  <!-- v-memo: skip the subtree unless the listed values changed -->
  <div v-for="item in items" :key="item.id" v-memo="[item.id === activeId]">
    <p>{{ item.name }}</p>
  </div>
</template>

Style injection, contained:

<!-- ✗ user controls position/z-index: can overlay a login button (clickjacking) -->
<!-- <a :style="userProvidedStyles"> -->
<!-- ✓ only the properties you allow -->
<a :style="{ color: userProvidedColor, background: userProvidedBackground }">
Gotcha· Escaping does not cover URLs and HTML

Vue escapes text and attribute values, so <a :href="url"> renders the string safely, but a javascript: URL is still a valid string and will execute when clicked. Escaping is not sanitisation; URLs and any v-html content need their own validation.

Exercise

Exercise
  • Build a 1,000-item list with the activeId prop, count renders with onRenderTriggered, then switch to the active boolean and count again.
  • Add a skip link and landmarks to a page and navigate it with VoiceOver or NVDA using landmark navigation.
  • Bind :href to javascript:alert(1) and click it; then run it through sanitize-url.

Be able to say

Be able to say· What does Vue protect you from, and what is still your job?

"Vue escapes what it renders: text interpolation is HTML-escaped and attribute bindings are escaped including quotes, so injecting markup through data does not work. It cannot protect against things that are valid as data but dangerous as behaviour: v-html with user content, a javascript: URL in :href, a user-controlled :style object that positions an element over a button, or binding user strings as event handlers. And the first rule is never to compile a user-provided template. Those need sanitisation, allow-lists and, in SSR, care about what server data ends up in the HTML."

Your notes

Open in Notes

Nothing yet. Notes filed here show up on this page and the section overview.