跳到主要内容

页面加载慢的排查与优化

场景

用户反馈或者监控告警提示某个页面加载很慢,你会怎么排查和优化?

分析思路

第一步:量化问题 — 到底有多慢?

在做任何优化之前,先用数据定义"慢":

核心指标:

指标含义需改进
FCP首次内容绘制< 1.8s1.8~3s> 3s
LCP最大内容绘制< 2.5s2.5~4s> 4s
TTI可交互时间< 3.8s3.8~7.3s> 7.3s
TBT总阻塞时间< 200ms200~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' },
};
排查清单速查
  1. Network 面板 → 看资源大小、TTFB、阻塞
  2. Performance 面板 → 看长任务、帧率
  3. Lighthouse → 看评分和建议
  4. Coverage 面板 → 看未使用的 JS/CSS 比例
  5. Bundle Analyzer → 看打包产物组成

常见面试问题

Q1: 你一般怎么排查页面加载慢的问题?

答案

按照"量化 → 定位 → 优化 → 验证"四步走:

  1. 量化问题:通过 Lighthouse、Performance API、监控平台采集 LCP/FCP/TTI 等指标
  2. 定位瓶颈
    • Network 面板看资源加载瀑布图,确认是网络慢还是资源大
    • Performance 面板看 Main 线程,确认是否有长任务阻塞
    • Coverage 面板看未使用代码比例
  3. 制定方案:根据瓶颈选择对应优化手段(代码分割、图片优化、SSR 等)
  4. 验证效果:优化前后对比核心指标,确认达标

Q2: TTFB 很高你会怎么处理?

答案

TTFB(Time to First Byte)高说明服务端响应慢,可能原因和对策:

原因排查方式优化手段
服务端处理慢后端日志、APM 工具加缓存、优化查询、异步处理
网络延迟ping、tracerouteCDN、就近部署
SSL 握手网络面板看 TLS 耗时TLS 1.3、OCSP Stapling
DNS 解析慢网络面板看 DNSDNS 预解析、更换 DNS 服务

前端能做的:dns-prefetchpreconnect、Service Worker 缓存、SSG/ISR 等。

Q3: 如何判断是网络问题还是代码问题?

答案

  • 网络问题:TTFB 高、资源下载时间长 → 开 Network Throttling 对比、换网络环境测试
  • 代码问题:资源下载完后到渲染/可交互之间间隔长 → Performance 面板看 JS 执行时间
  • 快速区分:用 performance.timing 对比 responseEnd(网络结束)和 domInteractive(解析完成),差值大说明是 JS 解析/执行太重

相关链接