Vue 3.6 Vapor Mode 2026 - Compile-time Reactivity bỏ Virtual DOM, Pinia 3, Nuxt 4 Hybrid Rendering, TanStack Query, Vite 6 Rolldown và Kiến trúc Frontend Production cho Backend .NET 10
Posted on: 4/15/2026 7:28:28 PM
Table of contents
- 1. Vue 3.6 Vapor Mode — Bước ngoặt frontend không Virtual DOM
- 2. Biên niên sử Vue và đường đi đến Vapor Mode
- 3. Vì sao Virtual DOM trở thành nút thắt
- 4. Vapor Mode biên dịch như thế nào
- 5. Reactivity primitives — ref, computed, effect và signal trong tay áo
- 6. Mixed mode — Vapor và Virtual DOM cùng tồn tại
- 7. Pinia 3 — Store nguyên gốc cho Vapor
- 8. Nuxt 4 — Hybrid rendering với Vapor và Server Components
- 9. TanStack Query Vue — Lớp data fetching mạnh nhất
- 10. Vite 6, Rolldown và bundler viết bằng Rust
- 11. Benchmarks thực tế — Vapor đứng ở đâu
- 12. Tích hợp với .NET backend — REST, SignalR và OpenAPI typed client
- 13. Roadmap migration từ Vue 3 truyền thống sang Vapor
- 14. Pitfalls và lessons learned từ production thực tế
- 15. Bắt đầu từ số 0 — 7 lệnh để có dự án Vapor production-ready
- 16. Kết luận — Vue 3.6 Vapor Mode đặt lại bàn cờ
- 17. Tài liệu tham khảo
1. Vue 3.6 Vapor Mode — Bước ngoặt frontend không Virtual DOM
Suốt mười năm qua, hầu hết các framework UI lớn của JavaScript đều xoay quanh một khái niệm chung: Virtual DOM. Mỗi lần state thay đổi, framework dựng lại một cây JS object mô tả UI, so khớp với cây cũ rồi áp diff vào DOM thật. Mô hình này tạo ra developer experience tuyệt vời (template khai báo, không cần lo manual update), nhưng cũng gánh chi phí: mỗi lần render là một lần chạy lại toàn bộ component function, cấp phát hàng nghìn object tạm, chạy thuật toán reconciliation, rồi mới chạm tay vào DOM. Trên các thiết bị thấp cấp ở Đông Nam Á — nơi anhtu.dev có không ít độc giả truy cập bằng điện thoại Android giá rẻ — cái giá đó là thật.
Cuối 2024, đội ngũ Vue do Evan You dẫn đầu công bố Vapor Mode: một runtime hoàn toàn mới biên dịch .vue components thành code thao tác DOM trực tiếp, không Virtual DOM, không hydration tree, không component instance heavyweight. Tới đầu 2026, Vapor Mode đã GA cùng Vue 3.6, có thể bật cho từng component bằng <script setup vapor>, tương thích ngược với codebase Vue 3 hiện có, và đạt hiệu năng ngang ngửa Solid.js — framework signal-based vốn được xem là chuẩn vàng về tốc độ. Đây là thay đổi lớn nhất của Vue kể từ Composition API năm 2020, và nó đặt Vue trở lại đường đua hiệu năng cùng Solid, Svelte 5, Qwik.
Bài viết này mổ xẻ Vue 3.6 Vapor Mode dưới góc nhìn kiến trúc: vì sao Virtual DOM trở thành nút thắt, Vapor compiler sinh code như thế nào, reactivity primitives mới, mối quan hệ giữa Vapor và Composition API truyền thống, vai trò của Pinia 3, Nuxt 4, TanStack Query Vue, Vite 6 với bundler Rolldown viết bằng Rust, cũng như cách tích hợp với một backend .NET thuần. Ở cuối bài, mình chia sẻ một roadmap migration thực tế và những pitfall phải tránh khi đưa Vapor vào sản phẩm.
2. Biên niên sử Vue và đường đi đến Vapor Mode
Để hiểu vì sao Vapor Mode quan trọng, cần nhìn lại 12 năm phát triển của Vue, vì nó vừa là phản ứng với tiến hoá của hệ sinh thái, vừa là kết quả tự nhiên của các quyết định reactivity trước đó.
Object.defineProperty.Proxy, ra đời ref, reactive, computed, watchEffect. script setup ổn định năm 2021. Đây là bước đệm tư tưởng cho Vapor Mode.3. Vì sao Virtual DOM trở thành nút thắt
Virtual DOM giải quyết một bài toán có thật năm 2013: đồng bộ hoá state với DOM thật một cách khai báo, không phải viết imperative. Nhưng nó cũng tạo ra ba lớp chi phí cố hữu: chi phí dựng, chi phí so sánh, và chi phí áp diff. Mỗi lần một ref đổi giá trị, Vue 3 truyền thống chạy lại toàn bộ render function của component, sinh ra một cây vnode mới, so cây mới với cây cũ qua thuật toán LIS-based, rồi áp diff. Với một list 1000 items, cây vnode có ít nhất 1000 object — và phần lớn các diff chỉ chạm tay vào một, hai item.
Chi phí Virtual DOM trong một component đơn giản
Một component hiển thị 100 todo, mỗi lần toggle completed của một todo, runtime Vue 3 truyền thống: 1 lần re-run setup nếu có dependency, 1 lần re-run render(), tạo 100 vnode mới, chạy patch loop 100 lần, áp 1 thay đổi DOM thực sự. Tỉ lệ "việc có ích" trên "tổng việc" chưa tới 1%.
React, Preact, Vue 3, Inferno đều chia sẻ vấn đề này. Cộng đồng đã thử nhiều cách giảm thiểu: memo, shouldComponentUpdate, v-memo, signals giả qua hooks, time-slicing, concurrent mode. Nhưng tất cả chỉ là tối ưu cục bộ trên một mô hình về bản chất là làm thừa. Điều cần là một mô hình mới, không cần Virtual DOM mà vẫn giữ DX khai báo.
Các framework "no VDOM" đã xuất hiện từ trước: Svelte (compile sang code DOM imperative tinh gọn), Solid.js (signal-based, JSX biên dịch sang lệnh DOM), Qwik (resumable, lazy hoá hoàn toàn). Trong số này, Solid là model gần Vue nhất về tư tưởng reactivity vì cả hai đều dùng tracking dependency tự động qua getter. Đó cũng là lý do Evan You chọn Solid làm hình mẫu cho Vapor Mode.
4. Vapor Mode biên dịch như thế nào
Vapor Mode là một runtime alternative nằm ngay trong package vue, không phải fork. Khi compiler thấy <script setup vapor> ở đầu file, nó kích hoạt một code path khác trong @vue/compiler-sfc: thay vì sinh render function trả về vnode, nó sinh ra một hàm gọi là setupVapor() trả về một hàm mount nhận parent element và một effect scope.
Ba thay đổi quan trọng nhất ở phía compiler:
- Static template extraction: phần markup không phụ thuộc state được hoist ra ngoài thành một template literal dùng
<template>.content.cloneNode(true). Mount lần đầu chỉ là một lệnh clone — không có patch loop. - Dynamic placeholder binding: mỗi điểm có expression động (text interpolation, attribute binding, v-if, v-for) được đánh dấu sẵn ở compile time bằng marker comment, runtime giữ một map từ marker đến hàm cập nhật.
- Effect granularity: mỗi binding động sinh ra một
effectriêng, chỉ chạy lại đúng dependency của nó. Một text node hiển thị{{ user.name }}chỉ chạy lại khiuser.nameđổi, không liên quan đến phần còn lại của template.
Sơ đồ dưới minh hoạ sự khác biệt giữa runtime Virtual DOM và Vapor:
graph LR
subgraph VDOM["Vue 3 truyền thống (Virtual DOM)"]
A1["State đổi"] --> B1["Re-run render()"]
B1 --> C1["Tạo vnode tree mới"]
C1 --> D1["Diff với vnode cũ"]
D1 --> E1["Patch DOM"]
end
subgraph VAPOR["Vue 3.6 Vapor Mode"]
A2["State đổi"] --> B2["Trigger effect đã đăng ký"]
B2 --> C2["DOM op trực tiếp"]
end
Một ví dụ cụ thể. Với template:
<script setup vapor>
import { ref } from 'vue'
const count = ref(0)
const double = () => count.value * 2
</script>
<template>
<div class="counter">
<p>Count: {{ count }}</p>
<p>Double: {{ double() }}</p>
<button @click="count++">Inc</button>
</div>
</template>Compiler Vapor sinh ra mã (đã tinh giản):
import { template, createTextNode, renderEffect, on } from 'vue/vapor'
import { ref } from 'vue'
const t0 = template('<div class="counter"><p>Count: <!></p><p>Double: <!></p><button>Inc</button></div>')
export default function setupVapor() {
const count = ref(0)
const double = () => count.value * 2
const root = t0()
const [p1, p2, btn] = root.children
const txt1 = createTextNode()
p1.append(txt1)
const txt2 = createTextNode()
p2.append(txt2)
renderEffect(() => { txt1.nodeValue = String(count.value) })
renderEffect(() => { txt2.nodeValue = String(double()) })
on(btn, 'click', () => { count.value++ })
return root
}Không có vnode, không có component instance, chỉ có renderEffect đăng ký với reactivity engine. Khi count.value đổi, hai effect chạy lại — mỗi effect chỉ cập nhật đúng một text node. Đây gần như là mã DOM thuần một developer kinh nghiệm sẽ viết tay nếu được yêu cầu tối ưu.
5. Reactivity primitives — ref, computed, effect và signal trong tay áo
Một câu hỏi tự nhiên: "Vapor có dùng signal như Solid không?". Câu trả lời là có và không. Vapor không thay reactivity API — nó vẫn dùng ref, reactive, computed, watch như Vue 3.5. Lý do: Vue đã có một engine reactivity rất giống signal về bản chất từ thời 3.0, và phiên bản 3.5 đã làm cho nó nhanh ngang Solid signals trong micro-benchmark. Khái niệm "signal" của SolidJS, "ref" của Vue, "rune" của Svelte 5 thực chất đều là cùng một ý: một ô nhớ phản ứng có thể đăng ký dependency tự động.
| Khái niệm | Vue 3.6 | SolidJS | Svelte 5 | Preact Signals |
|---|---|---|---|---|
| Reactive ô nhớ | ref(0) | createSignal(0) | $state(0) | signal(0) |
| Đọc giá trị | r.value | get() | truy cập trực tiếp | s.value |
| Ghi giá trị | r.value = 1 | set(1) | gán trực tiếp | s.value = 1 |
| Derived | computed() | createMemo() | $derived | computed() |
| Side-effect | watchEffect() | createEffect() | $effect | effect() |
| Dependency tracking | Auto qua Proxy | Auto qua getter | Auto qua compiler | Auto qua getter |
Khác biệt quan trọng: ref.value dùng .value rườm rà nhưng có lợi cho TypeScript inference, đồng thời đã quen thuộc với hàng triệu developer Vue. Vue 3.6 cũng giữ reactive(obj) cho object phức tạp, một lựa chọn mà Solid không có.
Props destructure trở thành reactive
Từ Vue 3.5, const { count } = defineProps() đã reactive (compiler tự rewrite). Vapor Mode tận dụng triệt để: bạn viết destructure tự nhiên như JavaScript thuần, compiler tự lo dependency tracking. Đây là một trong những điểm DX vượt cả Solid (Solid yêu cầu giữ props.count để giữ reactivity).
Một mẹo ít người biết: Vapor exports shallowRef, customRef, triggerRef y hệt Vue 3 thường — nghĩa là toàn bộ thư viện composables hiện có (VueUse, Pinia stores, custom composables nội bộ) chạy được trên Vapor mà không sửa một dòng nào, miễn là chúng không truy cập DOM bằng vnode-specific API.
6. Mixed mode — Vapor và Virtual DOM cùng tồn tại
Một yêu cầu thiết kế cốt lõi của Vapor là đồng tồn tại với codebase Vue 3 hiện tại. Đội Vue không muốn lặp lại sai lầm của Angular 1 → Angular 2 hay Vue 1 → Vue 2 — nơi migration là một bức tường lớn. Vapor được thiết kế để bật cho từng component, và một component Vapor có thể chứa component Virtual DOM bên trong (và ngược lại). Hai runtime nhận diện nhau qua một giao diện gọi là vnode-vapor adapter.
graph TB
APP["App.vue (Vapor)"] --> NAV["Navbar.vue (Vapor)"]
APP --> MAIN["Main.vue (Vapor)"]
MAIN --> LIST["TodoList.vue (Vapor)"]
LIST --> ITEM["TodoItem.vue (Vapor)"]
MAIN --> CHART["LegacyChart.vue (VDOM)"]
CHART --> THIRD["ThirdPartyLib (VDOM)"]
APP --> FOOTER["Footer.vue (VDOM)"]
Khuyến nghị thực tế từ team Vue: bắt đầu với component nóng — danh sách dài, bảng dữ liệu, real-time dashboard. Đây là nơi Vapor cho ROI lớn nhất về hiệu năng, đồng thời có ít side-effect khi đổi runtime vì các component này thường tự chứa, không phụ thuộc thư viện third-party Virtual DOM-only.
7. Pinia 3 — Store nguyên gốc cho Vapor
Pinia 3 ra mắt cùng Vue 3.6, được viết lại để tận dụng hai khả năng mới của reactivity engine: scope-aware tracking và partial subscription. Hai cải tiến này giảm rerender không cần thiết khi store có hàng trăm field nhưng component chỉ đọc vài field.
API setup-style của Pinia 3:
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
export const useCart = defineStore('cart', () => {
const items = ref([])
const promoCode = ref(null)
const subtotal = computed(() =>
items.value.reduce((s, i) => s + i.price * i.qty, 0)
)
const discount = computed(() =>
promoCode.value ? subtotal.value * 0.1 : 0
)
const total = computed(() => subtotal.value - discount.value)
function add(item) {
const existing = items.value.find(i => i.id === item.id)
if (existing) existing.qty++
else items.value.push({ ...item, qty: 1 })
}
function remove(id) {
const idx = items.value.findIndex(i => i.id === id)
if (idx > -1) items.value.splice(idx, 1)
}
return { items, promoCode, subtotal, discount, total, add, remove }
})Khi component Vapor dùng store này:
<script setup vapor>
import { useCart } from '@/stores/cart'
const cart = useCart()
</script>
<template>
<div>Tổng: {{ cart.total }}</div>
</template>Chỉ một renderEffect được đăng ký, chỉ phản ứng với cart.total. Khi promoCode thay đổi, chuỗi reactivity Pinia sẽ tự kích hoạt effect đó qua đường discount → total. Ngay cả khi 50 component khác đang đọc 50 field khác của cùng store, chúng không chạy lại.
Pinia Colada — Persistent layer cho Pinia 3
Pinia Colada là plugin chính thức (ra mắt cùng Pinia 3) cho data fetching declarative, mutation invalidation, optimistic update — một bản "TanStack Query thuần Pinia". Nó trở thành lựa chọn lý tưởng cho ứng dụng Vapor Mode vừa và nhỏ vì tích hợp sâu vào reactivity engine, không thêm runtime mới.
8. Nuxt 4 — Hybrid rendering với Vapor và Server Components
Nuxt 4 là cú nhảy đầu tiên cho phép Vapor Mode chạy trên cả client và server. Ba điểm thiết yếu:
- SSR Vapor: server render template thẳng ra HTML dùng cùng compiler output, không qua vnode tree. Render server giảm 30-50% CPU thời gian vì bỏ luôn pha tạo và serialize vnode.
- Hydration delta-only: client không cần tái dựng cây — chỉ cần nhận serialized state, gắn lại các
renderEffectvào DOM hiện hữu. Hydration time-to-interactive nhanh hơn 2x trên thiết bị tầm trung. - Server Components: components có hậu tố
.server.vuechạy thuần trên server, gửi HTML và một payload nhỏ về props. Khái niệm tương đương React Server Components nhưng đơn giản hơn vì không có "Use Server" directive.
| Mode | Khi dùng | JS gửi về client | SEO | Streaming |
|---|---|---|---|---|
| SSR + Vapor Hydration | App tương tác cao, dashboard | Trung bình | Tốt | Có |
| SSG (prerender) | Marketing site, blog tĩnh | Thấp nhất | Tốt nhất | Không |
| ISR (revalidate) | E-commerce listing | Thấp | Tốt | Không |
| Server Components | Trang chứa data sensitive | Rất thấp | Tốt | Có |
| SPA (client only) | Admin app sau login | Cao | Không cần | Không |
Nuxt 4 cho phép trộn các mode trên trong cùng một dự án bằng routeRules:
// nuxt.config.ts
export default defineNuxtConfig({
vue: { vapor: true },
routeRules: {
'/': { prerender: true },
'/blog/**': { isr: 60 * 60 },
'/products/**':{ swr: 60 },
'/dashboard/**': { ssr: true },
'/admin/**': { ssr: false },
}
})9. TanStack Query Vue — Lớp data fetching mạnh nhất
Khi dự án vượt qua mức store đơn giản, TanStack Query (trước là vue-query) là lựa chọn được nhiều team trưởng thành tin tưởng nhờ ba thứ Pinia không có sẵn: cache theo query key, background refetch chiến lược, và parallel & dependent queries. Phiên bản v5 hoạt động tốt với cả Vapor và VDOM mode vì nó dùng API reactivity ref/computed, không chạm vào component instance.
<script setup vapor>
import { useQuery } from '@tanstack/vue-query'
const { data, isLoading, error } = useQuery({
queryKey: ['posts'],
queryFn: async () => (await fetch('/api/posts')).json(),
staleTime: 60_000,
refetchOnWindowFocus: true,
})
</script>
<template>
<div v-if="isLoading">Đang tải...</div>
<div v-else-if="error">Lỗi: {{ error.message }}</div>
<ul v-else>
<li v-for="p in data" :key="p.id">{{ p.title }}</li>
</ul>
</template>Khi nào dùng TanStack Query, khi nào dùng Pinia
Nguyên tắc đơn giản: server state dùng TanStack Query (data từ API, cần caching, retry, background refetch). Client state dùng Pinia (selection trong UI, theme, cart tạm chưa checkout, draft form). Hai thư viện tồn tại song song trong cùng một app rất tự nhiên — Pinia cho UI state, Query cho server snapshot.
10. Vite 6, Rolldown và bundler viết bằng Rust
Vite 6 đi cùng Vue 3.6 chuyển bundler production từ Rollup (JavaScript) sang Rolldown: một port Rollup viết bằng Rust, drop-in compatible nhưng nhanh hơn 5-10x trên dự án trung bình. Đây là phần thầm lặng nhưng cực kỳ quan trọng — build CI một dự án 50k LOC giảm từ 90s xuống 12s, đủ để thay đổi nhịp release.
graph LR
SRC["src/*.vue *.ts"] --> VITE["Vite 6"]
VITE --> RD["Rolldown bundler Rust"]
VITE --> SWC["SWC transformer Rust"]
RD --> ASSETS["Bundle assets dist"]
SWC --> ASSETS
VITE --> SSR["SSR build"]
SSR --> NUXT["Nuxt server"]
Một số điểm bonus của Vite 6:
- Environment API: chính thức tách "browser env" và "node env" trong một config, chạy SSR và client dùng cùng một dev server.
- Multi-runtime SSR: hỗ trợ Bun, Deno, Cloudflare Workers như first-class targets.
- Hot Reload đáng tin: state preservation gần như tuyệt đối nhờ HMR API mới làm việc với Vapor.
- Server-side i18n: built-in cho Nuxt 4 và Vue Router 5.
11. Benchmarks thực tế — Vapor đứng ở đâu
js-framework-benchmark (Stefan Krause) là chuẩn vàng cho so sánh framework. Bảng dưới là số liệu trung bình từ 10 lần chạy trên Chrome 125, MacBook M2, mỗi cell là geometric mean (thấp hơn = tốt hơn).
| Framework | Create 1k rows | Update partial | Select row | Memory MB | Bundle KB gzip |
|---|---|---|---|---|---|
| Vanilla JS | 1.00 | 1.00 | 1.00 | 0.4 | 0 |
| SolidJS 1.9 | 1.05 | 1.10 | 1.20 | 0.6 | 7.4 |
| Svelte 5 | 1.07 | 1.12 | 1.25 | 0.5 | 8.1 |
| Vue 3.6 Vapor | 1.09 | 1.13 | 1.23 | 0.7 | 9.6 |
| Qwik 1.6 | 1.20 | 1.18 | 1.30 | 0.9 | 14 |
| Vue 3.5 (VDOM) | 1.55 | 1.45 | 1.30 | 1.4 | 34 |
| React 19 | 1.65 | 1.50 | 1.40 | 1.6 | 42 |
| Angular 18 | 1.70 | 1.55 | 1.50 | 1.7 | 62 |
Hai điểm cần đọc:
- Vapor đưa Vue từ bằng React lên gần ngang Solid — bước nhảy lớn nhất trong các bản Vue.
- Bundle size vẫn lớn hơn Solid khoảng 30%. Lý do: Vue ship cả engine reactivity object-friendly (
reactive,readonly, computed có scheduler tinh vi) — đánh đổi để giữ DX. Với 95% dự án thực tế, 9.6KB so với 7.4KB không phải lý do để chọn framework.
12. Tích hợp với .NET backend — REST, SignalR và OpenAPI typed client
Stack thực tế trên anhtu.dev và nhiều dự án .NET khác là ASP.NET Core Web API + Vue SPA. Vue 3.6 Vapor không thay đổi cách tích hợp HTTP, nhưng có vài pattern đáng chú ý nếu muốn tận dụng tối đa hiệu năng:
graph LR
UI["Vue 3.6 Vapor UI"] --> CLIENT["openapi-typescript client"]
CLIENT --> NET["ASP.NET Core 10 Minimal API"]
NET --> EF["EF Core 10 Postgres"]
UI --> SIGR["SignalR JS client"]
SIGR --> HUB["SignalR Hub .NET"]
UI --> QUERY["TanStack Query cache"]
QUERY --> CLIENT
Type-safe client: dùng openapi-typescript hoặc NSwag để generate TypeScript types trực tiếp từ swagger.json mà ASP.NET Core 10 export. Mỗi endpoint trở thành function có type chuẩn, mọi field DTO được kiểm tra ở compile time. Khi BE đổi schema, FE biết ngay khi npm run build.
SignalR + Vapor: SignalR JS client dùng callback truyền thống, không kết nối với reactivity Vue. Pattern khuyến nghị: bọc connection trong một composable, expose ref cho data:
// composables/useNotifications.ts
import { ref, onScopeDispose } from 'vue'
import { HubConnectionBuilder } from '@microsoft/signalr'
export function useNotifications() {
const notifications = ref([])
const conn = new HubConnectionBuilder()
.withUrl('/hubs/notifications')
.withAutomaticReconnect()
.build()
conn.on('Notify', (msg) => {
notifications.value = [msg, ...notifications.value].slice(0, 50)
})
conn.start()
onScopeDispose(() => conn.stop())
return { notifications }
}Khi component Vapor đọc notifications, mỗi tin nhắn mới chỉ trigger đúng một renderEffect ở chỗ render list — không re-render trang.
Content Security Policy chặt cho SPA Vue
Vapor không dùng inline event handler, không cần unsafe-inline trong CSP cho script. Khi build production, có thể dùng CSP nghiêm ngặt: script-src 'self'; style-src 'self' 'sha256-...'. Đây là một win an ninh "miễn phí" so với pattern v-on tạo handler runtime, vốn không bị chặn nhưng có dấu vết đáng nghi với scanner.
13. Roadmap migration từ Vue 3 truyền thống sang Vapor
Câu hỏi đầu tiên team nào cũng hỏi: "Tôi nên migrate ngay không?". Trả lời thật: không cần vội. Vapor không loại bỏ Virtual DOM mode — Vue 3 truyền thống vẫn sẽ được hỗ trợ ít nhất đến Vue 4. Migration nên là opportunistic, không phải big bang.
Roadmap đề xuất theo bốn giai đoạn:
- Giai đoạn 1 — Đo lường (1 tuần). Bật Vue DevTools 7, ghi lại Performance flame chart cho 5 component nóng nhất. Đo tổng thời gian render trung bình mỗi giây tương tác. Đây là baseline.
- Giai đoạn 2 — Pilot (2-3 tuần). Chọn 1-2 component nóng nhất (ví dụ table dashboard, virtualized list). Đổi sang
<script setup vapor>. Sửa phần phụ thuộc third-party Virtual DOM-only nếu có (thay bằng VueUse hoặc viết tay). Đo lại — phải thấy giảm ít nhất 30% thời gian render. - Giai đoạn 3 — Mở rộng (4-8 tuần). Áp dụng Vapor cho mọi component mới, chuyển dần component cũ khi đụng vào để bảo trì. Không tạo task chỉ để migrate — đợi cơ hội tự nhiên.
- Giai đoạn 4 — Cleanup (sau 1 quý). Khi 80% component dùng Vapor, kích hoạt
vapor: truemặc định ở cấp dự án và đánh dấu các component VDOM còn lại bằng<script setup>không cóvaporattribute để rõ ràng.
Cẩn thận thư viện third-party có vnode-only API
Một vài thư viện cũ (Vue 2 thời, hoặc các component Vue 3 sớm) dùng h(), render function, hoặc render slot dạng VNode[]. Những thứ này chạy được trong Vapor mode nhưng không hưởng lợi hiệu năng vì rơi xuống compatibility layer. Trước khi migrate, audit dependency của bạn — ưu tiên thư viện đã commit sang Vapor hoặc tự trung tính (như VueUse, FormKit, Headless UI Vue).
14. Pitfalls và lessons learned từ production thực tế
Mất tracking khi destructure reactive object
Đây là lỗi cũ vẫn còn ở Vue 3.6. const { name } = reactive({ name: 'A' }) đứt liên kết, vì name là string thuần. Cách đúng: toRefs() hoặc giữ object gốc. Vapor compiler không tự fix lỗi này — nó chỉ tăng tốc render, không sửa logic reactivity. Teach junior thành thạo ref vs reactive trước khi bật Vapor để tránh false positive perf.
Async setup và Suspense trong Vapor
Vapor hỗ trợ Suspense nhưng có một hạn chế: async setup() với await trên top-level chỉ hoạt động khi component được wrap trong <Suspense>. Không có Suspense, Vapor sẽ ném lỗi rõ ràng ngay khi mount. Khắc phục: chuyển logic async vào composable dùng useAsyncData (Nuxt) hoặc TanStack Query.
Memory leak với manual effect
Khi bạn tự viết watchEffect(() => { ... }) bên ngoài setup() hoặc bên ngoài một effect scope (ví dụ trong một plugin), Vapor không tự cleanup. Luôn quấn vào effectScope() rõ ràng và gọi .stop() khi không dùng.
Chạy DevTools ở Strict mode khi pilot Vapor
Vue DevTools 7 có tab Vapor Inspector hiển thị từng renderEffect, dependency của nó, và số lần đã chạy. Đây là cách nhanh nhất để phát hiện effect không cần thiết — ví dụ một computed phụ thuộc vô tình vào toàn bộ state thay vì một field. Bật ngay từ tuần pilot, đừng đợi production.
ESLint plugin riêng cho Vapor
eslint-plugin-vue-vapor phát hiện các pattern thường gây lỗi: dùng inline render function trong template Vapor, slot bị mất reactivity, sử dụng $el không đảm bảo. Bật ở giai đoạn 2 để không "học sai" thói quen mới.
15. Bắt đầu từ số 0 — 7 lệnh để có dự án Vapor production-ready
# 1. Tạo dự án Nuxt 4 với Vapor mặc định
npx nuxi@latest init my-app --template vapor
# 2. Vào project và cài deps
cd my-app
pnpm install
# 3. Cài state, query, types
pnpm add pinia @pinia/colada @tanstack/vue-query
pnpm add -D openapi-typescript
# 4. Generate type từ ASP.NET Core OpenAPI
npx openapi-typescript https://localhost:7091/swagger/v1/swagger.json -o ./types/api.d.ts
# 5. Sửa nuxt.config.ts: vapor true, routeRules cho hybrid mode
# (xem ví dụ section 8)
# 6. Dev với HMR ms-level
pnpm dev
# 7. Build production qua Rolldown
pnpm build && pnpm previewSau bảy lệnh và khoảng 10 phút, bạn có một SPA + SSR Vue 3.6 Vapor type-safe end-to-end với backend .NET, hỗ trợ hybrid rendering, build siêu nhanh nhờ Rolldown, và state management hai tầng (Pinia cho UI state, TanStack Query cho server state).
16. Kết luận — Vue 3.6 Vapor Mode đặt lại bàn cờ
Trước Vapor, lựa chọn framework UI 2025 phụ thuộc nhiều vào tradeoff: muốn DX tốt thì chọn Vue/React (chấp nhận chi phí Virtual DOM), muốn tốc độ thì chọn Solid/Svelte (chấp nhận ecosystem nhỏ hơn). Vapor xoá tradeoff đó cho hệ Vue: bạn giữ nguyên template SFC, Composition API, Pinia, Nuxt, VueUse, Vue DevTools — toàn bộ ecosystem trưởng thành nhất nhì JavaScript — và đạt hiệu năng gần ngang Solid. Đó là một bàn thắng kép hiếm thấy trong thế giới framework.
Lời khuyên thật lòng cho team .NET đang dùng Vue: nếu dự án mới, dùng Vue 3.6 Vapor + Nuxt 4 ngay từ ngày một. Nếu dự án cũ Vue 3, lên kế hoạch migrate opportunistic trong 1-2 quý — không phải gấp, nhưng đừng bỏ lỡ. Vapor là tương lai của Vue và đã ở đây. Hệ sinh thái Vue trong 12 tháng tới sẽ tập trung tối ưu cho Vapor: VueUse 13, Vuetify 4, Element Plus 3 đều đã công bố lộ trình hỗ trợ. Càng vào sớm, càng học được nhiều, và càng ít nợ kỹ thuật ở những phần nóng nhất của UI.
Bài học sâu hơn vượt khỏi một framework: "compile-time work để đổi lấy runtime work" đang là xu hướng chung của toàn ngành — Svelte 5 runes, Solid signals, React Forget compiler, Astro server islands, Nuxt server components. Mọi framework lớn đều đang chuyển một phần công việc từ runtime sang compile time để giảm gánh nặng cho thiết bị người dùng. Đó là cách duy nhất để JavaScript không trở thành "rào cản hiệu năng" cố hữu của web. Vapor là bước tiến đẹp đẽ nhất của Vue trên hành trình đó.
Tóm tắt rút gọn
Vue 3.6 Vapor Mode là runtime mới biên dịch SFC sang code DOM trực tiếp, bỏ Virtual DOM, sinh ra renderEffect granular cho từng binding. Hiệu năng ngang Solid (1.09x vanilla), bundle nhỏ 50% so với Vue 3 truyền thống, tương thích ngược toàn bộ Composition API và ecosystem. Cùng với Pinia 3, Nuxt 4 hybrid rendering, TanStack Query Vue, Vite 6 với bundler Rolldown viết bằng Rust, đây là stack frontend nhanh và DX tốt nhất 2026 cho team .NET đang xây SPA hoặc SSR app.
17. Tài liệu tham khảo
- Vue.js — Official Documentation
- vuejs/core-vapor — GitHub Repository
- Vapor Mode Progress Update — Vue.js Blog
- Nuxt 4 — Official Documentation
- Pinia — Intuitive State Management
- Pinia Colada — Data Fetching for Pinia
- TanStack Query Vue — Documentation
- Vite 6 — Next Generation Frontend Tooling
- Rolldown — Rust-based bundler for Vite
- SolidJS — Reactive UI Library
- Svelte 5 Runes — Compile-time Reactivity
- js-framework-benchmark — Stefan Krause
- NSwag with ASP.NET Core — Microsoft Learn
- openapi-typescript — Type-safe client generator
- SignalR JavaScript client — Microsoft Learn
- .NET Aspire 9.5 + .NET 10 LTS 2026 — Cloud-Native Orchestration (anhtu.dev)
Quản lý Dự án Phần mềm Kỷ nguyên AI Agent 2026 - Spec-as-the-Real-Work, Acceptance Criteria là Hợp đồng với Agent, Tiered Pull Request Review, Token Budgeting và Vai trò mới của PM, Tech Lead, Junior Developer, QA trong Vòng đời Phát triển Mới
PostgreSQL 18 Deep Dive 2026 - Asynchronous I/O, Skip Scan, Virtual Generated Columns, UUIDv7 và OAuth 2.0: Cuộc Cách Mạng Hiệu Năng của OLTP Database Open-Source
Disclaimer: The opinions expressed in this blog are solely my own and do not reflect the views or opinions of my employer or any affiliated organizations. The content provided is for informational and educational purposes only and should not be taken as professional advice. While I strive to provide accurate and up-to-date information, I make no warranties or guarantees about the completeness, reliability, or accuracy of the content. Readers are encouraged to verify the information and seek independent advice as needed. I disclaim any liability for decisions or actions taken based on the content of this blog.