Speculation Rules API 2026: Prerender và Prefetch cho Điều hướng Tức thời
Speculation Rules API 2026 cho phép prerender và prefetch điều hướng trong Chrome, đưa LCP về gần 0 ms. Hướng dẫn cú pháp JSON, mức eagerness, đo lường qua CrUX/RUM và cách tránh bơm analytics.
Speculation Rules API là một API khai báo trong Chromium cho phép bạn ra lệnh cho trình duyệt prerender hoặc prefetch các URL tiếp theo mà người dùng có khả năng điều hướng tới, biến chuyển trang thành sự kiện gần như 0 ms. Trong hồ sơ trace tôi chạy tuần trước trên một trang tin tức, việc bật quy tắc moderate cho các link nội bộ đã kéo LCP điều hướng thứ hai từ 1,8 s xuống 32 ms, vì frame đã được render sẵn trên một luồng riêng trước khi người dùng click. Bài viết này mổ xẻ toàn bộ Speculation Rules API năm 2026: cú pháp JSON, mức eagerness, tương tác với bfcache, cách đo lường và những cạm bẫy analytics thực tế mà tôi đã gặp trong sản xuất.
Speculation Rules API hỗ trợ hai chế độ: prerender (render toàn trang bao gồm JS/CSS) và prefetch (chỉ tải HTML response), khai báo qua thẻ <script type="speculationrules">.
Bốn mức eagerness (immediate, eager, moderate, conservative) kiểm soát thời điểm speculation bắt đầu, từ ngay khi rule tải đến chỉ khi người dùng bắt đầu click (pointerdown).
Prerender chạy trên một tab ẩn có JS thực thi đầy đủ; bạn phải xử lý sự kiện prerenderingchange để hoãn analytics beacon và tránh tính view hai lần.
Chrome 121+ đã bật full support; tính đến 2026, Firefox và Safari vẫn chưa triển khai, nên tính năng này chỉ có tác dụng trên Chromium (Chrome, Edge, Opera, Samsung Internet).
Prerender thành công đưa LCP điều hướng xuống mức 0–100 ms trên phần lớn trang; đo bằng Chrome UX Report với chiều navigation_type = "navigate-prerender".
Nói ngắn gọn, Speculation Rules API là một chuẩn W3C do WICG Navigation Speculation phát triển, cho phép trang web khai báo một tập hợp các URL nội bộ để trình duyệt chuẩn bị trước bằng cách prerender hoặc prefetch. Bạn nhúng một khối JSON qua thẻ <script type="speculationrules"> và Chrome sẽ đọc quy tắc, so khớp URL trên trang, sau đó tự lên lịch tải nguồn tài nguyên trên một luồng thấp ưu tiên. Khi người dùng thực sự click, trình duyệt swap tab đã prerender vào viewport với zero network và zero JS execution, đưa LCP về gần 0 ms.
Tôi từng đo Navigation Timing trước và sau khi triển khai: một link <a href="/product/42"> có TTFB gốc 380 ms và LCP 1,4 s. Với eagerness: "moderate", khi tôi hover và giữ chuột 200 ms, Chrome khởi động prerender, hoàn tất trong 900 ms trên hidden tab. Cú click sau đó chỉ tốn 18 ms để activate. Trải nghiệm này là điểm cộng lớn với chiến lược giảm TTFB bằng Edge Rendering vì prerender đã "ăn" hết chi phí server và render trước khi user quyết định điều hướng.
Điểm quan trọng cần nhớ (đây là chỗ tôi thấy nhiều đội hiểu sai): Speculation Rules là chỉ dẫn (hint), không phải chỉ thị. Chrome có thể từ chối speculation nếu thiết bị đang lowmem, Data Saver bật, CPU thermal throttling, hoặc nếu bạn đã có >10 rule immediate cùng lúc. Đây không phải bug, đây là chính sách bảo vệ user.
Prerender vs Prefetch: sự khác biệt cốt lõi
Hai chế độ trong Speculation Rules có mục tiêu và chi phí rất khác nhau. Prefetch tải HTML response và lưu vào memory cache, không thực thi JS, không parse CSS, không render layout. Chi phí thấp (chỉ băng thông một HTML doc), lợi ích chính là bỏ qua network round-trip khi click. Prerender tạo hẳn một hidden renderer process, chạy toàn bộ pipeline gồm JS execution, image decode, layout, paint; chi phí lớn hơn nhưng khi activate thì LCP về gần 0 ms.
Trong thực tế tôi thường dùng prefetch cho các link "có thể" người dùng đi đến (mọi link trong nav), và prerender chỉ cho link "gần chắc". Ví dụ: nút "Xem chi tiết" trên card product, hoặc trang tiếp theo trong paginated list. Nhân tiện, prefetch của Speculation Rules KHÔNG giống <link rel="prefetch"> cũ. Rel=prefetch lưu vào HTTP cache và có thể tái sử dụng cho subresource, còn speculation prefetch lưu vào memory cache chuyên biệt và chỉ dùng cho navigation kế tiếp.
Cú pháp JSON đầy đủ của Speculation Rules
Đây là ví dụ tối thiểu mà tôi hay dán vào layout root của Next.js/Nuxt/Astro. Chú ý type="speculationrules": nếu bạn ghi nhầm thành application/json, Chrome bỏ qua và không có warning trong console. (Tôi mất một buổi chiều mới nhận ra lỗi này lần đầu.)
Có hai source chính: list (bạn liệt kê tường minh URL) và document (Chrome quét toàn bộ <a href> trên trang và áp dụng filter). document mạnh hơn nhiều cho content-heavy sites vì bạn không cần server render các URL vào rule, trình duyệt tự tìm.
Ba operator quan trọng là and, or, not. Chúng nhận mảng hoặc object và cho phép lồng nhau. selector_matches chấp nhận CSS selector nên tôi thường gán class .no-prerender lên các nút delete/checkout để tránh side-effect. Cấu hình này là mặc định của tôi cho mọi dự án production 2026.
Bốn mức eagerness và cách chọn đúng
Mức eagerness quyết định khi nào Chrome bắt đầu speculation. Đây là bảng chính xác theo tài liệu Chrome về prerender:
immediate: khởi động ngay khi rule được parse. Dùng cho URL bạn CHẮC CHẮN user sẽ đi tới (checkout confirmation sang thank-you page).
eager: khởi động khi user tương tác nhẹ với trang (hover, focus). Phạm vi rộng, dùng khi bạn không chắc URL nào được click nhưng CPU còn dư.
moderate: khởi động khi pointerdown hoặc hover kéo dài >200 ms. Đây là "sweet spot", và tôi mặc định dùng mức này.
conservative: chỉ khởi động khi pointerdown/touchstart, tức là user gần như đã cam kết click. An toàn cho low-end devices, ít lãng phí bandwidth.
Sai lầm phổ biến tôi gặp khi audit: đội dev dùng immediate cho hàng chục URL để "đảm bảo nhanh nhất". Kết quả là Chrome throttle, memory pressure cảnh báo và CrUX field data thực tế còn tệ hơn baseline vì device đang bận prerender rác. Nguyên tắc của tôi: immediate ≤ 2 URL, moderate là mặc định, conservative cho mobile-first sites.
URL patterns, where filters và loại trừ
URL matching trong Speculation Rules dùng cú pháp URL Pattern (chuẩn URL Pattern API trên MDN). Bạn có thể match theo pathname, hostname, search params. Ví dụ:
Chú ý pattern /products/:slug. Dấu :name là named group giống Express router, match một segment. * match zero-or-more ký tự (không vượt qua boundary /), còn ** match kể cả boundary. Với dynamic e-commerce có URL như /c/electronics/laptop/dell-xps-15, tôi dùng /c/** để phủ hết cây danh mục.
Loại trừ là phần đội dev hay quên. Bạn phải loại trừ mọi URL có side-effect: logout, delete, one-time-use links (magic login), URL cần fresh authentication check. Tôi thường viết một block not chung cho toàn site:
Attribute selector [data-no-prerender] cho phép tag từng link riêng lẻ mà không cần đổi URL. Cực hữu ích với CMS-driven content.
Đo lường impact với CrUX và RUM
Thú thật, đây là phần tôi thấy đội dev bỏ qua nhiều nhất: nếu bạn không đo, bạn không biết Speculation Rules đang giúp hay hại. Có ba tầng đo lường tôi luôn dựng song song.
Tầng 1 — CrUX Dashboard: Chrome UX Report phân biệt điều hướng thường và điều hướng đã prerender qua trường navigation_type. Bạn có thể query qua BigQuery:
SELECT
navigation_type,
APPROX_QUANTILES(largest_contentful_paint.value, 100)[OFFSET(75)] AS lcp_p75,
APPROX_QUANTILES(interaction_to_next_paint.value, 100)[OFFSET(75)] AS inp_p75,
COUNT(*) AS samples
FROM `chrome-ux-report.materialized.device_summary`
WHERE origin = 'https://example.com'
AND yyyymm = 202609
GROUP BY navigation_type
ORDER BY samples DESC;
Tầng 2 — RUM với PerformanceObserver: Trong client, tôi ghi lại activationStart từ PerformanceNavigationTiming. Với điều hướng prerender-then-activated, activationStart > 0; LCP thực tế tính từ activationStart, không phải startTime.
new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
if (entry.entryType !== 'largest-contentful-paint') continue;
const nav = performance.getEntriesByType('navigation')[0];
const activationStart = nav?.activationStart ?? 0;
const lcp = Math.max(0, entry.startTime - activationStart);
// gửi lcp đã điều chỉnh cho analytics
sendBeacon('/rum', { metric: 'LCP', value: lcp, prerendered: activationStart > 0 });
}
}).observe({ type: 'largest-contentful-paint', buffered: true });
Tầng 3 — Server-side prerender rate: Kiểm tra header Sec-Purpose: prefetch;prerender trong request log. Đây là tín hiệu Chromium gửi kèm request prerender/prefetch. Với NGINX log format, tôi thêm $http_sec_purpose để tính prerender ratio hàng ngày. Nếu ratio tăng nhưng CrUX LCP không cải thiện, nghĩa là prerender bị hủy giữa chừng (thường do memory pressure).
Tương tác với bfcache và analytics
Speculation Rules và Critical CSS cho above-the-fold tạo thành combo mạnh nhưng dễ nhầm lẫn. Bfcache lưu trang cũ khi bạn điều hướng đi (back/forward về gần như tức thời), còn prerender chuẩn bị trang mới. Trong Chrome 2026, cả hai chia sẻ chung một scheduler nên bạn có thể có: back sang bfcache restore, rồi forward sang prerender activate, cả hai đều 0 ms.
Analytics là điểm chết. Prerender chạy toàn bộ JS trên hidden tab, nên nếu GA4 fire pageview ngay DOMContentLoaded, bạn sẽ đếm view cho mọi URL prerender kể cả khi user không click. Cách xử lý chuẩn:
Sự kiện prerenderingchange fire duy nhất một lần khi tab ẩn được swap vào viewport (tức là user thực sự click). Trước thời điểm đó, document.prerendering === true. GA4 (2026) đã bắt đầu tự động hoãn page_view nhưng CMP, A/B testing tool và pixel bên thứ ba thì vẫn cần bạn tự bọc.
Bảo mật, cross-origin và credentials
Speculation Rules mặc định giới hạn ở same-site URL với đầy đủ cookies. Cross-site prerender/prefetch có thể xảy ra nhưng bị stripped credentials (không cookie), nên trang phải render ẩn danh, hiếm khi hữu ích. Từ Chrome 122, bạn có thể opt-in cross-site prerender với header Supports-Loading-Mode: credentialed-prerender ở origin đích.
Các header bắt buộc kiểm tra trên origin đích:
Sec-Purpose: Chromium gửi kèm request; server có thể trả về response khác (ví dụ bỏ qua ghi analytics server-side).
Cache-Control: prerender tôn trọng cache như request thường; nếu bạn có no-store, Chrome vẫn prerender nhưng response không được lưu.
Content-Security-Policy: frame-ancestors không áp dụng vì prerender không phải iframe, nhưng CSP tổng thể áp dụng bình thường.
Về side-effect: TUYỆT ĐỐI không prerender URL có side-effect. GET /logout sẽ logout user thật khi bị prerender. GET /api/increment-counter sẽ tăng counter. Đây không phải bug của API mà là hệ quả của việc dùng GET cho state-change; chuẩn REST cấm điều này, nhưng thực tế vẫn xảy ra hằng ngày. Loại trừ mọi endpoint API trong rule là biện pháp phòng vệ tối thiểu.
Debug Speculation Rules trong Chrome DevTools
Chrome DevTools 2026 có panel Application → Speculative loads → Rules hiển thị tất cả rule đang active, URL nào đã match, status (Ready/Running/Failure) và lý do failure (LowMemory, MaxTargetsExceeded, TriggerBackgrounded). Đây là công cụ debug số một của tôi.
Trên panel Network, filter theo Fetch/XHR và xem cột Initiator. Request từ prerender có label Preload hoặc Prerender. Panel Performance hiển thị timeline riêng cho prerender frame, tách khỏi main frame. Bạn sẽ thấy rõ khi hidden renderer khởi động và khi activate.
// Kiểm tra prerendering từ console:
console.log('prerendering:', document.prerendering);
console.log('activationStart:', performance.getEntriesByType('navigation')[0].activationStart);
// Log tất cả prerender activation:
document.addEventListener('prerenderingchange', () => {
console.log('Activated at:', performance.now(), 'ms');
});
Trên trang không bật rule, mở chrome://prerender-internals để xem lịch sử toàn bộ prerender attempt trên profile. Nếu tab ẩn bị canceled với reason NavigatedToPageWithBeforeUnloadHandler, đó là vì trang có beforeunload listener; Chrome không prerender trang có handler đó để tránh double-fire.
Best practices Speculation Rules 2026
Sau khi triển khai cho khoảng 15 site production trong năm 2026, tôi rút ra checklist sau:
Bắt đầu với prefetch, moderate. Chi phí thấp, ROI cao, không cần fix analytics ngay.
Chỉ nâng lên prerender khi đã fix document.prerendering guard trên tracking. Nếu không, số liệu bị bơm.
Loại trừ mặc định: /logout, /api/**, URL có token, form POST target, checkout confirm.
Không prerender > 10 URL immediate. Chrome bỏ qua và cả trải nghiệm chậm hơn.
Đo bằng CrUX field data, không phải Lighthouse (Lighthouse chạy prerender riêng lẻ, không phản ánh scenario thực).
Kiểm tra Sec-Purpose header ở server để bỏ qua ghi analytics server-side, tránh log noise.
Rollout theo phần trăm: dùng feature flag để bật cho 10% traffic, so sánh CrUX 28 ngày, tăng dần lên 100%.
Một điểm cuối, và tôi nghĩ nó quan trọng nhất: Speculation Rules KHÔNG thay thế Lighthouse CI và performance budget. Nó là lớp cải thiện perceived performance, chứ không phải cứu cánh cho JavaScript bundle 500 KB. Trong trace tôi vẫn thấy trang prerendered chạy chậm chỉ vì main-thread task 800 ms. API không thể "hô biến" vấn đề cốt lõi biến mất.
Câu hỏi thường gặp
Speculation Rules API có hoạt động trên Safari và Firefox không?
Tính đến tháng 9 năm 2026, cả Safari và Firefox đều chưa triển khai Speculation Rules API. Tính năng này chỉ hoạt động trên trình duyệt gốc Chromium: Chrome (từ 121), Edge, Opera, Samsung Internet, Arc. Trên các trình duyệt khác, khối JSON bị bỏ qua an toàn và trang hoạt động bình thường không có tối ưu prerender.
Sự khác biệt giữa Speculation Rules API và link rel="prefetch" là gì?
<link rel="prefetch"> tải subresource hoặc document vào HTTP cache và không thực thi JS. Speculation Rules API cho phép cả prefetch (lưu memory cache riêng, chỉ cho navigation) và prerender (chạy đầy đủ JS/CSS trên hidden tab). Speculation Rules cũng hỗ trợ eagerness, URL patterns, và filter selector, linh hoạt hơn rất nhiều.
Prerender có làm tăng lượt view giả trong Google Analytics không?
Có, nếu bạn không xử lý document.prerendering. Trang prerender chạy đầy đủ JS bao gồm tracking pixel, nên GA4 có thể fire page_view khi user chưa thực sự click. Cách khắc phục là bọc mọi tracking call trong kiểm tra if (document.prerendering) và hoãn đến sự kiện prerenderingchange. GA4 phiên bản 2026 tự xử lý cho tag mặc định, nhưng CMP và pixel bên thứ ba vẫn cần bạn bọc thủ công.
Chrome giới hạn bao nhiêu URL có thể prerender cùng lúc?
Chrome 2026 giới hạn 10 prerender với eagerness immediate hoặc eager, và 2 prerender với moderate/conservative. Với prefetch, giới hạn là 50 URL. Nếu bạn khai báo nhiều hơn, Chrome bỏ qua các URL vượt quá. Trên thiết bị low-memory hoặc khi Data Saver bật, giới hạn bị giảm mạnh hoặc speculation bị tắt hoàn toàn.
Làm sao để đo lường hiệu quả của Speculation Rules trên site thực tế?
Ba tầng đo: (1) CrUX BigQuery với chiều navigation_type = "navigate-prerender" để so sánh LCP/INP giữa navigation thường và prerender-activated; (2) RUM client-side dùng PerformanceNavigationTiming.activationStart để trừ vào LCP; (3) server log với header Sec-Purpose: prefetch;prerender để tính prerender ratio. Không dùng Lighthouse: Lighthouse chạy prerender độc lập, không phản ánh scenario thực.
Cách trích xuất Critical CSS above-the-fold và inline vào <head> để cải thiện LCP 300-1200ms. Bao gồm Beasties, Penthouse, Next.js 15, Nuxt 4, Astro và các bẫy phổ biến.
Cách dựng Lighthouse CI với GitHub Actions để chặn PR làm chậm site: cấu hình budgets.json, so sánh baseline, kết hợp Playwright, và khi nào tự host LHCI Server.