做性能优化最怕的一件事,就是只知道页面慢,却不知道慢在哪一段。用户抱怨页面打开要五六秒,你盯着自己的服务器看半天,响应时间明明只有几十毫秒,问题到底出在哪?答案往往藏在浏览器端。从用户敲下回车到页面渲染完成,中间经历了重定向、DNS解析、TCP握手、TLS协商、请求等待、内容下载、DOM解析、资源加载等一系列环节,任何一段出现异常都会拖慢整体体验。Navigation Timing正是浏览器提供的一套原生计时接口,它把这些环节的时间戳完整记录下来,配合CDN的链路数据,就能把性能问题定位到具体阶段。

Navigation Timing的核心字段与阶段划分
Navigation Timing接口经历过两个版本,目前主流的是Level 2规范,数据通过performance.getEntriesByType('navigation')获取。它返回一个PerformanceNavigationTiming对象,里面包含十几个关键时间戳属性,理解这些属性是做一切分析的基础。
// 获取导航计时数据
const [nav] = performance.getEntriesByType('navigation');
console.log(nav);
// 计算各阶段耗时(单位:毫秒)
const stages = {
// 重定向阶段:从上一页面卸载完成到重定向结束
redirect: nav.redirectEnd - nav.redirectStart,
// DNS解析阶段:域名解析为IP地址
dns: nav.domainLookupEnd - nav.domainLookupStart,
// TCP连接阶段:与服务器建立连接
tcp: nav.connectEnd - nav.connectStart,
// TLS握手阶段:包含在TCP阶段内,可单独拆出
tls: nav.secureConnectionStart > 0 ? nav.connectEnd - nav.secureConnectionStart : 0,
// 请求响应阶段:发出请求到收到首字节
ttfb: nav.responseStart - nav.requestStart,
// 内容下载阶段:接收响应体
download: nav.responseEnd - nav.responseStart,
// DOM解析阶段:构建DOM树
domParse: nav.domInteractive - nav.responseEnd,
// 页面完全加载
fullLoad: nav.loadEventStart - nav.startTime
};
console.table(stages);这些时间戳之间存在明确的先后顺序:startTime(即fetchStart之前)到redirectEnd是重定向,domainLookupStart到domainLookupEnd是DNS解析,以此类推。需要特别注意的是,某些阶段的起止时间戳可能相等,比如命中了浏览器DNS缓存时,DNS阶段耗时会是0;使用了HTTP连接复用时,TCP阶段也可能接近0。这些等于零的阶段恰恰说明浏览器或者中间层做了缓存优化,反而是健康的表现。
另外还有一个容易被忽略的字段是nextHopProtocol,它记录了实际使用的协议,比如h2、h3。如果你的CDN已经支持HTTP/3,但这里显示的还是h2,说明浏览器并没有真正走到QUIC链路上,可能是CDN配置没开或者中间网络设备拦截了UDP流量,这类问题不看Navigation Timing数据是根本发现不了的。
CDN场景下各阶段耗用的典型特征
接入CDN之后,页面加载的时间分布会发生明显变化,理解这些变化才能正确解读数据。首先是DNS阶段。CDN通常使用智能调度,域名可能解析到一个CNAME链,比如先指向调度域名再指向边缘节点IP,理论上解析会更慢。但成熟的CDN商会通过DNS预热、TTL控制和Anycast技术把这一段控制在很短时间内。如果你观测到DNS阶段普遍超过100毫秒,多半是用户的Local DNS配置了较长的CNAME跳转链,或者运营商递归DNS性能差,可以考虑启用HTTPDNS方案绕过。
其次是TCP和TLS阶段,这是CDN最应该体现价值的环节。用户请求的是离自己最近的边缘节点,RTT应该明显低于直连源站。以国内场景为例,用户到边缘节点的往返延迟通常在10到30毫秒,TCP握手加上TLS 1.3的1-RTT建连,整体连接时间应该控制在50毫秒以内。如果TTFB(首字节时间)很高但TCP阶段正常,说明瓶颈在CDN节点内部处理或者回源环节,可能是边缘节点缓存未命中,请求被转发回了源站。这时可以对比responseStart减去requestStart的值与CDN日志里的回源标记,判断是命中了缓存还是穿透了。
还有一个典型问题是重定向阶段。有些站点习惯把http强制302到https,或者把裸域重定向到www子域,用户第一次访问时就要经历完整的两次建连流程。redirectStart到redirectEnd这段时间包含了两次完整的DNS加TCP加TLS过程,非常昂贵。正确的做法是在DNS层直接解析到最终域名,或者在CDN边缘节点配置301并开启HSTS,让浏览器后续访问直接跳过重定向。
如何采集数据并落地到监控体系
单看一个用户的数据意义不大,性能分析需要的是统计视角。采集方案一般是前端在页面加载完成后读取Navigation Timing数据,组装成指标上报到日志系统。上报时机建议放在load事件里或者用PerformanceObserver监听,避免阻塞页面。
// 使用PerformanceObserver监听导航条目
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
// 只上报关键指标,减少日志量
const payload = {
url: location.pathname,
ttfb: Math.round(entry.responseStart - entry.requestStart),
dns: Math.round(entry.domainLookupEnd - entry.domainLookupStart),
tcp: Math.round(entry.connectEnd - entry.connectStart),
domReady: Math.round(entry.domContentLoadedEventEnd - entry.startTime),
fullLoad: Math.round(entry.loadEventStart - entry.startTime),
protocol: entry.nextHopProtocol
};
// 通过sendBeacon上报,页面关闭也不会丢失
navigator.sendBeacon('/perf-report', JSON.stringify(payload));
}
});
observer.observe({ type: 'navigation', buffered: true });数据落库之后,不要只看平均值。平均值会被极端值拉偏,一个10秒的超时请求足以让整体数据失真。更合理的做法是看分位数,重点关注P75和P90,因为用户感知到的卡顿往往来自长尾部分。可以按地域、运营商、URL维度做交叉分组,比如发现某个省份移动用户的TTFB中位数明显偏高,就能定位到是那一带CDN节点覆盖不足或者调度不准的问题,再针对性地增补节点或调整调度策略。
最后一点建议是把浏览器端数据和CDN侧数据打通。浏览器端的Navigation Timing只能看到用户到边缘节点的表现,而回源耗时、边缘节点内部处理时间需要CDN日志或API来补充。两边数据通过请求ID或时间窗口关联起来,才能形成从用户点击到源站响应的完整链路视图,这也是排查那些间歇性慢请求的关键手段。建立起这套体系之后,页面性能问题就不再是模糊的感觉,而是一组可以逐段拆解、逐段优化的明确数字。
CDNNavigation Timing页面性能优化修改时间:2026-09-12 16:18:36