名称: 性能优化
描述: Web性能优化,包括捆绑分析、懒加载、缓存策略和核心网页指标
性能优化
捆绑分析和代码分割
// 动态导入用于路由级代码分割
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));
function App() {
return (
<Suspense fallback={<PageSkeleton />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/settings" element={<Settings />} />
</Routes>
</Suspense>
);
}
// vite.config.ts - 手动块分割
export default defineConfig({
build: {
rollupOptions: {
output: {
manualChunks: {
vendor: ["react", "react-dom"],
charts: ["recharts", "d3"],
editor: ["@monaco-editor/react"],
},
},
},
},
});
# 分析捆绑组成
npx vite-bundle-visualizer
npx source-map-explorer dist/assets/*.js
图像优化
import Image from "next/image";
function ProductImage({ src, alt }: { src: string; alt: string }) {
return (
<Image
src={src}
alt={alt}
width={800}
height={600}
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
placeholder="blur"
blurDataURL={generateBlurHash(src)}
loading="lazy"
/>
);
}
<!-- 原生懒加载,带宽高比 -->
<img
src="product.webp"
alt="产品照片"
width="800"
height="600"
loading="lazy"
decoding="async"
fetchpriority="low"
/>
<!-- 预加载LCP图像 -->
<link rel="preload" as="image" href="/hero.webp" fetchpriority="high" />
缓存头
function setCacheHeaders(res: Response, options: CacheOptions) {
if (options.immutable) {
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
return;
}
if (options.revalidate) {
res.setHeader("Cache-Control", `public, max-age=0, s-maxage=${options.revalidate}, stale-while-revalidate=${options.staleWhileRevalidate ?? 86400}`);
return;
}
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
}
app.use("/assets", (req, res, next) => {
setCacheHeaders(res, { immutable: true });
next();
});
app.use("/api", (req, res, next) => {
setCacheHeaders(res, { revalidate: 60, staleWhileRevalidate: 3600 });
next();
});
大型数据的虚拟列表
import { useVirtualizer } from "@tanstack/react-virtual";
function VirtualList({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 50,
overscan: 5,
});
return (
<div ref={parentRef} style={{ height: "600px", overflow: "auto" }}>
<div style={{ height: `${virtualizer.getTotalSize()}px`, position: "relative" }}>
{virtualizer.getVirtualItems().map((virtualRow) => (
<div
key={virtualRow.key}
style={{
position: "absolute",
top: 0,
transform: `translateY(${virtualRow.start}px)`,
height: `${virtualRow.size}px`,
width: "100%",
}}
>
<ItemRow item={items[virtualRow.index]} />
</div>
))}
</div>
</div>
);
}
核心网页指标监控
import { onCLS, onINP, onLCP } from "web-vitals";
function sendMetric(metric: { name: string; value: number; id: string }) {
navigator.sendBeacon("/api/vitals", JSON.stringify(metric));
}
onCLS(sendMetric);
onINP(sendMetric);
onLCP(sendMetric);
- LCP (最大内容绘制): < 2.5秒。预加载英雄图像,优化服务器响应时间。
- INP (交互到下次绘制): < 200毫秒。避免长任务,使用
requestIdleCallback。
- CLS (累计布局偏移): < 0.1。为图像和嵌入设置明确尺寸。
反模式
- 提前加载所有JavaScript,而不是按路由进行代码分割
- 提供未优化的图像(无WebP/AVIF,无响应式尺寸)
- 图像缺少
width 和 height(导致布局偏移)
- 在具有内容哈希的静态资产上使用
Cache-Control: no-cache
- 渲染数千个DOM节点而不是虚拟化列表
- 使用同步计算阻塞主线程
检查清单
- [ ] 路由通过动态
import() 和 Suspense 进行懒加载
- [ ] 捆绑分析并将供应商块分离
- [ ] 图像以WebP/AVIF格式提供,具有响应式
sizes 属性
- [ ] LCP图像使用
fetchpriority="high" 预加载
- [ ] 静态资产使用不可变头和内容哈希缓存
- [ ] 具有100+项的列表使用虚拟化
- [ ] 在生产环境中监控核心网页指标 (LCP, INP, CLS)
- [ ] 关键路径中无渲染阻塞资源