Web 页面性能与用户体验分析¶
Web RUM SDK 会把真实用户访问页面时的加载、渲染、交互、布局变化、错误、资源和长任务数据关联到同一个 view_id。应用数据上报到观测云后,可以先用 Core Web Vitals 判断整体体验,再通过页面、版本、浏览器、设备和网络等维度定位受影响范围,最后沿 view_id 下钻错误、资源、长任务和会话重放。
本文所有字段名均来自 Web RUM SDK 当前上报的数据结构。完整字段清单参见 Web 应用数据采集。
分析前先明确统计口径¶
区分首次加载和路由切换¶
view_loading_type 用来区分两类 View:
initial_load:浏览器首次打开或刷新页面;route_change:单页应用(SPA)发生路由切换。
当前 SDK 只在 initial_load View 上采集 first_byte、first_contentful_paint、largest_contentful_paint、first_input_delay 和导航时序字段。loading_time、interaction_to_next_paint、cumulative_layout_shift 及 View 关联计数同时适用于 initial_load 和 route_change。
因此:
- 分析首屏、白屏候选、FCP、LCP、
TTFB时,必须筛选view_loading_type = "initial_load"; - 分析 SPA 路由性能时,优先使用
loading_time、interaction_to_next_paint、cumulative_layout_shift、错误和长任务等字段; - 不要把
route_change中缺少 FCP 或 LCP 的记录判定为异常。
使用 P75,而不是只看平均值¶
Core Web Vitals 建议使用页面访问样本的第 75 百分位数(P75)进行评估,并分别观察移动端和桌面端。P75 达标表示至少 75% 的页面访问处于目标范围。平均值容易被大量快速样本稀释,不能代表慢用户的真实体验。
建议同时观察:
- P50:典型用户体验;
- P75:Core Web Vitals 主判断口径;
- P90 或 P95:慢网络、低性能设备等尾部用户体验;
- 达标率:处于“良好”阈值内的 View 数占有效样本数的比例;
- 样本量:避免依据少量样本判断页面或版本质量。
指标定义、阈值和 P75 统计方式参考 web.dev:Web Vitals 和 真实环境 Web Vitals 统计最佳实践。
注意单位¶
- 除
cumulative_layout_shift外,本文列出的时间指标在数据中均以纳秒(ns)存储; 1s = 1,000,000,000ns,1ms = 1,000,000ns;cumulative_layout_shift是无单位分数,不要做时间换算;- DQL 阈值必须使用原始存储单位。例如 LCP 2.5 秒应写成
2500000000。
核心体验指标¶
Core Web Vitals¶
| 体验维度 | SDK 字段 | 有效 View | 良好 | 需要改进 | 较差 | 定位字段 |
|---|---|---|---|---|---|---|
| 加载性能(LCP) | largest_contentful_paint |
initial_load |
≤ 2.5s | > 2.5s 且 ≤ 4s | > 4s | largest_contentful_paint_element_selector |
| 交互响应(INP) | interaction_to_next_paint |
initial_load、route_change |
≤ 200ms | > 200ms 且 ≤ 500ms | > 500ms | interaction_to_next_paint_target_selector |
| 视觉稳定(CLS) | cumulative_layout_shift |
initial_load、route_change |
≤ 0.1 | > 0.1 且 ≤ 0.25 | > 0.25 | cumulative_layout_shift_target_selector |
LCP、INP、CLS 的阈值分别参考 LCP、INP 和 CLS。
LCP:用户何时看到主要内容¶
largest_contentful_paint 表示视口内最大图片、文本块或视频完成绘制的时间。SDK 同时上报 largest_contentful_paint_element_selector,用于定位产生 LCP 的页面元素。
LCP 较差时,结合以下字段判断时间消耗发生在哪一段:
first_byte较高:优先检查服务端响应、重定向、CDN、地域和网络;first_byte正常但first_contentful_paint较高:优先检查阻塞渲染的 CSS、JavaScript 和客户端初始化;first_contentful_paint正常但largest_contentful_paint较高:优先检查 LCP 元素对应资源是否发现过晚、下载过慢或渲染延迟;loading_time仍明显高于 LCP:页面主要内容已可见,但仍有网络请求或 DOM 变动持续发生。
INP:页面是否能及时响应操作¶
interaction_to_next_paint 衡量一次交互从输入、事件处理到下一帧绘制的完整延迟。SDK 在一个 View 内持续观察交互并报告接近最差交互的值,同时通过 interaction_to_next_paint_target_selector 标识对应元素。
INP 较差时,重点关联:
view_long_task_count:页面是否存在阻塞主线程超过 50ms 的任务;view_error_count:交互期间是否发生脚本错误;frustration_count:是否出现重复点击、无效点击或错误点击等挫败行为;R::long_task中的duration、blocking_duration和scripts:定位阻塞时间和相关脚本。
没有用户交互的 View 可能没有 interaction_to_next_paint,统计 INP 时只使用该字段存在的样本。
CLS:页面内容是否意外移动¶
cumulative_layout_shift 衡量页面可见元素的意外位移,cumulative_layout_shift_target_selector 指向造成最大影响的元素。
CLS 较差时,常见检查方向包括:
- 图片、视频、广告或
iframe未预留宽高; - 异步内容插入到已有内容上方;
- Web 字体切换导致文本尺寸变化;
- 动画通过会触发布局的属性实现。
首次加载辅助指标¶
| SDK 字段 | 含义 | 建议用途 |
|---|---|---|
first_byte |
从导航开始到收到文档响应首字节 | 判断服务端、网络、重定向或 CDN 是否拖慢后续渲染 |
first_contentful_paint |
首个文本、图片、SVG 或非白色 Canvas 绘制时间 | 衡量用户何时第一次看到内容,也是白屏候选分析的主要字段 |
dom_interactive |
文档解析器完成解析的时间点 | 判断 HTML 和同步脚本是否延迟 DOM 可交互状态 |
dom_content_loaded |
DOMContentLoaded 完成时间点 |
判断 DOM 构建和延迟脚本完成时间 |
dom_complete |
页面和子资源完成后的时间点 | 判断完整文档生命周期是否过长 |
load_event |
load 事件完成时间点 |
判断依赖资源全部加载完成的时间 |
first_paint_time |
当前 SDK 计算的 responseEnd - fetchStart |
观察文档响应阶段耗时;不要把它等同于 FCP |
time_to_interactive |
当前 SDK 计算的 domInteractive - fetchStart |
观察 DOM 到达可交互状态的时间;不要与实验室工具的 TTI 混用 |
dom_ready |
当前 SDK 计算的 domContentLoadedEventEnd - fetchStart |
观察 DOM Ready 完成耗时 |
resource_load_time |
当前 SDK 计算的 loadEventStart - domContentLoadedEventEnd |
观察 DOM Ready 之后等待依赖资源的耗时 |
dom |
当前 SDK 计算的 domComplete - domInteractive |
观察 DOM 可交互之后到完整完成的耗时 |
FCP 可参考 web.dev:FCP:良好为不超过 1.8 秒,较差为超过 3 秒。first_byte 可参考 web.dev:首字节时间:0.8 秒以内通常较好,超过 1.8 秒通常较差;TTFB 不是 Core Web Vitals,应结合 FCP 和 LCP 判断。
页面加载与使用体验指标¶
| SDK 字段 | 含义 | 适合回答的问题 |
|---|---|---|
loading_time |
SDK 综合 load 事件、网络请求和 DOM 活动得到的页面稳定耗时 |
页面何时结束主要加载活动;SPA 路由切换是否持续请求或更新 DOM |
time_spent |
用户在当前 View 的停留时间 | 慢页面是否导致快速离开;异常是否影响长时间使用 |
view_error_count |
当前 View 关联的错误数 | 页面是否因脚本异常不可用 |
view_resource_count |
当前 View 关联的资源请求数 | 页面是否请求过多;是否需要继续分析资源瀑布 |
view_long_task_count |
当前 View 关联的长任务数 | 主线程是否频繁被阻塞 |
view_action_count |
当前 View 关联的用户操作数 | 页面是否真正被使用;INP 样本缺失是否因为没有交互 |
frustration_count |
当前 View 关联的挫败行为数量 | 用户是否因无响应、无结果或错误而重复操作 |
is_active |
View 是否仍处于活跃状态 | 过滤尚未结束、指标仍可能继续更新的 View |
session_has_replay |
当前会话是否有关联会话重放 | 是否可以直接回放异常用户现场 |
loading_time 不等同于视觉完成时间。页面可以先完成 LCP,但后台轮询、接口请求或持续 DOM 更新会让 loading_time 更长;反之,loading_time 正常也不能证明页面主要内容已经正确显示。
建立页面性能基线¶
第一步:看每个页面的 P75¶
下面的查询面向仪表板图表,故意不在 DQL 中写 [24h]、[1h] 等固定时间范围。省略时间范围后,查询会跟随仪表板右上方的时间控件;如果在 DQL 中直接指定时间范围,其优先级高于仪表板时间控件,会使图表固定查询该时间段。更多说明参见 DQL 时间范围指定。将 <APP_ID> 替换为应用 ID 即可。
按页面查看 LCP P75:
R::view:(percentile(largest_contentful_paint, 75) AS p75_lcp) {app_id = "<APP_ID>", view_loading_type = "initial_load", largest_contentful_paint = exists()} BY view_path_group SORDER BY p75_lcp DESC SLIMIT 20
按页面查看 INP P75:
R::view:(percentile(interaction_to_next_paint, 75) AS p75_inp) {app_id = "<APP_ID>", interaction_to_next_paint = exists()} BY view_path_group SORDER BY p75_inp DESC SLIMIT 20
按页面查看 CLS P75:
R::view:(percentile(cumulative_layout_shift, 75) AS p75_cls) {app_id = "<APP_ID>", cumulative_layout_shift = exists()} BY view_path_group SORDER BY p75_cls DESC SLIMIT 20
不要直接把三个指标相加形成一个分数。LCP、INP、CLS 分别代表加载、响应和稳定性,任一项较差都需要单独定位。
第二步:观察达标率¶
下面的查询计算 LCP 不超过 2.5 秒的有效 View 占比:
eval(100 * A / B, alias='LCP_good_rate', A="R::view:(count(view_id)) {app_id = '<APP_ID>', view_loading_type = 'initial_load', largest_contentful_paint <= 2500000000}", B="R::view:(count(view_id)) {app_id = '<APP_ID>', view_loading_type = 'initial_load', largest_contentful_paint = exists()}")
同理可将条件替换为:
- INP 达标:
interaction_to_next_paint <= 200000000; - CLS 达标:
cumulative_layout_shift <= 0.1; - FCP 达标:
first_contentful_paint <= 1800000000。
分母必须只统计指标存在的有效样本,否则不支持该指标的浏览器、无交互 View 或过早离开的 View 会扭曲结果。
第三步:按关键维度拆分¶
建议依次使用以下 SDK 字段分组或筛选:
view_path_group:先找出受影响页面;version:判断是否为新版本回归;browser、browser_version_major:判断浏览器兼容性或能力差异;device、screen_size:区分设备性能和布局差异;network_type:区分 Wi-Fi、蜂窝网络和不可达网络;env、service:避免混合不同环境或服务的数据。
例如,按版本和页面比较 Core Web Vitals:
R::view:(percentile(largest_contentful_paint, 75) AS p75_lcp, percentile(interaction_to_next_paint, 75) AS p75_inp, percentile(cumulative_layout_shift, 75) AS p75_cls) {app_id = "<APP_ID>"} BY version, view_path_group SORDER BY p75_lcp DESC SLIMIT 50
由于 LCP 仅存在于 initial_load,而 INP 可能因没有交互而缺失,生产看板中更建议为三个指标分别创建图表,再使用相同的页面、版本和设备筛选条件联动分析。
白屏与长时间无内容分析¶
白屏不是 SDK 的直接指标¶
当前 SDK 不上报名为 white_screen 的字段。白屏只能通过已采集指标构造候选集合,再结合错误、资源、长任务和会话重放确认。
推荐将候选分为两级:
- 慢 FCP 候选:
first_contentful_paint > 3000000000。表示用户超过 3 秒才看到首个内容,是相对稳定、可统计的白屏体验近似; - FCP 缺失候选:已结束的首次加载 View 停留超过 3 秒,但
first_contentful_paint不存在。该规则只能用于排查,不能直接等同于白屏。
FCP 缺失还可能由以下情况造成:
- 当前浏览器不支持 SDK 使用的 FCP 采集能力;
- 页面在 FCP 产生前进入后台或被隐藏;
- 用户提前关闭页面;
- 数据采样、网络上报或页面生命周期提前结束;
- 错误地把
route_change纳入分析。
因此,FCP 缺失规则必须筛选 view_loading_type = "initial_load",并按 browser 验证采集覆盖率。浏览器能力边界参见 Browser Support。
统计慢 FCP 候选比例¶
eval(100 * A / B, alias='white_screen_candidate_rate', A="R::view:(count(view_id)) {app_id = '<APP_ID>', view_loading_type = 'initial_load', first_contentful_paint > 3000000000}", B="R::view:(count(view_id)) {app_id = '<APP_ID>', view_loading_type = 'initial_load', first_contentful_paint = exists()}")
该比例表示“有效 FCP 样本中超过 3 秒才看到内容”的比例,不表示经过视觉确认的真实白屏率。建议再按 view_path_group、version、browser 和 network_type 拆分。
找出慢 FCP View¶
R::view:(view_id, session_id, view_url, first_contentful_paint, first_byte, loading_time, view_error_count, view_resource_count, view_long_task_count, session_has_replay) {app_id = "<APP_ID>", view_loading_type = "initial_load", first_contentful_paint > 3000000000} ORDER BY first_contentful_paint DESC LIMIT 100
结果解释:
first_byte同时很高:优先检查服务端、CDN、网络和重定向;first_byte正常但 FCP 很高:优先检查阻塞渲染资源、客户端渲染和同步 JavaScript;view_error_count > 0:优先查看错误堆栈,确认初始化是否中断;view_long_task_count > 0:检查主线程是否在首次绘制前被长任务阻塞;session_has_replay = true:使用session_id或view_id打开会话重放确认用户实际看到的画面。
找出 FCP 缺失候选¶
R::view:(view_id, session_id, view_url, time_spent, view_error_count, view_resource_count, view_long_task_count, session_has_replay) {app_id = "<APP_ID>", view_loading_type = "initial_load", is_active = false, time_spent > 3000000000, first_contentful_paint != exists()} ORDER BY time_spent DESC LIMIT 100
该结果应先按 browser 排除不支持 FCP 的样本,再结合会话重放和下钻查询确认。不能仅根据 FCP 缺失创建“真实白屏率”告警。
沿 view_id 下钻根因¶
将候选结果中的 <VIEW_ID> 带入下列查询。
查看脚本和运行时错误:
R::error:(error_type, error_source, error_message, error_stack) {app_id = "<APP_ID>", view_id = "<VIEW_ID>"} ORDER BY time ASC LIMIT 100
查看失败或缓慢资源:
R::resource:(resource_url, resource_type, resource_status, duration, resource_ttfb, resource_trans) {app_id = "<APP_ID>", view_id = "<VIEW_ID>", resource_status >= 400} ORDER BY duration DESC LIMIT 100
如果没有 HTTP 错误,但仍怀疑资源过慢,可以移除 resource_status >= 400,再按 duration 查看最慢资源。resource_ttfb 较高通常指向服务端或网络等待,resource_trans 较高通常指向内容传输时间。
查看长任务:
R::long_task:(duration, blocking_duration, scripts) {app_id = "<APP_ID>", view_id = "<VIEW_ID>"} ORDER BY duration DESC LIMIT 100
duration 表示长任务总时长,blocking_duration 表示其中阻塞用户输入的时间,scripts 可用于定位相关脚本。
常见体验问题的判断路径¶
| 现象 | 先看字段 | 继续关联 | 常见方向 |
|---|---|---|---|
| 页面迟迟没有内容 | first_contentful_paint、first_byte |
view_error_count、view_long_task_count、Resource |
服务端慢、渲染阻塞、初始化错误、长任务 |
| 主要内容加载慢 | largest_contentful_paint |
largest_contentful_paint_element_selector、Resource |
LCP 资源发现晚、下载慢、渲染延迟 |
| 点击后无响应 | interaction_to_next_paint |
interaction_to_next_paint_target_selector、Long Task、Error |
主线程阻塞、事件处理过重、脚本错误 |
| 页面内容跳动 | cumulative_layout_shift |
cumulative_layout_shift_target_selector |
未预留尺寸、动态插入、字体和动画 |
| 页面一直显示加载中 | loading_time |
Resource、DOM 活动、Error | 请求未结束、轮询、持续 DOM 更新、异常状态未收敛 |
| 用户反复点击或点击无结果 | frustration_count |
Action、INP、Error、会话重放 | 无视觉反馈、无效点击、错误点击、慢交互 |
| 新版本体验下降 | version + P75 |
view_path_group、browser、network_type |
资源体积、代码路径、兼容性或发布回归 |
看板与告警建议¶
建议至少建立以下视图:
- LCP、INP、CLS 的 P75 趋势和达标率;
- FCP、
first_byte、loading_time的 P75 趋势; - 按
view_path_group排序的慢页面 Top 20; - 按
version对比的发布前后趋势; - 按
browser、device、network_type和地域拆分的尾部体验; view_error_count、view_long_task_count、frustration_count与性能指标的关联图;- 慢 FCP 候选比例和可回放样本列表。
告警应优先使用 P75 或达标率,并设置最小样本量和连续触发条件,避免少量异常样本或短时间流量波动造成误报。建议从 Core Web Vitals 的“较差”阈值开始,再根据业务页面的重要程度、历史基线和实际流量调整。
在控制台中完成分析¶
- 在「用户访问监测 > 分析看板 > Web 端」查看概览、页面性能、资源和错误分析;
- 在「用户访问监测 > 查看器」通过
view_id、session_id、页面、版本和浏览器筛选原始 View; - 进入单个 View 后,关联查看 Resource、Error、Long Task、Action 和会话重放;
- 需要自定义统计时,使用本文 DQL 创建仪表板图表或用户访问指标检测。