Web Performance 2026 — Core Web Vitals, Speculation Rules API và View Transitions
Posted on: 4/17/2026 10:20:19 AM
Table of contents
- 1. Core Web Vitals 2026 — Ba trụ cột mới
- 2. INP Deep Dive — Chỉ số khó nhất để tối ưu
- 3. Speculation Rules API — Prerender thông minh
- 4. View Transitions API — Chuyển trang mượt như Native App
- 5. Combo Tối Thượng: Speculation Rules + View Transitions
- 6. LCP Optimization Checklist
- 7. CLS — Giữ layout ổn định
- 8. Islands Architecture — Selective Hydration
- 9. Đo lường và Monitoring
- 10. Performance Budget và CI/CD Gate
- 11. Tổng kết chiến lược
- Nguồn tham khảo
Năm 2026, người dùng web kỳ vọng trải nghiệm ngang ngửa ứng dụng native — mọi tương tác phải phản hồi dưới 200ms, trang mới phải hiển thị gần như tức thì, và layout không được nhảy lung tung. Google đã chính thức thay thế FID bằng INP (Interaction to Next Paint), đồng thời trình duyệt hiện đại mang đến hai API đột phá: Speculation Rules API cho prerender/prefetch thông minh, và View Transitions API cho chuyển trang mượt mà. Bài viết này đi sâu vào kiến trúc tối ưu hiệu năng web toàn diện cho năm 2026.
1. Core Web Vitals 2026 — Ba trụ cột mới
Core Web Vitals là bộ ba chỉ số đo lường trải nghiệm người dùng thực tế, ảnh hưởng trực tiếp đến ranking trên Google Search. Năm 2026, bộ ba này gồm LCP, INP, và CLS — với INP là thay đổi lớn nhất.
LCP — Largest Contentful Paint
Thời gian render phần tử lớn nhất trong viewport (hình ảnh hero, heading chính, video poster). Phản ánh tốc độ tải "nội dung chính" mà người dùng quan tâm.
INP — Interaction to Next Paint
Đo latency từ lúc người dùng tương tác (click, tap, keypress) đến khi trình duyệt paint frame tiếp theo. Khác FID, INP đo toàn bộ tương tác trong vòng đời trang, không chỉ lần đầu.
CLS — Cumulative Layout Shift
Tổng điểm dịch chuyển layout bất ngờ trong suốt vòng đời trang. Ảnh hưởng bởi hình ảnh không có kích thước, font flash, nội dung inject động.
graph LR
A[User Request] --> B[TTFB]
B --> C[FCP]
C --> D[LCP]
D --> E[User Interaction]
E --> F[INP]
G[Layout Shift Events] --> H[CLS Score]
style A fill:#e94560,stroke:#fff,color:#fff
style D fill:#e94560,stroke:#fff,color:#fff
style F fill:#e94560,stroke:#fff,color:#fff
style H fill:#e94560,stroke:#fff,color:#fff
style B fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style C fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style E fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style G fill:#f8f9fa,stroke:#e94560,color:#2c3e50
Timeline các chỉ số Core Web Vitals trong vòng đời trang web
2. INP Deep Dive — Chỉ số khó nhất để tối ưu
INP có tỷ lệ pass thấp nhất trong ba chỉ số Core Web Vitals, bởi việc tối ưu đòi hỏi thay đổi kiến trúc JavaScript ở mức sâu — không đơn giản là thêm lazy loading hay nén ảnh.
2.1. Nguyên nhân INP cao
graph TD
A[INP cao > 200ms] --> B[Long Tasks trên Main Thread]
A --> C[Event Handler nặng]
A --> D[Third-party Scripts]
A --> E[Layout Thrashing]
B --> B1[JavaScript bundle > 300KB]
B --> B2[Hydration toàn trang]
B --> B3[Parsing JSON lớn]
C --> C1[Re-render toàn bộ component tree]
C --> C2[Synchronous computation]
C --> C3[DOM manipulation lớn]
D --> D1[Analytics scripts]
D --> D2[Chat widgets]
D --> D3[Social media embeds]
E --> E1[Read-write layout loop]
E --> E2[Forced reflow]
style A fill:#c62828,stroke:#fff,color:#fff
style B fill:#e94560,stroke:#fff,color:#fff
style C fill:#e94560,stroke:#fff,color:#fff
style D fill:#e94560,stroke:#fff,color:#fff
style E fill:#e94560,stroke:#fff,color:#fff
style B1 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style B2 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style B3 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style C1 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style C2 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style C3 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style D1 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style D2 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style D3 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style E1 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style E2 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
Nguyên nhân chính khiến INP vượt ngưỡng 200ms
2.2. Kỹ thuật tối ưu INP
a) Yield to Main Thread với scheduler.yield()
API mới scheduler.yield() cho phép "nhường" main thread giữa các tác vụ nặng, giúp trình duyệt xử lý input events và paint frames kịp thời:
async function processLargeDataset(items) {
const results = [];
for (let i = 0; i < items.length; i++) {
results.push(transformItem(items[i]));
// Yield mỗi 50 items để main thread xử lý user input
if (i % 50 === 0 && i > 0) {
await scheduler.yield();
}
}
return results;
}
// Fallback cho trình duyệt chưa hỗ trợ
function yieldToMain() {
if ('scheduler' in window && 'yield' in scheduler) {
return scheduler.yield();
}
return new Promise(resolve => setTimeout(resolve, 0));
}
b) Web Workers cho tác vụ nặng
Chuyển các tính toán nặng sang Web Worker, giữ main thread chỉ xử lý UI:
// Worker thread — chạy song song, không block main thread
self.onmessage = function(e) {
const { data, query } = e.data;
// Fuzzy search trên dataset lớn — nếu chạy trên main thread
// sẽ block UI 200-500ms
const results = data.filter(item =>
item.title.toLowerCase().includes(query.toLowerCase()) ||
item.content.toLowerCase().includes(query.toLowerCase())
);
// Ranking theo relevance
results.sort((a, b) => scoreRelevance(b, query) - scoreRelevance(a, query));
self.postMessage({ results: results.slice(0, 50) });
};
const searchWorker = new Worker('/search-worker.js');
searchInput.addEventListener('input', debounce((e) => {
searchWorker.postMessage({
data: allProducts,
query: e.target.value
});
}, 150));
searchWorker.onmessage = (e) => {
renderSearchResults(e.data.results);
};
Mẹo thực tế
Đặt touch target tối thiểu 48x48 pixels và cung cấp visual feedback ngay lập tức (CSS :active state) cho mọi nút bấm. Người dùng cảm nhận tương tác nhanh hơn khi có phản hồi thị giác, ngay cả khi processing chưa xong.
c) Tối ưu INP trên Vue.js
Vue.js có một số kỹ thuật đặc thù giúp giảm INP đáng kể:
<script setup>
import { shallowRef, computed, triggerRef } from 'vue'
// shallowRef: chỉ track reference change, không deep-watch
// Với 10,000+ items, tiết kiệm ~60% reactivity overhead
const products = shallowRef([])
// Computed với lazy evaluation — chỉ tính khi template cần
const filteredProducts = computed(() => {
return products.value.filter(p => p.active)
})
async function loadProducts() {
const data = await fetch('/api/products').then(r => r.json())
products.value = data // trigger re-render
}
function updateProduct(id, changes) {
const idx = products.value.findIndex(p => p.id === id)
if (idx !== -1) {
products.value[idx] = { ...products.value[idx], ...changes }
triggerRef(products) // manual trigger vì shallowRef
}
}
</script>
<template>
<!-- v-once cho static content, giảm re-render cost -->
<header v-once>
<h1>Product Catalog</h1>
<p>Browse our collection</p>
</header>
<!-- Virtual scroll cho list dài -->
<VirtualList :items="filteredProducts" :item-height="80">
<template #default="{ item }">
<ProductCard :product="item" @update="updateProduct" />
</template>
</VirtualList>
</template>
Bundle size budget cho Vue apps (2026)
Marketing pages: ≤ 200KB JS | Transactional pages: ≤ 300KB JS | Dashboards: ≤ 350KB JS. Vượt ngưỡng này, INP gần như chắc chắn > 200ms trên thiết bị mid-range.
3. Speculation Rules API — Prerender thông minh
Speculation Rules API cho phép khai báo trước những trang mà người dùng có khả năng điều hướng đến. Trình duyệt sẽ prefetch (tải trước tài nguyên) hoặc prerender (render sẵn toàn bộ trang) ở background, giúp navigation gần như tức thì.
sequenceDiagram
participant U as User
participant B as Browser
participant S as Server
Note over B: Đọc Speculation Rules
B->>S: Prerender /pricing (background)
S-->>B: HTML + CSS + JS (cached)
Note over B: Render sẵn /pricing trong hidden tab
U->>B: Click link /pricing
Note over B: Swap prerendered page ~0ms
B-->>U: Trang /pricing hiển thị tức thì
Note over U,B: LCP ≈ 0ms vì đã prerender sẵn
Flow prerender với Speculation Rules API — trang đích đã sẵn sàng trước khi user click
3.1. Cú pháp khai báo
Speculation Rules sử dụng JSON khai báo trực tiếp trong HTML hoặc qua HTTP header:
<script type="speculationrules">
{
"prerender": [
{
"source": "document",
"where": {
"selector_matches": "nav a, a.cta-button"
},
"eagerness": "moderate"
}
],
"prefetch": [
{
"source": "document",
"where": {
"selector_matches": ".blog-list a"
},
"eagerness": "conservative"
}
]
}
</script>
Speculation-Rules: "/speculation-rules.json"
// File speculation-rules.json (MIME: application/speculationrules+json)
{
"prerender": [
{
"source": "list",
"urls": ["/pricing", "/checkout", "/dashboard"]
}
]
}
3.2. Eagerness levels
| Eagerness | Trigger | Use case | Bandwidth cost |
|---|---|---|---|
| immediate | Ngay khi rule được parse | Trang tiếp theo gần như chắc chắn (checkout flow) | Cao |
| eager | Tương tự immediate, ít ưu tiên hơn | Top navigation links | Cao |
| moderate | Hover trên link ~200ms | Navigation chính, CTA buttons | Trung bình |
| conservative | Pointerdown hoặc touchstart | Blog list, search results | Thấp |
Khi nào dùng prefetch vs prerender?
Prefetch: tải trước HTML và critical resources — tiết kiệm bandwidth, phù hợp cho danh sách nhiều link (blog list, search results).
Prerender: render toàn bộ trang sẵn — tốn nhiều tài nguyên hơn nhưng navigation gần tức thì, phù hợp cho CTA flow (pricing → checkout) hoặc navigation chính.
3.3. Tích hợp Speculation Rules vào Vue Router
import { onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
export function useSpeculationRules() {
const router = useRouter()
let scriptEl: HTMLScriptElement | null = null
function updateRules() {
// Xóa rules cũ
scriptEl?.remove()
// Lấy tất cả route paths có thể navigate
const routes = router.getRoutes()
.filter(r => !r.meta?.noPrerender)
.map(r => r.path)
.slice(0, 10) // Giới hạn 10 trang để tiết kiệm tài nguyên
const rules = {
prerender: [{
source: "list",
urls: routes.filter(r => r === '/pricing' || r === '/checkout'),
eagerness: "moderate"
}],
prefetch: [{
source: "list",
urls: routes.filter(r => r !== '/pricing' && r !== '/checkout'),
eagerness: "conservative"
}]
}
scriptEl = document.createElement('script')
scriptEl.type = 'speculationrules'
scriptEl.textContent = JSON.stringify(rules)
document.head.appendChild(scriptEl)
}
onMounted(updateRules)
onUnmounted(() => scriptEl?.remove())
return { updateRules }
}
4. View Transitions API — Chuyển trang mượt như Native App
View Transitions API cho phép trình duyệt thực hiện animated transitions giữa hai trạng thái DOM — cả trong SPA và cross-document navigation (MPA). Kết hợp với Speculation Rules, ta có navigation vừa nhanh vừa mượt.
graph LR
subgraph "Trước View Transitions"
A1[Page A] -->|"Hard cut - flash trắng"| B1[Page B]
end
subgraph "Với View Transitions"
A2[Page A] -->|"Capture snapshot"| T[Transition Layer]
T -->|"Crossfade + morph"| B2[Page B]
end
style A1 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style B1 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
style A2 fill:#e94560,stroke:#fff,color:#fff
style T fill:#2c3e50,stroke:#fff,color:#fff
style B2 fill:#e94560,stroke:#fff,color:#fff
View Transitions tạo layer chuyển tiếp giữa hai trạng thái, loại bỏ hiệu ứng "flash trắng"
4.1. Cross-document View Transitions (MPA)
Cho các trang multi-page truyền thống (bao gồm .NET Core MVC), chỉ cần thêm meta tag:
<!-- Opt-in View Transitions cho cross-document navigation -->
<meta name="view-transition" content="same-origin" />
<style>
/* Transition mặc định: crossfade */
::view-transition-old(root) {
animation: fade-out 0.2s ease-in;
}
::view-transition-new(root) {
animation: fade-in 0.3s ease-out;
}
/* Named transition cho hero image — morph giữa list và detail */
.blog-card img {
view-transition-name: hero-image;
}
.blog-detail .hero {
view-transition-name: hero-image;
}
::view-transition-group(hero-image) {
animation-duration: 0.4s;
animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
}
@keyframes fade-out { to { opacity: 0; } }
@keyframes fade-in { from { opacity: 0; } }
</style>
4.2. SPA View Transitions với Vue Router
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [/* ... */]
})
// Wrap mỗi navigation trong View Transition
router.beforeResolve(async (to, from) => {
// Chỉ apply transition khi API được hỗ trợ
if (!document.startViewTransition) return
// Đặt class hint để CSS biết direction
const direction = to.meta?.index > from.meta?.index
? 'forward' : 'back'
document.documentElement.dataset.transition = direction
await new Promise(resolve => {
const transition = document.startViewTransition(() => {
resolve(undefined)
})
})
})
export default router
/* Slide transition dựa trên direction */
[data-transition="forward"]::view-transition-old(root) {
animation: slide-out-left 0.3s ease-in;
}
[data-transition="forward"]::view-transition-new(root) {
animation: slide-in-right 0.3s ease-out;
}
[data-transition="back"]::view-transition-old(root) {
animation: slide-out-right 0.3s ease-in;
}
[data-transition="back"]::view-transition-new(root) {
animation: slide-in-left 0.3s ease-out;
}
@keyframes slide-out-left { to { transform: translateX(-100%); opacity: 0; } }
@keyframes slide-in-right { from { transform: translateX(100%); opacity: 0; } }
@keyframes slide-out-right { to { transform: translateX(100%); opacity: 0; } }
@keyframes slide-in-left { from { transform: translateX(-100%); opacity: 0; } }
5. Combo Tối Thượng: Speculation Rules + View Transitions
Khi kết hợp cả hai API, trình duyệt prerender trang đích sẵn rồi animate transition giữa hai trang đã render. Kết quả: LCP ≈ 0ms và transition mượt mà — trải nghiệm không thể phân biệt với native app.
sequenceDiagram
participant U as User
participant B as Browser (Main)
participant P as Prerender Tab
participant S as Server
Note over B: Parse Speculation Rules
B->>S: GET /destination (background)
S-->>P: Full page HTML + assets
Note over P: Render hoàn chỉnh (hidden)
U->>B: Hover link /destination
Note over B: moderate eagerness → đã prerender xong
U->>B: Click link /destination
Note over B,P: startViewTransition()
B->>B: Capture old snapshot
B->>P: Swap to prerendered page
B->>B: Animate old → new
B-->>U: Smooth transition ~300ms
Note over U: LCP = 0ms, visual transition mượt
Speculation Rules prerender + View Transitions = instant & smooth navigation
<!DOCTYPE html>
<html>
<head>
<!-- 1. Opt-in View Transitions -->
<meta name="view-transition" content="same-origin" />
<!-- 2. Speculation Rules -->
<script type="speculationrules">
{
"prerender": [
{
"source": "document",
"where": { "selector_matches": "nav a, .cta-primary" },
"eagerness": "moderate"
}
],
"prefetch": [
{
"source": "document",
"where": { "selector_matches": ".blog-list a, footer a" },
"eagerness": "conservative"
}
]
}
</script>
<style>
/* 3. View Transition animations */
::view-transition-old(root) {
animation: fade-and-scale-out 0.25s ease-in forwards;
}
::view-transition-new(root) {
animation: fade-and-scale-in 0.35s ease-out forwards;
}
@keyframes fade-and-scale-out {
to { opacity: 0; transform: scale(0.95); }
}
@keyframes fade-and-scale-in {
from { opacity: 0; transform: scale(1.05); }
}
</style>
</head>
<body>
<!-- Content -->
</body>
</html>
6. LCP Optimization Checklist
Ngoài Speculation Rules (giải quyết LCP triệt để cho navigation giữa các trang), vẫn cần tối ưu LCP cho lần tải đầu tiên:
| Kỹ thuật | Impact | Implementation |
|---|---|---|
| Preload LCP resource | -200ms đến -1s | <link rel="preload" as="image" href="hero.webp" fetchpriority="high"> |
| fetchpriority="high" | -100ms đến -500ms | Thêm vào LCP image/element để browser ưu tiên tải |
| AVIF/WebP format | -30% đến -50% size | <picture> với fallback: AVIF → WebP → JPEG |
| CDN + Edge caching | -50ms đến -200ms TTFB | Cache HTML tại edge, stale-while-revalidate cho assets |
| Server-side rendering | -500ms đến -2s | .NET Core: MapRazorPages() hoặc Vue SSR với Nuxt |
| Inline critical CSS | -100ms đến -300ms | Embed above-the-fold CSS trong <head>, defer phần còn lại |
7. CLS — Giữ layout ổn định
CLS đo sự dịch chuyển layout bất ngờ. Năm 2026, nguyên nhân phổ biến nhất là web fonts, hình ảnh không kích thước, và quảng cáo/embed inject muộn.
7.1. Kỹ thuật triệt tiêu CLS
/* 1. Luôn set aspect-ratio hoặc width/height cho media */
img, video {
max-width: 100%;
height: auto;
aspect-ratio: attr(width) / attr(height);
}
/* 2. Font display swap + size-adjust để giảm layout shift */
@font-face {
font-family: 'Inter';
src: url('/fonts/inter.woff2') format('woff2');
font-display: swap;
/* size-adjust khớp fallback font → giảm shift khi swap */
size-adjust: 107%;
ascent-override: 90%;
descent-override: 22%;
line-gap-override: 0%;
}
/* 3. Reserve space cho dynamic content */
.ad-slot {
min-height: 250px; /* Giữ chỗ trước khi ad load */
contain: layout; /* CSS Containment ngăn ảnh hưởng layout */
}
/* 4. content-visibility cho offscreen sections */
.below-fold-section {
content-visibility: auto;
contain-intrinsic-size: 0 500px; /* Ước lượng chiều cao */
}
CSS Containment — vũ khí ẩn cho CLS
contain: layout nói với trình duyệt rằng nội dung bên trong element không ảnh hưởng layout bên ngoài. Kết hợp content-visibility: auto giúp browser skip rendering cho off-screen sections, giảm cả CLS lẫn render time.
8. Islands Architecture — Selective Hydration
Thay vì hydrate toàn bộ trang (ship cả megabyte JS cho mỗi trang), Islands Architecture chỉ hydrate các "đảo" tương tác. Phần static HTML còn lại không cần JavaScript — giảm đáng kể bundle size và INP.
graph TD
subgraph "Traditional SPA"
T1[Full Page JS Bundle 450KB] --> T2[Hydrate toàn bộ DOM]
T2 --> T3[INP bị block 300-800ms]
end
subgraph "Islands Architecture"
I1[Static HTML - 0KB JS] --> I2[Island: Search 25KB]
I1 --> I3[Island: Cart 18KB]
I1 --> I4[Island: Comments 30KB]
I2 --> I5[Chỉ hydrate khi visible]
I3 --> I5
I4 --> I5
I5 --> I6[INP < 100ms]
end
style T1 fill:#c62828,stroke:#fff,color:#fff
style T3 fill:#c62828,stroke:#fff,color:#fff
style I1 fill:#4CAF50,stroke:#fff,color:#fff
style I6 fill:#4CAF50,stroke:#fff,color:#fff
style I2 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style I3 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style I4 fill:#f8f9fa,stroke:#e94560,color:#2c3e50
style I5 fill:#e94560,stroke:#fff,color:#fff
style T2 fill:#f8f9fa,stroke:#e0e0e0,color:#2c3e50
So sánh JS budget: SPA truyền thống vs Islands Architecture
Trong ecosystem Vue, Nuxt 4 hỗ trợ Islands Architecture native qua <NuxtIsland> component và server components. Với .NET Core, pattern tương tự có thể đạt được bằng Blazor SSR kết hợp enhanced navigation.
9. Đo lường và Monitoring
Tối ưu mà không đo lường thì chỉ là đoán mò. Dưới đây là pipeline giám sát Core Web Vitals đề xuất:
graph LR
A[web-vitals.js Library] -->|Real User Data| B[Analytics Endpoint]
B --> C[Dashboard / Alerting]
D[Lighthouse CI] -->|Synthetic Tests| C
E[CrUX API] -->|28-day Field Data| C
C --> F{Pass CWV?}
F -->|Yes| G[Monitor & Maintain]
F -->|No| H[Debug với DevTools Performance Panel]
H --> I[Fix & Deploy]
I --> A
style A fill:#e94560,stroke:#fff,color:#fff
style C fill:#2c3e50,stroke:#fff,color:#fff
style F fill:#e94560,stroke:#fff,color:#fff
style G fill:#4CAF50,stroke:#fff,color:#fff
style H fill:#ff9800,stroke:#fff,color:#fff
Pipeline đo lường CWV: kết hợp RUM, synthetic testing, và CrUX data
9.1. Tích hợp web-vitals vào production
import { onINP, onLCP, onCLS } from 'web-vitals';
function sendToAnalytics(metric) {
const body = JSON.stringify({
name: metric.name,
value: metric.value,
rating: metric.rating, // "good" | "needs-improvement" | "poor"
delta: metric.delta,
id: metric.id,
navigationType: metric.navigationType,
url: location.href,
// Attribution data giúp debug root cause
...(metric.attribution && {
element: metric.attribution.element,
largestShiftTarget: metric.attribution.largestShiftTarget,
interactionTarget: metric.attribution.interactionTarget,
})
});
// sendBeacon đảm bảo data được gửi ngay cả khi user rời trang
if (navigator.sendBeacon) {
navigator.sendBeacon('/api/vitals', body);
} else {
fetch('/api/vitals', { body, method: 'POST', keepalive: true });
}
}
// Đo tất cả Core Web Vitals với attribution
onLCP(sendToAnalytics, { reportAllChanges: true });
onINP(sendToAnalytics, { reportAllChanges: true });
onCLS(sendToAnalytics, { reportAllChanges: true });
9.2. Endpoint thu thập trên .NET Core
app.MapPost("/api/vitals", async (HttpContext ctx) =>
{
var metric = await ctx.Request.ReadFromJsonAsync<WebVitalMetric>();
// Log structured data cho monitoring
logger.LogInformation(
"CWV {Name}: {Value}ms rating={Rating} url={Url} element={Element}",
metric.Name, metric.Value, metric.Rating,
metric.Url, metric.Element);
// Có thể push vào time-series DB để dashboard
await metricsService.RecordAsync(metric);
return Results.Ok();
});
record WebVitalMetric(
string Name, double Value, string Rating,
double Delta, string Url, string? Element);
10. Performance Budget và CI/CD Gate
Thiết lập performance budget trong CI pipeline đảm bảo không ai vô tình ship code làm tụt Core Web Vitals:
{
"ci": {
"assert": {
"assertions": {
"categories:performance": ["error", { "minScore": 0.9 }],
"largest-contentful-paint": ["error", { "maxNumericValue": 2500 }],
"interactive": ["error", { "maxNumericValue": 3800 }],
"cumulative-layout-shift": ["error", { "maxNumericValue": 0.1 }],
"total-blocking-time": ["error", { "maxNumericValue": 300 }]
}
},
"collect": {
"numberOfRuns": 3,
"url": ["http://localhost:3000/", "http://localhost:3000/blog"]
}
}
}
Performance budget thực tế cho 2026
Lighthouse score ≥ 90 là baseline, nhưng đừng chỉ dựa vào synthetic tests. Kết hợp CrUX data (dữ liệu thực tế từ Chrome users) qua Chrome UX Report API để thấy performance trên thiết bị và mạng thực tế của người dùng.
11. Tổng kết chiến lược
| Chỉ số | Chiến lược chính | API / Tool | Expected Impact |
|---|---|---|---|
| LCP | Prerender + preload + SSR | Speculation Rules, fetchpriority, .NET SSR | ≈ 0ms (prerendered) / < 2s (first load) |
| INP | Yield main thread + selective hydration | scheduler.yield(), Web Workers, Islands | < 150ms trên mid-range devices |
| CLS | Reserve space + font stability | aspect-ratio, size-adjust, content-visibility | < 0.05 |
| Navigation UX | Animated transitions | View Transitions API + Speculation Rules | Native-app feel, ~0ms perceived latency |
| Monitoring | RUM + synthetic CI gates | web-vitals.js, Lighthouse CI, CrUX | Catch regressions before production |
Web Performance 2026 không còn chỉ là nén ảnh và minify JS. Với Speculation Rules API, View Transitions API, và các kỹ thuật tối ưu INP hiện đại, ta có thể mang đến trải nghiệm web ngang ngửa native app — trang tải tức thì, tương tác mượt mà, và chuyển cảnh đẹp mắt. Hãy bắt đầu đo lường, thiết lập performance budget, và tích hợp các API mới vào pipeline phát triển của bạn ngay hôm nay.
Nguồn tham khảo
- Web Vitals — Google web.dev
- Prerender pages in Chrome for instant page navigations — Chrome Developers
- View Transitions API — Chrome Developers
- Speculation Rules API — MDN Web Docs
- Improvements to the Speculation Rules API — Chrome for Developers Blog
- Blazing Fast Websites with Speculation Rules — DebugBear
- Performance — Vue.js Official Documentation
- The Most Important Core Web Vitals Metrics in 2026 — NitroPack
Redis 8 và Caching Patterns 2026 - I/O Threading, Vector Set và Chiến lược Cache hiệu năng cao
Elasticsearch 9 và Hybrid Search 2026 — BBQ, ELSER, Retrievers API và Kiến trúc Search System cho Production
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.