Remix性能优化 #
一、性能优化概述 #
Remix 提供了多种性能优化手段,包括并行数据加载、代码分割、缓存策略等。
二、代码分割 #
2.1 路由级代码分割 #
Remix 自动进行路由级代码分割:
text
访问 /dashboard
├── 加载 root.tsx 代码
├── 加载 dashboard.tsx 代码
└── 不加载其他路由代码
2.2 组件懒加载 #
tsx
import { lazy, Suspense } from "react";
const HeavyChart = lazy(() => import("~/components/HeavyChart"));
export default function Dashboard() {
return (
<div>
<Suspense fallback={<div>加载图表中...</div>}>
<HeavyChart data={chartData} />
</Suspense>
</div>
);
}
2.3 路由懒加载 #
tsx
import { lazy, Suspense } from "react";
import { RouteObject } from "react-router-dom";
const AdminPanel = lazy(() => import("./routes/admin"));
const routes: RouteObject[] = [
{
path: "admin",
element: (
<Suspense fallback={<div>加载中...</div>}>
<AdminPanel />
</Suspense>
),
},
];
三、数据加载优化 #
3.1 并行加载 #
tsx
export async function loader({ params }: LoaderFunctionArgs) {
const [user, posts, comments] = await Promise.all([
getUser(params.id),
getPosts(params.id),
getComments(params.id),
]);
return json({ user, posts, comments });
}
3.2 延迟加载非关键数据 #
tsx
import { defer } from "@remix-run/node";
import { Await, useLoaderData } from "@remix-run/react";
import { Suspense } from "react";
export async function loader({ params }: LoaderFunctionArgs) {
const post = await getPost(params.id);
const comments = getComments(params.id);
return defer({ post, comments });
}
export default function Post() {
const { post, comments } = useLoaderData<typeof loader>();
return (
<div>
<article>{post.content}</article>
<Suspense fallback={<div>加载评论中...</div>}>
<Await resolve={comments}>
{(comments) => <CommentList comments={comments} />}
</Await>
</Suspense>
</div>
);
}
3.3 预加载 #
tsx
import { Link } from "@remix-run/react";
export default function PostList({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link
to={`/posts/${post.id}`}
prefetch="intent"
>
{post.title}
</Link>
</li>
))}
</ul>
);
}
四、缓存优化 #
4.1 HTTP缓存 #
tsx
export async function loader() {
const data = await fetchData();
return json(data, {
headers: {
"Cache-Control": "public, max-age=300, stale-while-revalidate=60",
},
});
}
4.2 服务端缓存 #
tsx
const cache = new Map<string, { data: any; expires: number }>();
export async function loader({ params }: LoaderFunctionArgs) {
const cacheKey = `post-${params.id}`;
const cached = cache.get(cacheKey);
if (cached && cached.expires > Date.now()) {
return json(cached.data);
}
const post = await getPost(params.id);
cache.set(cacheKey, {
data: post,
expires: Date.now() + 60 * 1000,
});
return json({ post });
}
五、资源优化 #
5.1 图片优化 #
tsx
import type { LinksFunction } from "@remix-run/node";
export const links: LinksFunction = () => [
{
rel: "preload",
href: "/images/hero.webp",
as: "image",
},
];
export default function Hero() {
return (
<picture>
<source srcSet="/images/hero.webp" type="image/webp" />
<img
src="/images/hero.jpg"
alt="Hero"
loading="lazy"
width={800}
height={400}
/>
</picture>
);
}
5.2 字体优化 #
tsx
export const links: LinksFunction = () => [
{
rel: "preload",
href: "/fonts/inter-var.woff2",
as: "font",
type: "font/woff2",
crossOrigin: "anonymous",
},
];
六、性能监控 #
6.1 Web Vitals #
tsx
import { useEffect } from "react";
export function useWebVitals() {
useEffect(() => {
if (typeof window !== "undefined" && "performance" in window) {
const observer = new PerformanceObserver((list) => {
for (const entry of list.getEntries()) {
console.log(entry.name, entry.startTime);
}
});
observer.observe({ entryTypes: ["paint", "largest-contentful-paint"] });
return () => observer.disconnect();
}
}, []);
}
6.2 自定义指标 #
tsx
export function trackPerformance() {
if (typeof window === "undefined") return;
const navigation = performance.getEntriesByType("navigation")[0] as PerformanceNavigationTiming;
const metrics = {
dns: navigation.domainLookupEnd - navigation.domainLookupStart,
tcp: navigation.connectEnd - navigation.connectStart,
request: navigation.responseStart - navigation.requestStart,
response: navigation.responseEnd - navigation.responseStart,
domProcessing: navigation.domComplete - navigation.domInteractive,
total: navigation.loadEventEnd - navigation.fetchStart,
};
// 发送到分析服务
sendMetrics(metrics);
}
七、最佳实践 #
7.1 减少JavaScript体积 #
- 使用动态导入
- 避免大型依赖
- 使用Tree Shaking
7.2 优化关键路径 #
- 预加载关键资源
- 内联关键CSS
- 延迟非关键JavaScript
7.3 优化数据加载 #
- 并行加载独立数据
- 延迟加载非关键数据
- 使用缓存减少请求
八、总结 #
本章我们学习了:
- 代码分割:路由级和组件级分割
- 数据加载:并行加载和延迟加载
- 缓存优化:HTTP缓存和服务端缓存
- 资源优化:图片和字体优化
- 性能监控:Web Vitals和自定义指标
核心要点:
- 利用Remix的并行数据加载
- 使用预加载优化导航体验
- 合理使用缓存
- 监控关键性能指标
最后更新:2026-03-28