Web vitals từ người dùng thật
Đo LCP, INP, CLS, FCP và TTFB trong trình duyệt của người dùng với @belvador/browser: mặc định tắt, lấy mẫu, hỏi đồng ý, chỉ gửi đường dẫn đã bỏ id, xem p75 theo route trên node của bạn.
Trong bài này
Web vitals cho bạn biết trang của mình nhanh thế nào với chính người đang dùng: đo trong trình duyệt của họ, trên thiết bị và mạng của họ, không phải trong phòng thí nghiệm. SDK trình duyệt của Belvador thu chúng từ phiên bản 0.7.0; dữ liệu nằm trên node của bạn như mọi thứ khác và hiện ở Metrics → Web vitals.
Tính năng này mặc định tắt. Không có gì về web vitals được tải hay gửi cho tới khi bạn bật.
Bật web vitals#
import { init } from "@belvador/browser"; // or @belvador/react, @belvador/tanstack-start/clientinit({ dsn: "https://<public-key>@ingest.belvador.dev/<project-id>", // Real-user LCP, INP, CLS, FCP and TTFB from 10 % of page loads. webVitals: { sampleRate: 0.1 },});Dùng giống hệt với @belvador/react và @belvador/tanstack-start/client. Node của workspace cần agent 0.1.40 trở lên (node tự cập nhật); trước đó tab Web vitals báo node cần cập nhật trước.
| Tuỳ chọn | Mặc định | Tác dụng |
|---|---|---|
webVitals | tắt | true hoặc một object sẽ bật web vitals. |
sampleRate | 10 % lượt tải trang | Tỉ lệ lượt tải trang được đo, từ 0 tới 1. Quyết định một lần cho mỗi lượt tải, trước khi tải bất cứ thứ gì. |
consent | không đặt | Hàm được hỏi trước mỗi lần gửi. Khác true — hoặc hàm ném lỗi — thì không gửi gì. |
respectGpc | true | Khi trình duyệt báo Global Privacy Control, không gửi gì. |
route | route của router khi dùng withBelvadorRouter, nếu không thì đường dẫn với id thay bằng :id | Hàm đổi đường dẫn thành mẫu route (/w/acme/settings → /w/:slug/settings). Không trả gì hoặc lỗi thì gửi /:unmatched. |
Khi có banner đồng ý#
import { init } from "@belvador/browser";init({ dsn: "https://<public-key>@ingest.belvador.dev/<project-id>", webVitals: { sampleRate: 0.1, // Asked before every send: nothing leaves until your consent banner says yes. consent: () => window.localStorage.getItem("analytics-consent") === "granted", },});consent được hỏi lại trước mỗi lần gửi, nên người đồng ý giữa chừng sẽ được đo từ lúc đó, còn người rút lại đồng ý sẽ không bị đo nữa.
Đo những gì#
SDK dùng thư viện web-vitals của Google (bản attribution). Mỗi chỉ số hiển thị ở phân vị 75 (p75) của các mẫu và được xếp loại theo ngưỡng web.dev:
| Chỉ số | Đo gì | Tốt | Cần cải thiện | Kém | Tên metric |
|---|---|---|---|---|---|
| LCP | Hiển thị phần tử lớn nhất: lúc nội dung chính đã hiện | ≤ 2.5 s | ≤ 4 s | > 4 s | browser.web_vital.lcp |
| INP | Phản hồi tương tác: trang mất bao lâu để phản hồi một cú bấm, chạm hoặc phím | ≤ 200 ms | ≤ 500 ms | > 500 ms | browser.web_vital.inp |
| CLS | Xê dịch bố cục: bố cục nhảy bao nhiêu | ≤ 0.1 | ≤ 0.25 | > 0.25 | browser.web_vital.cls |
| FCP | Hiển thị nội dung đầu tiên: lúc có bất kỳ nội dung nào hiện ra | ≤ 1.8 s | ≤ 3 s | > 3 s | browser.web_vital.fcp |
| TTFB | Thời gian tới byte đầu tiên: lúc máy chủ bắt đầu trả lời | ≤ 800 ms | ≤ 1.8 s | > 1.8 s | browser.web_vital.ttfb |
LCP, CLS và INP là Core Web Vitals. CLS và INP chỉ chốt khi trang bị ẩn hoặc đóng, nên tới vào lúc đó; LCP, FCP và TTFB tới ngay khi có giá trị. Không phải trình duyệt nào cũng báo đủ mọi chỉ số (xem bảng hỗ trợ trình duyệt của thư viện) — lượt tải trên những trình duyệt đó chỉ thêm ít mẫu hơn.
Những gì rời khỏi trang#
Mỗi phép đo là một item Sentry log với body = "browser.web_vital" trên DSN trình duyệt của project, chỉ với các thuộc tính sau:
| Thuộc tính | Ví dụ | Ghi chú |
|---|---|---|
browser.web_vital.name, .value, .delta, .id, .rating | LCP, 2480 | Node tính lại xếp loại từ giá trị; xếp loại do trang gửi không được tin. |
url.path | /posts/:id | Mẫu route khi biết (xem route ở trên), nếu không thì chỉ đường dẫn: không origin, query hay fragment; số, UUID, chuỗi hex dài, token và địa chỉ e-mail trong một đoạn thành :id. Tối đa 200 ký tự. |
user_agent.original | Chrome 131 | Trình duyệt và phiên bản chính, không bao giờ là cả chuỗi user-agent. |
navigation.type | navigate, reload, back-forward | Do trình duyệt báo. |
sentry.environment, sentry.release | production, web@1.4.0 | environment và release bạn truyền vào init, để phép đo nằm cùng môi trường với lỗi của trang. |
browser.web_vital.attribution.target | main > img.hero | Phần tử LCP, đích tương tác INP hoặc phần tử xê dịch lớn nhất của CLS, dưới dạng bộ chọn CSS cắt còn 100 ký tự. |
/posts/123?token=abc#x được gửi thành /posts/:id; token=abc không bao giờ rời khỏi trang. Sau đó log đi qua cùng các quy tắc riêng tư như mọi log khác trên node (xem Quyền riêng tư) trước khi được lưu. LCP, FCP và TTFB dùng route lúc trang được tải; INP và CLS dùng route đang hiện khi trang bị ẩn, nên app một trang gán chúng cho đúng màn người dùng đang ở.
Chữ trong đường dẫn được giữ nguyên: không có mẫu route thì /w/acme/settings vẫn là /w/acme/settings, nên tên khách hàng, nhóm hay project trong đường dẫn sẽ được lưu cùng mỗi lần đo. Hãy gửi route thay vì đường dẫn:
TanStack Start / TanStack Router — với
createRouter(withBelvadorRouter({ routeTree, … })), từ bản 0.9.2 SDK lấy tên route từ cây route của bạn:/w/acme/settingsđược gửi thành/w/:slug/settings. Không cần đặt gì thêm.App khác — truyền
webVitals: { route: (path) => … }và trả về mẫu route, ví dụ từ route mà router của bạn đã khớp.
Khi hàm route không trả gì (không route nào khớp) hoặc ném lỗi, lần đo được gửi với đường dẫn /:unmatched — không bao giờ là đường dẫn đã truy cập.
Các phép đo của một lượt tải được gửi cùng nhau — một giây sau phép đo đầu, hoặc ngay khi đã có năm phép đo — và khi trang bị ẩn hoặc đóng thì gửi ngay bằng keepalive. Chúng không bao giờ được giữ lại để gửi sau: web vitals không dùng hàng đợi offline, vì một phép đo cũ kém giá trị hơn phép đo mới.
Kích thước và tốc độ#
Khi tắt, web vitals không thêm chút mã web-vitals nào: cổng phát hành kiểm rằng bundle chính không chứa thư viện. Khi bật, thư viện và phần mã web-vitals của SDK tới thành một tệp riêng khoảng 6 KB gzip, chỉ tải sau init ở những lượt tải được chọn mẫu. Việc đo không chặn trang: trình duyệt báo từng giá trị qua performance observer, SDK gửi một nhóm nhỏ sau một giây, hoặc gửi ngay khi trang bị ẩn.
Trên node của bạn#
Mỗi phép đo được giữ thành một log, và node còn cộng nó vào một metric histogram — browser.web_vital.lcp, .inp, .cls, .fcp, .ttfb — theo url.path và xếp loại. Mỗi project giữ tối đa 200 route thành series riêng (đếm trong 24 giờ); phép đo trên các route sau đó được tính vào route other, còn log của chúng vẫn giữ đường dẫn thật. Mỗi phép đo tính vào lượng log của project.
Metrics → Web vitals cho một project, môi trường và khoảng thời gian (tối đa 7 ngày) hiển thị:
p75 của LCP, INP và CLS kèm xếp loại, FCP và TTFB bên cạnh, và tỉ lệ mẫu tốt;
phân bố của một chỉ số theo từng khoảng, tô màu theo xếp loại;
các phần tử gây ra mẫu chưa tốt, nhiều nhất trước;
các route nhiều lượt nhất với p75 — chọn một route để chỉ xem số của nó.
Dưới 100 mẫu, p75 được ghi ước lượng: khi ít mẫu, một lượt chậm cũng làm nó thay đổi nhiều.
Cảnh báo#
Đây là metric bình thường, nên một monitor metric có thể theo dõi — ví dụ browser.web_vital.lcp, phép gộp p75, lọc url.path = /checkout, trên 2500 trong 15 phút. Nút Tạo monitor ở tab Web vitals điền sẵn chỉ số và route cho bạn.
API và AI agent#
Cùng bản tóm tắt này có qua REST API với token metrics:read:
curl -s "https://api.belvador.dev/v1/projects/<project-id>/web-vitals?environment=production&path=/posts/:id" -H "Authorization: Bearer $BELVADOR_TOKEN"AI agent đọc được các histogram bằng công cụ MCP query_metrics (phép gộp p75). Coverage có thể kỳ vọng web vitals từ bề mặt trình duyệt; ô chuyển sang đã xác minh khi node đã lưu một phép đo trong môi trường đó.
Tắt web vitals#
Bỏ webVitals (hoặc đặt sampleRate: 0) rồi triển khai. Các phép đo đã lưu ở lại cho tới khi thời hạn giữ log và metric của project xoá chúng.
Thấy chỗ sai hoặc thiếu? Gửi cho chúng tôi: hello@belvador.dev