frontend-performance

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Frontend Performance Optimization

前端性能优化

Diagnose and fix performance issues in Next.js applications.
诊断并修复Next.js应用中的性能问题。

Quick Diagnostics

快速诊断

Run Lighthouse CLI

运行Lighthouse CLI

bash
undefined
bash
undefined

Performance audit (headless)

Performance audit (headless)

npx lighthouse http://localhost:3000 --output=json --output-path=./lighthouse.json --chrome-flags="--headless" --only-categories=performance
npx lighthouse http://localhost:3000 --output=json --output-path=./lighthouse.json --chrome-flags="--headless" --only-categories=performance

Parse key metrics

Parse key metrics

cat lighthouse.json | jq '{ score: .categories.performance.score, FCP: .audits["first-contentful-paint"].displayValue, LCP: .audits["largest-contentful-paint"].displayValue, TBT: .audits["total-blocking-time"].displayValue, CLS: .audits["cumulative-layout-shift"].displayValue }'
cat lighthouse.json | jq '{ score: .categories.performance.score, FCP: .audits["first-contentful-paint"].displayValue, LCP: .audits["largest-contentful-paint"].displayValue, TBT: .audits["total-blocking-time"].displayValue, CLS: .audits["cumulative-layout-shift"].displayValue }'

Find slow scripts

Find slow scripts

cat lighthouse.json | jq '.audits["bootup-time"].details.items | .[0:8]'
cat lighthouse.json | jq '.audits["bootup-time"].details.items | .[0:8]'

Main thread breakdown

Main thread breakdown

cat lighthouse.json | jq '.audits["mainthread-work-breakdown"].details.items'

**Important**: Always test production builds (`next build && next start`), not dev mode. Dev mode has 2-10x overhead from HMR, source maps, and no optimizations.
cat lighthouse.json | jq '.audits["mainthread-work-breakdown"].details.items'

**重要提示**:请始终测试生产构建版本(`next build && next start`),而非开发模式。开发模式由于HMR、源映射以及未启用优化,会产生2-10倍的性能开销。

Bundle Analysis

包分析

bash
undefined
bash
undefined

Install

Install

bun add -d @next/bundle-analyzer
bun add -d @next/bundle-analyzer

Run analysis

Run analysis

ANALYZE=true bun run build

Configure in `next.config.js`:
```js
import bundleAnalyzer from '@next/bundle-analyzer'

const withBundleAnalyzer = bundleAnalyzer({
  enabled: process.env.ANALYZE === 'true',
})

export default withBundleAnalyzer(nextConfig)
ANALYZE=true bun run build

在`next.config.js`中配置:
```js
import bundleAnalyzer from '@next/bundle-analyzer'

const withBundleAnalyzer = bundleAnalyzer({
  enabled: process.env.ANALYZE === 'true',
})

export default withBundleAnalyzer(nextConfig)

Common Fixes

常见修复方案

1. optimizePackageImports

1. optimizePackageImports

For libraries with many exports (icons, utilities, animation libraries):
js
// next.config.js
const nextConfig = {
  experimental: {
    optimizePackageImports: [
      'framer-motion',
      'lucide-react',
      '@phosphor-icons/react',
      'lodash',
      'date-fns',
      '@heroicons/react',
    ],
  },
}
This ensures tree-shaking works correctly - only imports you use get bundled.
针对包含大量导出的库(图标、工具类、动画库):
js
// next.config.js
const nextConfig = {
  experimental: {
    optimizePackageImports: [
      'framer-motion',
      'lucide-react',
      '@phosphor-icons/react',
      'lodash',
      'date-fns',
      '@heroicons/react',
    ],
  },
}
此配置确保Tree Shaking正常工作 - 仅打包你实际使用的导入内容。

2. Framer Motion - Variants Pattern

2. Framer Motion - 变体模式

WRONG - Creates N animation controllers:
tsx
// Each element has its own animation state - expensive!
{items.map((item, i) => (
  <motion.div
    key={i}
    initial={{ opacity: 0, y: 20 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ delay: i * 0.05 }}  // Individual delays
  >
    {item}
  </motion.div>
))}
RIGHT - Single controller with staggerChildren:
tsx
// Parent controls all children - efficient!
const containerVariants = {
  hidden: {},
  visible: {
    transition: {
      staggerChildren: 0.05,
    },
  },
}

const itemVariants = {
  hidden: { opacity: 0, y: 20 },
  visible: {
    opacity: 1,
    y: 0,
    transition: { type: 'spring' as const, damping: 15 }
  },
}

<motion.div
  variants={containerVariants}
  initial="hidden"
  animate="visible"
>
  {items.map((item, i) => (
    <motion.div key={i} variants={itemVariants}>
      {item}
    </motion.div>
  ))}
</motion.div>
Also avoid:
  • filter: blur()
    in animations - very expensive
  • Too many infinite animations (reduce or use CSS)
  • Individual
    transition
    props on children when using variants
错误示例 - 创建N个动画控制器:
tsx
// Each element has its own animation state - expensive!
{items.map((item, i) => (
  <motion.div
    key={i}
    initial={{ opacity: 0, y: 20 }}
    animate={{ opacity: 1, y: 0 }}
    transition={{ delay: i * 0.05 }}  // Individual delays
  >
    {item}
  </motion.div>
))}
正确示例 - 单个控制器配合staggerChildren:
tsx
// Parent controls all children - efficient!
const containerVariants = {
  hidden: {},
  visible: {
    transition: {
      staggerChildren: 0.05,
    },
  },
}

const itemVariants = {
  hidden: { opacity: 0, y: 20 },
  visible: {
    opacity: 1,
    y: 0,
    transition: { type: 'spring' as const, damping: 15 }
  },
}

<motion.div
  variants={containerVariants}
  initial="hidden"
  animate="visible"
>
  {items.map((item, i) => (
    <motion.div key={i} variants={itemVariants}>
      {item}
    </motion.div>
  ))}
</motion.div>
同时需避免:
  • 在动画中使用
    filter: blur()
    - 性能开销极大
  • 过多无限循环动画(减少使用或改用CSS实现)
  • 使用变体模式时,为子元素单独设置
    transition
    属性

3. Move Heavy Computation Server-Side

3. 重计算逻辑移至服务端

Keep these out of client bundles:
  • Syntax highlighting: use
    shiki
    server-side, not
    prism-react-renderer
  • Markdown parsing: render on server
  • Date formatting libraries: consider
    Intl.DateTimeFormat
  • Large data transformations: API routes or server components
避免将以下内容打包到客户端:
  • 语法高亮:使用服务端的
    shiki
    ,而非客户端的
    prism-react-renderer
  • Markdown解析:在服务端渲染
  • 日期格式化库:考虑使用
    Intl.DateTimeFormat
  • 大型数据转换:通过API路由或服务端组件处理

4. Image Optimization

4. 图片优化

tsx
// Always use next/image
import Image from 'next/image'

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  priority  // For LCP images
  placeholder="blur"  // Reduces CLS
/>
tsx
// Always use next/image
import Image from 'next/image'

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={600}
  priority  // For LCP images
  placeholder="blur"  // Reduces CLS
/>

5. Font Optimization

5. 字体优化

tsx
// app/layout.tsx
import { Inter } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',  // Prevents FOIT
  preload: true,
})
tsx
// app/layout.tsx
import { Inter } from 'next/font/google'

const inter = Inter({
  subsets: ['latin'],
  display: 'swap',  // Prevents FOIT
  preload: true,
})

6. External Packages (Server Only)

6. 外部包(仅服务端使用)

Prevent server-only packages from being bundled:
js
// next.config.js
const nextConfig = {
  serverExternalPackages: ['sharp', 'canvas'],
}
防止仅服务端使用的包被打包到客户端:
js
// next.config.js
const nextConfig = {
  serverExternalPackages: ['sharp', 'canvas'],
}

Performance Targets

性能指标目标

MetricGoodNeeds ImprovementPoor
LCP≤2.5s2.5-4s>4s
FCP≤1.8s1.8-3s>3s
TBT≤200ms200-600ms>600ms
CLS≤0.10.1-0.25>0.25
指标优秀待改进较差
LCP≤2.5s2.5-4s>4s
FCP≤1.8s1.8-3s>3s
TBT≤200ms200-600ms>600ms
CLS≤0.10.1-0.25>0.25

Debugging Workflow

调试流程

  1. Run production Lighthouse - Get baseline metrics
  2. Check bootup-time audit - Find slow scripts
  3. Run bundle analyzer - Identify large chunks
  4. Fix largest issues first - Usually 1-2 packages cause most problems
  5. Re-test - Verify improvements
  1. 运行生产环境Lighthouse - 获取基准指标
  2. 查看bootup-time审计结果 - 定位缓慢脚本
  3. 运行包分析工具 - 识别体积较大的代码块
  4. 优先修复最大问题 - 通常1-2个包是主要性能瓶颈
  5. 重新测试 - 验证优化效果

Quick Wins Checklist

快速优化检查清单

  • Test production build, not dev
  • Add
    optimizePackageImports
    for icon/utility libraries
  • Use Framer Motion variants pattern
  • Remove
    filter: blur()
    from animations
  • Add
    priority
    to LCP images
  • Use
    next/font
    with
    display: swap
  • Move heavy libraries to server components
  • 测试生产构建版本,而非开发模式
  • 为图标/工具类库添加
    optimizePackageImports
    配置
  • 使用Framer Motion变体模式
  • 移除动画中的
    filter: blur()
  • 为LCP图片添加
    priority
    属性
  • 使用
    next/font
    并设置
    display: swap
  • 将大型库移至服务端组件