performance-optimization

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Performance Optimization

性能优化

Overview

概述

Measure before optimizing. Performance work without measurement is guessing — and guessing leads to premature optimization that adds complexity without improving what matters. Profile first, identify the actual bottleneck, fix it, measure again. Optimize only what measurements prove matters.
优化前先测量。没有测量的性能工作全凭猜测——猜测会导致过早优化,徒增复杂度却无法真正改进核心指标。先做性能分析,找出实际瓶颈,修复后再次测量。仅优化那些测量数据证明确实需要优化的部分。

When to Use

适用场景

  • Performance requirements exist in the spec (load time budgets, response time SLAs)
  • Users or monitoring report slow behavior
  • Core Web Vitals scores are below thresholds
  • You suspect a change introduced a regression
  • Building features that handle large datasets or high traffic
When NOT to use: Don't optimize before you have evidence of a problem. Premature optimization adds complexity that costs more than the performance it gains.
  • 规范中明确了性能要求(加载时间预算、响应时间SLA)
  • 用户或监控系统反馈运行缓慢
  • Core Web Vitals得分低于阈值
  • 你怀疑某次变更引入了性能回退
  • 开发需要处理大量数据集或高流量的功能
不适用场景: 在没有问题证据之前不要优化。过早优化会增加复杂度,其成本远高于带来的性能收益。

Core Web Vitals Targets

Core Web Vitals指标目标

MetricGoodNeeds ImprovementPoor
LCP (Largest Contentful Paint)≤ 2.5s≤ 4.0s> 4.0s
INP (Interaction to Next Paint)≤ 200ms≤ 500ms> 500ms
CLS (Cumulative Layout Shift)≤ 0.1≤ 0.25> 0.25
指标优秀需要优化较差
LCP (Largest Contentful Paint)≤ 2.5s≤ 4.0s> 4.0s
INP (Interaction to Next Paint)≤ 200ms≤ 500ms> 500ms
CLS (Cumulative Layout Shift)≤ 0.1≤ 0.25> 0.25

The Optimization Workflow

优化工作流

1. MEASURE  → Establish baseline with real data
2. IDENTIFY → Find the actual bottleneck (not assumed)
3. FIX      → Address the specific bottleneck
4. VERIFY   → Measure again, confirm improvement
5. GUARD    → Add monitoring or tests to prevent regression
1. MEASURE  → 用真实数据建立基准线
2. IDENTIFY → 找到实际瓶颈(而非假设的)
3. FIX      → 解决特定瓶颈
4. VERIFY   → 再次测量,确认优化效果
5. GUARD    → 增加监控或测试防止性能回退

Step 1: Measure

步骤1:测量

Frontend:
bash
undefined
前端:
bash
undefined

Lighthouse in Chrome DevTools (or CI)

Chrome DevTools中的Lighthouse(或CI中运行)

Chrome DevTools → Performance tab → Record

Chrome DevTools → 性能面板 → 录制

Chrome DevTools MCP → Performance trace

Chrome DevTools MCP → 性能追踪

Web Vitals library in code

代码中引入Web Vitals库

import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(console.log); onINP(console.log); onCLS(console.log);

**Backend:**
```bash
import { onLCP, onINP, onCLS } from 'web-vitals';
onLCP(console.log); onINP(console.log); onCLS(console.log);

**后端:**
```bash

Response time logging

响应时间日志

Application Performance Monitoring (APM)

应用性能监控(APM)

Database query logging with timing

带耗时统计的数据库查询日志

Simple timing

简单计时

console.time('db-query'); const result = await db.query(...); console.timeEnd('db-query');
undefined
console.time('db-query'); const result = await db.query(...); console.timeEnd('db-query');
undefined

Where to Start Measuring

测量起始点选择

Use the symptom to decide what to measure first:
What is slow?
├── First page load
│   ├── Large bundle? --> Measure bundle size, check code splitting
│   ├── Slow server response? --> Measure TTFB, check API/database
│   └── Render-blocking resources? --> Check network waterfall for CSS/JS blocking
├── Interaction feels sluggish
│   ├── UI freezes on click? --> Profile main thread, look for long tasks (>50ms)
│   ├── Form input lag? --> Check re-renders, controlled component overhead
│   └── Animation jank? --> Check layout thrashing, forced reflows
├── Page after navigation
│   ├── Data loading? --> Measure API response times, check for waterfalls
│   └── Client rendering? --> Profile component render time, check for N+1 fetches
└── Backend / API
    ├── Single endpoint slow? --> Profile database queries, check indexes
    ├── All endpoints slow? --> Check connection pool, memory, CPU
    └── Intermittent slowness? --> Check for lock contention, GC pauses, external deps
根据症状决定优先测量的维度:
什么环节慢?
├── 首次页面加载
│   ├── 包体积过大? --> 测量包体积,检查代码拆分
│   ├── 服务端响应慢? --> 测量TTFB,检查API/数据库
│   └── 渲染阻塞资源? --> 检查网络瀑布流中的CSS/JS阻塞情况
├── 交互卡顿
│   ├── 点击后UI冻结? --> 分析主线程,查找长任务(>50ms)
│   ├── 表单输入延迟? --> 检查重渲染、受控组件开销
│   └── 动画掉帧? --> 检查布局抖动、强制回流
├── 导航后页面加载慢
│   ├── 数据加载问题? --> 测量API响应时间,检查瀑布流请求
│   └── 客户端渲染问题? --> 分析组件渲染时间,检查N+1请求
└── 后端 / API
    ├── 单个接口慢? --> 分析数据库查询,检查索引
    ├── 所有接口都慢? --> 检查连接池、内存、CPU
    └── 偶发缓慢? --> 检查锁竞争、GC停顿、外部依赖

Step 2: Identify the Bottleneck

步骤2:识别瓶颈

Common bottlenecks by category:
Frontend:
SymptomLikely CauseInvestigation
Slow LCPLarge images, render-blocking resources, slow serverCheck network waterfall, image sizes
High CLSImages without dimensions, late-loading content, font shiftsCheck layout shift attribution
Poor INPHeavy JavaScript on main thread, large DOM updatesCheck long tasks in Performance trace
Slow initial loadLarge bundle, many network requestsCheck bundle size, code splitting
Backend:
SymptomLikely CauseInvestigation
Slow API responsesN+1 queries, missing indexes, unoptimized queriesCheck database query log
Memory growthLeaked references, unbounded caches, large payloadsHeap snapshot analysis
CPU spikesSynchronous heavy computation, regex backtrackingCPU profiling
High latencyMissing caching, redundant computation, network hopsTrace requests through the stack
按类别划分的常见瓶颈:
前端:
症状可能原因排查方向
LCP慢图片过大、渲染阻塞资源、服务端慢检查网络瀑布流、图片体积
CLS高图片无尺寸、内容延迟加载、字体偏移检查布局偏移归因
INP差主线程JavaScript执行压力大、DOM更新量过大检查性能追踪中的长任务
首次加载慢包体积大、网络请求过多检查包体积、代码拆分
后端:
症状可能原因排查方向
API响应慢N+1查询、缺失索引、查询未优化检查数据库查询日志
内存持续增长引用泄露、无边界缓存、载荷过大堆快照分析
CPU突增同步 heavy 计算、正则回溯CPU性能分析
高延迟缺失缓存、冗余计算、网络跳数过多全链路请求追踪

Step 3: Fix Common Anti-Patterns

步骤3:修复常见反模式

N+1 Queries (Backend)

N+1 查询(后端)

typescript
// BAD: N+1 — one query per task for the owner
const tasks = await db.tasks.findMany();
for (const task of tasks) {
  task.owner = await db.users.findUnique({ where: { id: task.ownerId } });
}

// GOOD: Single query with join/include
const tasks = await db.tasks.findMany({
  include: { owner: true },
});
typescript
// 反面案例: N+1 — 为每个任务单独查询所属用户
const tasks = await db.tasks.findMany();
for (const task of tasks) {
  task.owner = await db.users.findUnique({ where: { id: task.ownerId } });
}

// 正面案例: 带关联查询的单次请求
const tasks = await db.tasks.findMany({
  include: { owner: true },
});

Unbounded Data Fetching

无边界数据拉取

typescript
// BAD: Fetching all records
const allTasks = await db.tasks.findMany();

// GOOD: Paginated with limits
const tasks = await db.tasks.findMany({
  take: 20,
  skip: (page - 1) * 20,
  orderBy: { createdAt: 'desc' },
});
typescript
// 反面案例: 拉取所有记录
const allTasks = await db.tasks.findMany();

// 正面案例: 带限制的分页查询
const tasks = await db.tasks.findMany({
  take: 20,
  skip: (page - 1) * 20,
  orderBy: { createdAt: 'desc' },
});

Missing Image Optimization (Frontend)

缺失图片优化(前端)

html
<!-- BAD: No dimensions, no lazy loading, no responsive sizes -->
<img src="/hero.jpg" />

<!-- GOOD: Responsive, lazy-loaded, properly sized -->
<img
  src="/hero.jpg"
  srcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w"
  sizes="(max-width: 768px) 100vw, 50vw"
  width="1200"
  height="600"
  loading="lazy"
  alt="Hero image description"
/>
html
<!-- 反面案例: 无尺寸、无懒加载、无响应式尺寸 -->
<img src="/hero.jpg" />

<!-- 正面案例: 响应式、懒加载、尺寸正确 -->
<img
  src="/hero.jpg"
  srcset="/hero-400.webp 400w, /hero-800.webp 800w, /hero-1200.webp 1200w"
  sizes="(max-width: 768px) 100vw, 50vw"
  width="1200"
  height="600"
  loading="lazy"
  alt="Hero图片描述"
/>

Unnecessary Re-renders (React)

不必要的重渲染(React)

tsx
// BAD: Creates new object on every render, causing children to re-render
function TaskList() {
  return <TaskFilters options={{ sortBy: 'date', order: 'desc' }} />;
}

// GOOD: Stable reference
const DEFAULT_OPTIONS = { sortBy: 'date', order: 'desc' } as const;
function TaskList() {
  return <TaskFilters options={DEFAULT_OPTIONS} />;
}

// Use React.memo for expensive components
const TaskItem = React.memo(function TaskItem({ task }: Props) {
  return <div>{/* expensive render */}</div>;
});

// Use useMemo for expensive computations
function TaskStats({ tasks }: Props) {
  const stats = useMemo(() => calculateStats(tasks), [tasks]);
  return <div>{stats.completed} / {stats.total}</div>;
}
tsx
// 反面案例: 每次渲染都创建新对象,导致子组件重渲染
function TaskList() {
  return <TaskFilters options={{ sortBy: 'date', order: 'desc' }} />;
}

// 正面案例: 稳定的引用
const DEFAULT_OPTIONS = { sortBy: 'date', order: 'desc' } as const;
function TaskList() {
  return <TaskFilters options={DEFAULT_OPTIONS} />;
}

// 给开销大的组件使用React.memo
const TaskItem = React.memo(function TaskItem({ task }: Props) {
  return <div>{/* 开销大的渲染逻辑 */}</div>;
});

// 给开销大的计算使用useMemo
function TaskStats({ tasks }: Props) {
  const stats = useMemo(() => calculateStats(tasks), [tasks]);
  return <div>{stats.completed} / {stats.total}</div>;
}

Large Bundle Size

包体积过大

typescript
// BAD: Importing entire library
import { format } from 'date-fns';

// GOOD: Tree-shakable import (if the library supports it)
import { format } from 'date-fns/format';

// GOOD: Dynamic import for heavy, rarely-used features
const ChartLibrary = lazy(() => import('./ChartLibrary'));
typescript
// 反面案例: 导入整个库
import { format } from 'date-fns';

// 正面案例: 支持tree-shaking的导入(如果库支持)
import { format } from 'date-fns/format';

// 正面案例: 对不常用的重型功能做动态导入
const ChartLibrary = lazy(() => import('./ChartLibrary'));

Missing Caching (Backend)

缺失缓存(后端)

typescript
// Cache frequently-read, rarely-changed data
const CACHE_TTL = 5 * 60 * 1000; // 5 minutes
let cachedConfig: AppConfig | null = null;
let cacheExpiry = 0;

async function getAppConfig(): Promise<AppConfig> {
  if (cachedConfig && Date.now() < cacheExpiry) {
    return cachedConfig;
  }
  cachedConfig = await db.config.findFirst();
  cacheExpiry = Date.now() + CACHE_TTL;
  return cachedConfig;
}

// HTTP caching headers for static assets
app.use('/static', express.static('public', {
  maxAge: '1y',           // Cache for 1 year
  immutable: true,        // Never revalidate (use content hashing in filenames)
}));

// Cache-Control for API responses
res.set('Cache-Control', 'public, max-age=300'); // 5 minutes
typescript
// 缓存读多改少的数据
const CACHE_TTL = 5 * 60 * 1000; // 5分钟
let cachedConfig: AppConfig | null = null;
let cacheExpiry = 0;

async function getAppConfig(): Promise<AppConfig> {
  if (cachedConfig && Date.now() < cacheExpiry) {
    return cachedConfig;
  }
  cachedConfig = await db.config.findFirst();
  cacheExpiry = Date.now() + CACHE_TTL;
  return cachedConfig;
}

// 静态资源的HTTP缓存头
app.use('/static', express.static('public', {
  maxAge: '1y',           // 缓存1年
  immutable: true,        // 无需重新验证(文件名使用内容哈希)
}));

// API响应的Cache-Control
res.set('Cache-Control', 'public, max-age=300'); // 5分钟

Performance Budget

性能预算

Set budgets and enforce them:
JavaScript bundle: < 200KB gzipped (initial load)
CSS: < 50KB gzipped
Images: < 200KB per image (above the fold)
Fonts: < 100KB total
API response time: < 200ms (p95)
Time to Interactive: < 3.5s on 4G
Lighthouse Performance score: ≥ 90
Enforce in CI:
bash
undefined
设置预算并强制执行:
JavaScript包: gzip后 < 200KB(首次加载)
CSS: gzip后 < 50KB
图片: 首屏单张图片 < 200KB
字体: 总计 < 100KB
API响应时间: p95 < 200ms
可交互时间: 4G网络下 < 3.5s
Lighthouse性能得分: ≥ 90
在CI中强制执行:
bash
undefined

Bundle size check

包体积检查

npx bundlesize --config bundlesize.config.json
npx bundlesize --config bundlesize.config.json

Lighthouse CI

Lighthouse CI

npx lhci autorun
undefined
npx lhci autorun
undefined

Common Rationalizations

常见误区

RationalizationReality
"We'll optimize later"Performance debt compounds. Fix obvious anti-patterns now, defer micro-optimizations.
"It's fast on my machine"Your machine isn't the user's. Profile on representative hardware and networks.
"This optimization is obvious"If you didn't measure, you don't know. Profile first.
"Users won't notice 100ms"Research shows 100ms delays impact conversion rates. Users notice more than you think.
"The framework handles performance"Frameworks prevent some issues but can't fix N+1 queries or oversized bundles.
误区实际情况
"我们之后再优化"性能债务会越积越多。现在就修复明显的反模式,微优化可以延后。
"我机器上跑得很快"你的机器不等于用户的设备。请在代表性的硬件和网络环境下分析。
"这个优化效果是明显的"如果你没有测量,你就无法确认效果。先做性能分析。
"用户不会注意到100ms的延迟"研究表明100ms的延迟就会影响转化率。用户的感知比你想象的更敏锐。
"框架会处理性能问题"框架可以避免部分问题,但无法修复N+1查询或者过大的包体积。

Red Flags

危险信号

  • Optimization without profiling data to justify it
  • N+1 query patterns in data fetching
  • List endpoints without pagination
  • Images without dimensions, lazy loading, or responsive sizes
  • Bundle size growing without review
  • No performance monitoring in production
  • React.memo
    and
    useMemo
    everywhere (overusing is as bad as underusing)
  • 没有性能分析数据支撑的优化
  • 数据拉取中存在N+1查询模式
  • 列表接口没有分页
  • 图片没有设置尺寸、懒加载或者响应式规格
  • 包体积无审核持续增长
  • 生产环境没有性能监控
  • 到处使用
    React.memo
    useMemo
    (过度使用和不足使用危害一样大)

Verification

验证

After any performance-related change:
  • Before and after measurements exist (specific numbers)
  • The specific bottleneck is identified and addressed
  • Core Web Vitals are within "Good" thresholds
  • Bundle size hasn't increased significantly
  • No N+1 queries in new data fetching code
  • Performance budget passes in CI (if configured)
  • Existing tests still pass (optimization didn't break behavior)
任何性能相关变更后:
  • 留存变更前后的测量数据(具体数值)
  • 特定瓶颈已被识别并修复
  • Core Web Vitals处于「优秀」阈值范围内
  • 包体积没有显著增长
  • 新的数据拉取代码中没有N+1查询
  • CI中的性能预算校验通过(如果已配置)
  • 现有测试全部通过(优化没有破坏原有功能)