ProductReadyProductReady

性能优化

优化 ProductReady 应用速度 - Core Web Vitals、缓存、代码分割和数据库优化

性能优化

使用 Next.js 15+ 优化技术让您的 ProductReady 应用快如闪电。学习如何改善 Core Web Vitals、数据库性能和用户体验。

性能 = SEO + 用户体验:快速的网站在 Google 中排名更高,转化率更好。每延迟 100ms 损失 1% 转化率。


快速概览

关键指标(Core Web Vitals):

  • LCP(Largest Contentful Paint)- 加载速度 < 2.5s
  • FID/INP(First Input Delay / Interaction to Next Paint)- 交互性 < 200ms
  • CLS(Cumulative Layout Shift)- 视觉稳定性 < 0.1

优化领域:

  • ✅ 图片和字体优化
  • ✅ 代码分割和懒加载
  • ✅ 缓存策略
  • ✅ 数据库查询优化
  • ✅ API 响应时间
  • ✅ 包大小减少

性能测量

工具

  1. Lighthouse(Chrome DevTools)

    • 按 F12 → Lighthouse 标签 → 生成报告
  2. PageSpeed Insights

  3. Vercel Analytics(如果部署到 Vercel)

    • 真实用户监控(RUM)
    • 自动跟踪 Core Web Vitals

图片优化

Next.js Image 组件

始终使用 <Image> 而非 <img>

import Image from 'next/image';

// ✅ 好:自动优化
<Image
  src="/hero.png"
  alt="Hero 图片"
  width={1200}
  height={630}
  priority // 用于首屏图片
  placeholder="blur"
/>

// ❌ 坏:无优化
<img src="/hero.png" alt="Hero" />

优先加载

标记关键图片为优先:

// Hero 图片(首屏)
<Image src="/hero.png" priority alt="Hero" width={1200} height={630} />

// 首屏以下的图片(默认懒加载)
<Image src="/feature.png" alt="Feature" width={800} height={600} />

字体优化

Next.js 字体加载

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

const inter = Inter({
  subsets: ['latin'],
  display: 'swap', // 防止不可见文本
  variable: '--font-inter',
});

export default function RootLayout({ children }: Props) {
  return (
    <html className={inter.variable}>
      <body className="font-sans">{children}</body>
    </html>
  );
}

代码分割和懒加载

动态导入

import dynamic from 'next/dynamic';

// 懒加载重型组件
const HeavyChart = dynamic(() => import('~/components/heavy-chart'), {
  loading: () => <div>加载图表中...</div>,
  ssr: false, // 如果 SEO 不需要,跳过 SSR
});

export function Dashboard() {
  return (
    <div>
      <h1>仪表板</h1>
      <HeavyChart /> {/* 仅在组件挂载时加载 */}
    </div>
  );
}

缓存策略

静态页面(ISR)

定期重建页面:

// src/app/posts/[slug]/page.tsx
export const revalidate = 3600; // 每小时重新验证

export default async function PostPage({ params }: Props) {
  const post = await fetchPost(params.slug);
  return <div>{post.title}</div>;
}

React Query 缓存

'use client';

import { trpc } from '~/lib/trpc/client';

export function Posts() {
  const { data } = trpc.posts.list.useQuery(undefined, {
    staleTime: 5 * 60 * 1000, // 5 分钟
    cacheTime: 10 * 60 * 1000, // 10 分钟
  });

  return <div>{/* 渲染文章 */}</div>;
}

数据库优化

查询优化

❌ 坏:N+1 查询

const posts = await db.query.posts.findMany();

for (const post of posts) {
  const author = await db.query.users.findFirst({
    where: eq(users.id, post.authorId),
  });
  post.author = author; // N 个查询!
}

✅ 好:在单个查询中 JOIN

const posts = await db.query.posts.findMany({
  with: {
    author: true, // 使用 JOIN 的单个查询
  },
});

索引

为频繁查询的字段添加索引:

// src/db/schema/posts.ts
import { pgTable, text, timestamp, index } from 'drizzle-orm/pg-core';

export const posts = pgTable('posts', {
  id: text('id').primaryKey(),
  authorId: text('author_id').notNull(),
  slug: text('slug').notNull(),
  createdAt: timestamp('created_at').defaultNow(),
}, (table) => ({
  // 用于更快查找的索引
  slugIdx: index('slug_idx').on(table.slug),
  authorIdx: index('author_idx').on(table.authorId),
}));

分页

❌ 坏:加载所有记录

const posts = await db.query.posts.findMany(); // 可能有数百万条!

✅ 好:分页

const posts = await db.query.posts.findMany({
  limit: 20,
  offset: page * 20,
  orderBy: [desc(posts.createdAt)],
});

包大小优化

分析包

# 带分析构建
pnpm build

# 安装分析器
pnpm add -D @next/bundle-analyzer

# 运行分析
ANALYZE=true pnpm build

Tree Shaking

只导入需要的内容:

// ❌ 坏:导入整个库
import _ from 'lodash';
const result = _.map(array, fn);

// ✅ 好:可 tree-shake 的导入
import { map } from 'lodash-es';
const result = map(array, fn);

// ✅ 更好:使用原生方法
const result = array.map(fn);

React 性能

记忆化

import { memo, useMemo, useCallback } from 'react';

// 记忆化昂贵的组件
export const ExpensiveComponent = memo(function ExpensiveComponent({ data }: Props) {
  const processedData = useMemo(() => {
    return expensiveProcessing(data);
  }, [data]);

  return <div>{processedData}</div>;
});

检查清单

图片

  • 使用 Next.js <Image> 组件
  • 为首屏图片添加 priority
  • 使用适当的尺寸
  • 提供 WebP 格式

字体

  • 使用 next/font 进行优化
  • 设置 display: 'swap'
  • 预加载关键字体

代码

  • 懒加载重型组件
  • 按路由分割代码
  • 移除未使用的依赖
  • Tree-shake 导入

缓存

  • 为静态页面启用 ISR
  • 缓存 API 响应
  • 使用 React Query 缓存

数据库

  • 为频繁查询的字段添加索引
  • 使用连接池
  • 分页大结果集
  • 避免 N+1 查询

快速胜利

30 秒:

  • 添加 <Analytics /> 组件

5 分钟:

  • <Image> 替换 <img>
  • 为 hero 图片添加 priority
  • 使用 next/font 加载字体

30 分钟:

  • 添加数据库索引
  • 为静态页面启用 ISR
  • 懒加载重型组件

下一步

  • 先测量 - 运行 Lighthouse 识别问题
  • 一次修复一个 - 不要过早优化
  • 持续监控 - 跟踪 Core Web Vitals
  • 在真实设备上测试 - 移动性能很重要

首先专注于 Core Web Vitals - 它们直接影响 SEO 和用户体验。从图片和字体开始,它们是最容易获得的胜利。

On this page