性能优化
优化 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 响应时间
- ✅ 包大小减少
性能测量
工具
-
Lighthouse(Chrome DevTools)
- 按 F12 → Lighthouse 标签 → 生成报告
-
PageSpeed Insights
- https://pagespeed.web.dev/
- Chrome 用户的真实数据
-
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 buildTree 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 和用户体验。从图片和字体开始,它们是最容易获得的胜利。