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 优化数据加载 #

  • 并行加载独立数据
  • 延迟加载非关键数据
  • 使用缓存减少请求

八、总结 #

本章我们学习了:

  1. 代码分割:路由级和组件级分割
  2. 数据加载:并行加载和延迟加载
  3. 缓存优化:HTTP缓存和服务端缓存
  4. 资源优化:图片和字体优化
  5. 性能监控:Web Vitals和自定义指标

核心要点:

  • 利用Remix的并行数据加载
  • 使用预加载优化导航体验
  • 合理使用缓存
  • 监控关键性能指标
最后更新:2026-03-28