页面加载慢的排查与优化
场景
用户反馈或者监控告警提示某个页面加载很慢,你会怎么排查和优化?
分析思路
第一步:量化问题 — 到底有多慢?
在做任何优化之前,先用数据定义"慢":
核心指标:
| 指标 | 含义 | 好 | 需改进 | 差 |
|---|---|---|---|---|
| FCP | 首次内容绘制 | < 1.8s | 1.8~3s | > 3s |
| LCP | 最大内容绘制 | < 2.5s | 2.5~4s | > 4s |
| TTI | 可交互时间 | < 3.8s | 3.8~7.3s | > 7.3s |
| TBT | 总阻塞时间 | < 200ms | 200~600ms | > 600ms |
数据来源:
// 1. 使用 Performance API 采集真实用户数据
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(`${entry.name}: ${entry.startTime.toFixed(0)}ms`);
}
});
observer.observe({ type: 'largest-contentful-paint', buffered: true });
// 2. Navigation Timing
const navEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
const metrics = {
dns: navEntry.domainLookupEnd - navEntry.domainLookupStart,
tcp: navEntry.connectEnd - navEntry.connectStart,
ttfb: navEntry.responseStart - navEntry.requestStart,
download: navEntry.responseEnd - navEntry.responseStart,
domParse: navEntry.domInteractive - navEntry.responseEnd,
domReady: navEntry.domContentLoadedEventEnd - navEntry.navigationStart,
load: navEntry.loadEventEnd - navEntry.navigationStart,
};
第二步:定位瓶颈 — 慢在哪里?
使用 Chrome DevTools 和 Lighthouse 系统排查:
2.1 Network 面板 — 资源加载分析
打开 DevTools → Network 面板 → 刷新页面
重点关注:
- 瀑布图(Waterfall):看资源加载的时序和阻塞关系
- TTFB(Time to First Byte):如果 > 600ms,可能是服务端问题
- 资源大小:排序看哪些资源过大(JS > 200KB、图片 > 100KB 需关注)
- 请求数量:过多的小文件会导致队头阻塞(HTTP/1.1)
// 快速统计资源分布
const resources = performance.getEntriesByType('resource') as PerformanceResourceTiming[];
const summary = resources.reduce((acc, r) => {
const type = r.initiatorType;
if (!acc[type]) acc[type] = { count: 0, size: 0 };
acc[type].count++;
acc[type].size += r.transferSize;
return acc;
}, {} as Record<string, { count: number; size: number }>);
console.table(summary);
2.2 Performance 面板 — 运行时分析
打开 DevTools → Performance → 勾选 Screenshots → 录制页面加载
重点关注:
- Main 线程:长任务(黄色三角标记,> 50ms)
- Network 行:请求时序
- Frames 行:帧率和截图,定位视觉渲染节点
- Timings 行:FP、FCP、LCP、DCL 等关键时间点
2.3 Lighthouse — 综合评分
# 命令行运行
npx lighthouse https://example.com --view
# 或在 DevTools → Lighthouse 面板
Lighthouse 会给出具体的优化建议和预估提升。
第三步:常见瓶颈与优化方案
瓶颈 1:JS 包过大
路由级代码分割(React)
import { lazy, Suspense } from 'react';
// 路由级别懒加载
const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
按需导入(减少 bundle 体积)
// ❌ 全量导入 — 打包整个 lodash
import _ from 'lodash';
// ✅ 按需导入 — 只打包用到的函数
import debounce from 'lodash/debounce';
// ✅ 使用 lodash-es + Tree Shaking
import { debounce } from 'lodash-es';
瓶颈 2:图片资源过大
响应式图片 + 现代格式
// Next.js Image 组件自动优化
import Image from 'next/image';
<Image
src="/hero.jpg"
alt="Hero"
width={1200}
height={600}
priority // LCP 图片,预加载
sizes="(max-width: 768px) 100vw, 1200px"
/>
原生 HTML 方案
<picture>
<source srcset="hero.avif" type="image/avif">
<source srcset="hero.webp" type="image/webp">
<img src="hero.jpg" alt="Hero"
loading="lazy"
decoding="async"
width="1200" height="600">
</picture>
瓶颈 3:渲染阻塞资源
优化脚本加载
<!-- 关键 CSS 内联 -->
<style>
/* 首屏关键样式 */
.hero { ... }
</style>
<!-- 非关键 CSS 异步加载 -->
<link rel="preload" href="non-critical.css" as="style"
onload="this.onload=null;this.rel='stylesheet'">
<!-- JS 使用 defer/async -->
<script src="analytics.js" defer></script>
<script src="vendor.js" async></script>
瓶颈 4:TTFB 过高(服务端响应慢)
| 原因 | 解决方案 |
|---|---|
| 数据库查询慢 | 加索引、缓存、读写分离 |
| 后端逻辑复杂 | 异步处理、缓存计算结果 |
| 地理距离远 | CDN、边缘计算 |
| 服务器性能不足 | 扩容、负载均衡 |
前端层面缓解 TTFB
// 1. DNS 预解析
// <link rel="dns-prefetch" href="//api.example.com">
// 2. 预连接
// <link rel="preconnect" href="https://api.example.com" crossorigin>
// 3. 预获取下一页资源
// <link rel="prefetch" href="/next-page.js">
// 4. SSG / ISR(Next.js)
export async function getStaticProps() {
const data = await fetchData();
return {
props: { data },
revalidate: 60, // ISR: 60 秒重新生成
};
}
瓶颈 5:第三方脚本阻塞
延迟加载第三方脚本
// 页面可交互后再加载非关键第三方脚本
function loadThirdPartyScripts() {
const scripts = [
'https://www.googletagmanager.com/gtag/js?id=GA_ID',
'https://cdn.example.com/chat-widget.js',
];
scripts.forEach((src) => {
const script = document.createElement('script');
script.src = src;
script.async = true;
document.body.appendChild(script);
});
}
// 使用 requestIdleCallback 在空闲时加载
if ('requestIdleCallback' in window) {
requestIdleCallback(loadThirdPartyScripts);
} else {
setTimeout(loadThirdPartyScripts, 3000);
}
第四步:验证效果
性能监控对比
// 优化前后的数据对比
interface PerformanceReport {
page: string;
before: { lcp: number; fcp: number; tti: number; bundleSize: string };
after: { lcp: number; fcp: number; tti: number; bundleSize: string };
}
const report: PerformanceReport = {
page: '/dashboard',
before: { lcp: 4200, fcp: 2800, tti: 6500, bundleSize: '1.2MB' },
after: { lcp: 1800, fcp: 1200, tti: 3200, bundleSize: '380KB' },
};
排查清单速查
- Network 面板 → 看资源大小、TTFB、阻塞
- Performance 面板 → 看长任务、帧率
- Lighthouse → 看评分和建议
- Coverage 面板 → 看未使用的 JS/CSS 比例
- Bundle Analyzer → 看打包产物组成
常见面试问题
Q1: 你一般怎么排查页面加载慢的问题?
答案:
按照"量化 → 定位 → 优化 → 验证"四步走:
- 量化问题:通过 Lighthouse、Performance API、监控平台采集 LCP/FCP/TTI 等指标
- 定位瓶颈:
- Network 面板看资源加载瀑布图,确认是网络慢还是资源大
- Performance 面板看 Main 线程,确认是否有长任务阻塞
- Coverage 面板看未使用代码比例
- 制定方案:根据瓶颈选择对应优化手段(代码分割、图片优化、SSR 等)
- 验证效果:优化前后对比核心指标,确认达标
Q2: TTFB 很高你会怎么处理?
答案:
TTFB(Time to First Byte)高说明服务端响应慢,可能原因和对策:
| 原因 | 排查方式 | 优化手段 |
|---|---|---|
| 服务端处理慢 | 后端日志、APM 工具 | 加缓存、优化查询、异步处理 |
| 网络延迟 | ping、traceroute | CDN、就近部署 |
| SSL 握手 | 网络面板看 TLS 耗时 | TLS 1.3、OCSP Stapling |
| DNS 解析慢 | 网络面板看 DNS | DNS 预解析、更换 DNS 服务 |
前端能做的:dns-prefetch、preconnect、Service Worker 缓存、SSG/ISR 等。
Q3: 如何判断是网络问题还是代码问题?
答案:
- 网络问题:TTFB 高、资源下载时间长 → 开 Network Throttling 对比、换网络环境测试
- 代码问题:资源下载完后到渲染/可交互之间间隔长 → Performance 面板看 JS 执行时间
- 快速区分:用
performance.timing对比responseEnd(网络结束)和domInteractive(解析完成),差值大说明是 JS 解析/执行太重