Chapter 81: Vue v-once Directive
V-once
This directive is not for everyday use like v-if, v-for, or v-model. It is a very targeted performance & optimization tool that says:
“Render this element (and everything inside it) only once, when it first appears. After that, never update it again — no matter how many times the component re-renders or how much the reactive data changes.”
It is exactly like telling Vue:
“This part is static forever — freeze it after first render. Don’t waste time re-evaluating or diffing it ever again.”
1. The Core Idea – When & Why You Use v-once
You use v-once in these very specific situations:
- Content that never changes during the lifetime of the component (prices displayed in ₹, copyright year, static labels, version number…)
- Parts of the UI that are purely presentational and depend on data that won’t change after mount (static header title, fixed footer text, build timestamp…)
- Very deeply nested or expensive-to-render static content where you want to save even tiny amounts of render time
- Debugging or showing raw mustache syntax literally (rare)
Most important rule: Once the element is rendered the first time, Vue completely ignores it forever — no re-renders, no watchers, no updates, even if the data it depends on changes 1000 times.
2. Real, Practical Examples
Example 1 – Static Copyright & Version Footer (most common real use)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
<template> <footer class="app-footer"> <p>© {{ currentYear }} Webliance – All rights reserved</p> <!-- v-once here: this line will NEVER update again --> <p v-once>App version: {{ appVersion }}</p> <!-- This line WILL update if appVersion changes --> <p>Running version: {{ appVersion }}</p> </footer> </template> <script setup lang="ts"> import { ref } from 'vue' const currentYear = new Date().getFullYear() const appVersion = ref('3.5.2-beta') // Simulate version update after 5 seconds (e.g. hot-module-reload or API fetch) setTimeout(() => { appVersion.value = '3.5.3' }, 5000) </script> <style scoped> .app-footer { text-align: center; padding: 2rem; color: #6b7280; border-top: 1px solid #e5e7eb; margin-top: 4rem; } </style> |
What happens:
- First render: both lines show “App version: 3.5.2-beta” and “Running version: 3.5.2-beta”
- After 5 seconds: only the second line updates to “Running version: 3.5.3”
- The line with v-oncestays frozen at 3.5.2-beta forever
→ Saves tiny render time + prevents unnecessary DOM diffing on version changes
Example 2 – Static Price Display (e-commerce common pattern)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
<template> <div class="product-card"> <h3>{{ product.name }}</h3> <!-- Price never changes after first render – use v-once --> <p class="price" v-once> ₹{{ product.price.toLocaleString('en-IN') }} </p> <!-- Quantity & total WILL change dynamically --> <p>Quantity: {{ quantity }}</p> <p>Total: ₹{{ (quantity * product.price).toLocaleString('en-IN') }}</p> <button @click="quantity++">Add one more</button> </div> </template> <script setup lang="ts"> import { ref } from 'vue' const product = { name: 'Vue.js Mastery Course', price: 4999 } const quantity = ref(1) </script> <style scoped> .price { font-size: 2rem; font-weight: bold; color: #15803d; margin: 1rem 0; } </style> |
→ Even when quantity changes 100 times → Vue never re-evaluates or re-renders the price line
3. v-once vs Other “Static” Tools – Quick Comparison
| Directive / Pattern | What it does | Re-renders when data changes? | Use-case summary |
|---|---|---|---|
| v-once | Render once, never again | Never | Truly static content (copyright, version, fixed price) |
| v-memo | Skip re-render unless dependencies change | Only if deps change | Expensive subtree that rarely changes |
| Plain {{ }} interpolation | Normal reactive rendering | Every time | Most content |
| <KeepAlive> | Keeps component alive (state preserved) | Still re-renders when shown | Tabs, cached pages |
Rule of thumb (2026)
- Use v-once when content is 100% static forever after first render
- Use v-memo when content is almost static but might change occasionally
- Use normal reactivity ({{ }}) for 99% of dynamic content
4. Quick Summary Table – v-once in 2026
| Question | Answer / Best Practice |
|---|---|
| What does it do? | Renders element + subtree once, then freezes it forever |
| Does it run lifecycle hooks again? | No — only first mount |
| Does it re-evaluate expressions? | No — mustache {{ }} inside v-once is computed only once |
| Can it be combined with v-if/v-for? | Yes — but v-once wins → frozen even if condition changes |
| Performance gain? | Small (usually) — but adds up in large apps with many static parts |
| Still useful in 2026? | Yes — especially in landing pages, pricing tables, footers, version displays |
Pro Tips from Real Projects (Hyderabad 2026)
- Put v-once on static footers, copyright notices, build timestamps, fixed prices, static SVG icons
- Combine with <template v-once> when you want to freeze multiple elements without wrapper
- Use it sparingly — most content should stay reactive
- In marketing/landing pages — v-once + v-cloak together prevent any flash of raw {{ }}
- In performance audits — look for deeply nested static parts → wrap in v-once
- Never use v-once on anything that might need to update later — you’ll regret it
Your mini practice task:
- Create a simple component with:
- Dynamic {{ count }} counter
- Static {{ appVersion }} with v-once
- Static copyright footer with v-once
- Click button to increment count → watch version & footer stay frozen
- Change appVersion value in script → see v-once part never updates
Any part confusing? Want full examples for:
- v-once + v-cloak on landing page to prevent any flash?
- v-once vs v-memo vs normal reactivity comparison?
- v-once inside <Transition> or <TransitionGroup>?
- Real pricing table with frozen prices using v-once?
Just tell me — we’ll build the next clean, optimized static section together 🚀
