Vercel 团队 10 年 React 性能优化经验:10 大核心策略让性能提升 300%
Vercel 最近发布了 React 最佳实践库,将十余年来积累的 React 和 Next.js 优化经验整合到了一个指南中。
其中一共包含8 个类别、40 多条规则。
这些原则并不是纸上谈兵,而是 Vercel 团队在 10 余年从无数生产代码库中总结出的经验之谈。它们已经被无数成功案例验证,能切实改善用户体验和业务指标。
以下将是对你的 React 和 Next.js 项目影响最大的 10 大实践。
1. 将独立的异步操作并行
请求瀑布流是 React 应用性能的头号杀手。
每次顺序执行 await 都会增加网络延迟,消除它们可以带来最大的性能提升。
❌ 错误:
async function Page() {
const user = await fetchUser();
const posts = await fetchPosts();
return <Dashboard user={user} posts={posts} />;
}
✅ 正确:
async function Page() {
const [user, posts] = await Promise.all([fetchUser(), fetchPosts()]);
return <Dashboard user={user} posts={posts} />;
}
当处理多个数据源时,这个简单的改变可以将页面加载时间减少数百毫秒。
![]()
2. 避免桶文件导入
从桶文件导入会强制打包程序解析整个库,即使你只需要其中一个组件。
这就像把整个衣柜都搬走,只为了穿一件衣服。
❌ 错误:
import { Check, X, Menu } from "lucide-react";
✅ 正确:
import Check from "lucide-react/dist/esm/icons/check";
import X from "lucide-react/dist/esm/icons/x";
import Menu from "lucide-react/dist/esm/icons/menu";
更好的方式(使用 Next.js 配置):
// next.config.js
module.exports = {
experimental: {
optimizePackageImports: ["lucide-react", "@mui/material"],
},
};
// 然后保持简洁的导入方式
import { Check, X, Menu } from "lucide-react";
直接导入可将启动速度提高 15-70%,构建难度降低 28%,冷启动速度提高 40%,HMR 速度显著提高。
![]()
3. 使用延迟状态初始化
当初始化状态需要进行耗时的计算时,将初始化程序包装在一个函数中,确保它只运行一次。
❌ 错误:
function Component() {
const [config, setConfig] = useState(JSON.parse(localStorage.getItem("config")));
return <div>{config.theme}</div>;
}
✅ 正确:
function Component() {
const [config, setConfig] = useState(() => JSON.parse(localStorage.getItem("config")));
return <div>{config.theme}</div>;
}
组件每次渲染都会从 localStorage 解析 JSON 配置,但其实它只需要在初始化的时候读取一次,将其封装在回调函数中可以消除这种浪费。
![]()
4. 最小化 RSC 边界的数据传递
React 服务端/客户端边界会将所有对象属性序列化为字符串并嵌入到 HTML 响应中,这会直接影响页面大小和加载时间。
❌ 错误:
async function Page() {
const user = await fetchUser(); // 50 fields
return <Profile user={user} />;
}
("use client");
function Profile({ user }) {
return <div>{user.name}</div>; // uses 1 field
}
✅ 正确:
async function Page() {
const user = await fetchUser();
return <Profile name={user.name} />;
}
("use client");
function Profile({ name }) {
return <div>{name}</div>;
}
只传递客户端组件实际需要的数据。
![]()
5. 动态导入大型组件
仅在功能激活时加载大型库,减少初始包体积。
❌ 错误:
import { AnimationPlayer } from "./heavy-animation-lib";
function Component() {
const [enabled, setEnabled] = useState(false);
return enabled ? <AnimationPlayer /> : null;
}
✅ 正确:
function AnimationPlayer({ enabled, setEnabled }) {
const [frames, setFrames] = useState(null);
useEffect(() => {
if (enabled && !frames && typeof window !== "undefined") {
import("./animation-frames.js").then((mod) => setFrames(mod.frames)).catch(() => setEnabled(false));
}
}, [enabled, frames, setEnabled]);
if (!frames) return <Skeleton />;
return <Canvas frames={frames} />;
}
typeof window 可以防止将此模块打包用于 SSR,优化服务端包体积和构建速度。
![]()
6. 延迟加载第三方脚本
分析和跟踪脚本不要阻塞用户交互。
❌ 错误:
export default function RootLayout({ children }) {
useEffect(() => {
initAnalytics();
}, []);
return (
<html>
<body>{children}</body>
</html>
);
}
✅ 正确:
import { Analytics } from "@vercel/analytics/react";
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
</body>
</html>
);
}
在水合后加载分析脚本,优先处理交互内容。
![]()
7. 使用 React.cache() 进行请求去重
防止服务端在同一渲染周期内重复请求。
❌ 错误:
async function Sidebar() {
const user = await fetchUser();
return <div>{user.name}</div>;
}
async function Header() {
const user = await fetchUser(); // 重复请求
return <nav>{user.email}</nav>;
}
✅ 正确:
import { cache } from "react";
const getUser = cache(async () => {
return await fetchUser();
});
async function Sidebar() {
const user = await getUser();
return <div>{user.name}</div>;
}
async function Header() {
const user = await getUser(); // 已缓存,无重复请求
return <nav>{user.email}</nav>;
}
![]()
8. 实现跨请求数据的 LRU 缓存
React.cache() 仅在单个请求内有效,因此对于跨连续请求共享的数据,使用 LRU 缓存。
❌ 错误:
import { LRUCache } from "lru-cache";
const cache = new LRUCache({
max: 1000,
ttl: 5 * 60 * 1000, // 5 分钟
});
export async function getUser(id) {
const cached = cache.get(id);
if (cached) return cached;
const user = await db.user.findUnique({ where: { id } });
cache.set(id, user);
return user;
}
这在 Vercel 的 Fluid Compute 中特别有效,多个并发请求共享同一个函数实例。
9. 通过组件组合实现并行化
React 服务端组件在树状结构中按顺序执行,因此需要使用组合对组件树进行重构以实现并行化数据获取:
❌ 错误:
async function Page() {
const data = await fetchPageData();
return (
<>
<Header />
<Sidebar data={data} />
</>
);
}
✅ 正确:
async function Page() {
return (
<>
<Header />
<Sidebar />
</>
);
}
async function Sidebar() {
const data = await fetchPageData();
return <div>{data.content}</div>;
}
这样一来,页眉和侧边栏就可以并行获取数据了。
10. 使用 SWR 进行客户端请求去重
当客户端上的多个组件请求相同的数据时,SWR 会自动对请求进行去重。
❌ 错误:
function UserProfile() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch("/api/user")
.then((r) => r.json())
.then(setUser);
}, []);
return <div>{user?.name}</div>;
}
function UserAvatar() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch("/api/user")
.then((r) => r.json())
.then(setUser);
}, []);
return <img src={user?.avatar} />;
}
✅ 正确:
import useSWR from "swr";
const fetcher = (url) => fetch(url).then((r) => r.json());
function UserProfile() {
const { data: user } = useSWR("/api/user", fetcher);
return <div>{user?.name}</div>;
}
function UserAvatar() {
const { data: user } = useSWR("/api/user", fetcher);
return <img src={user?.avatar} />;
}
SWR 只发出一个请求,并将结果在两个组件之间共享。
11. 总结
这些最佳实践的美妙之处在于:它们不是复杂的架构变更。大多数都是简单的代码修改,却能产生显著的性能改进。
一个 600ms 的瀑布等待时间,会影响每一位用户,直到被修复。
一个桶文件导入造成的包膨胀,会减慢每一次构建和每一次页面加载。
所以越早采用这些实践,就能避免积累越来越多的性能债务。
![]()
现在开始应用这些技巧,让你的 React 应用快如闪电吧!
我是冴羽,10 年笔耕不辍,专注前端领域,更新了 10+ 系列、300+ 篇原创技术文章,翻译过 Svelte、Solid.js、TypeScript 文档,著有小册《Next.js 开发指南》、《Svelte 开发指南》、《Astro 实战指南》。
欢迎围观我的“网页版朋友圈”,关注我的公众号:冴羽(或搜索 yayujs),每天分享前端知识、AI 干货。