写给想让网站飞起来的开发者,不讲废话,直接上干货
先说一个让人抓狂的场景
你辛辛苦苦开发了一个网站,兴冲冲地发给朋友测试。
朋友说:“点开要等好几秒,点个按钮还要转圈圈,体验太差了。”
你一看数据:
- 首屏加载时间:8 秒(人家要求 3 秒以内)
- LCP(最大内容绘制):6 秒(Google 要求 2.5 秒以内)
- FID(首次输入延迟):300ms(人家要求 100ms 以内)
你慌了,开始到处搜"怎么让网站变快",搜到一堆专业术语:Tree Shaking、Code Splitting、Lazy Loading、CDN 加速…
看完更懵了,不知道从哪下手。
别慌,这篇文章帮你把性能优化讲清楚,让你知道该从哪下手。
性能优化到底优化什么?
| 指标 | 名字 | 达标标准 | 体验感 |
|---|---|---|---|
| FCP | First Contentful Paint | < 1.8s | 看到第一个内容 |
| LCP | Largest Contentful Paint | < 2.5s | 看到主要内容 |
| FID | First Input Delay | < 100ms | 第一次能交互 |
| CLS | Cumulative Layout Shift | < 0.1 | 页面不乱跳 |
| TTI | Time to Interactive | < 3.8s | 完全可交互 |
打开 Chrome,按 F12,切到 Lighthouse 标签,点"Analyze Page Load",它会给你打个分,顺便告诉你哪里有问题。
优化思路:三板斧
1. 少加载东西(体积)
2. 加载快一点(速度)
3. 边用边加载(顺序)
第一斧:少加载东西(体积优化)
1. 图片压缩——最大的罪魁祸首
网页加载慢,90% 是图片的问题。
// 用 sharp 压缩图片(Node.js)
import sharp from 'sharp';
await sharp('original.png')
.resize(1920, 1080)
.webp({ quality: 80 })
.toFile('optimized.webp');
图片格式选择:
| 格式 | 适合场景 | 压缩效果 |
|---|---|---|
| JPEG | 照片、复杂图片 | 体积小,有损 |
| PNG | 需要透明、图标 | 体积大,无损 |
| WebP | 通用场景 | 体积小 30%,兼容性好 |
| AVIF | 最新格式 | 体积最小,兼容性差 |
| SVG | 图标、简单图形 | 体积极小,可缩放 |
<!-- 用 picture 标签,浏览器自动选最优格式 -->
<picture>
<source srcset="image.avif" type="image/avif">
<source srcset="image.webp" type="image/webp">
<img src="image.jpg" alt="描述" loading="lazy" width="800" height="600">
</picture>
2. 代码压缩
// vite.config.js
export default defineConfig({
build: {
minify: 'terser',
terserOptions: {
compress: {
drop_console: true,
drop_debugger: true,
},
},
cssMinify: true,
},
});
3. Tree Shaking——只打包用到的代码
// ❌ 整个 lodash 都打包进来了(500KB+)
import _ from 'lodash';
// ✅ 只打包 debounce(几 KB)
import { debounce } from 'lodash-es';
第二斧:加载快一点(速度优化)
1. CDN 加速
<!-- ✅ 用 CDN,用户从最近的节点拿 -->
<script src="https://cdn.staticfile.org/react/18.2.0/umd/react.production.min.js"></script>
2. 开启 gzip——让带宽省一半
import compression from 'compression';
app.use(compression({ level: 6, threshold: 1024 }));
gzip 能让传输体积减少 60-70%。
3. 缓存策略——第二次访问秒开
app.use((req, res, next) => {
if (req.path.endsWith('.html')) {
res.setHeader('Cache-Control', 'no-cache');
} else {
// 静态资源缓存一年,文件名带 hash 自然失效
res.setHeader('Cache-Control', 'public, max-age=31536000, immutable');
}
next();
});
4. DNS 预解析
<link rel="dns-prefetch" href="//cdn.example.com">
<link rel="preconnect" href="https://cdn.example.com" crossorigin>
第三斧:边用边加载(顺序优化)
1. 代码分割
// ❌ 所有路由打包成一个文件,进首页就下载整站代码
import ProductList from './pages/ProductList';
// ✅ 按需加载,用到才下载
const ProductList = lazy(() => import('./pages/ProductList'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
function App() {
return (
<Suspense fallback={<div>加载中...</div>}>
<Routes>
<Route path="/products" element={<ProductList />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
);
}
2. 图片懒加载
<!-- 原生懒加载,一行搞定 -->
<img src="banner.jpg" loading="lazy" width="800" height="600">
// 需要精细控制时,用 Intersection Observer
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img);
}
});
}, { rootMargin: '200px' });
document.querySelectorAll('[data-src]').forEach(img => observer.observe(img));
3. defer 和 async
<!-- ❌ 阻塞渲染,白屏时间变长 -->
<script src="app.js"></script>
<!-- ✅ defer:等 HTML 解析完再执行(推荐) -->
<script src="app.js" defer></script>
<!-- async:适合无依赖的第三方脚本 -->
<script src="analytics.js" async></script>
四个容易踩的坑
坑 1:首屏图片也懒加载了
<!-- ❌ 首屏图片懒加载,LCP 变差 -->
<img src="hero.jpg" loading="lazy">
<!-- ✅ 首屏图片要快,加 fetchpriority -->
<img src="hero.jpg" fetchpriority="high">
坑 2:图片没设宽高,CLS 很差
<!-- ❌ 图片加载后页面跳动 -->
<img src="photo.jpg">
<!-- ✅ 提前留好位置 -->
<img src="photo.jpg" width="800" height="600" loading="lazy">
坑 3:第三方脚本没用 async
<!-- ❌ 统计脚本阻塞渲染 -->
<script src="https://analytics.example.com/track.js"></script>
<!-- ✅ 不影响主流程,用 async -->
<script src="https://analytics.example.com/track.js" async></script>
坑 4:字体加载导致闪烁
@font-face {
font-family: 'MyFont';
src: url('/fonts/myfont.woff2') format('woff2');
font-display: swap; /* 先用系统字体,加载完再替换 */
}
上线前检查清单
- 图片压缩了吗?(WebP/AVIF)
- 首屏图片加了
fetchpriority="high"吗? - 非首屏图片加了
loading="lazy"吗? - 图片设置宽高了吗?(防止 CLS)
- JS 压缩了吗?(去掉 console)
- 路由代码分割了吗?
- CDN 接入了吗?
- gzip/brotli 开启了吗?
- 缓存设置了吗?(文件名加 hash)
- Lighthouse 分数达到 90+ 了吗?
总结
少加载东西 —— 压缩图片、压缩代码、Tree Shaking
加载快一点 —— CDN、gzip 压缩、合理缓存
边用边加载 —— 代码分割、懒加载、预加载
先用 Lighthouse 跑一遍,找到最影响性能的问题,从那个开始改。
一般优化完,首屏时间能从 5-8 秒降到 1-2 秒,用户体验直接翻倍。