Chapter 09

性能与优化

掌握 Remix 的链接预获取、流式加载、HTTP 缓存等性能优化手段

9.1 链接预获取 prefetch="intent"

Remix 的 <Link> 组件支持 prefetch 属性,可以在用户悬停或触焦某个链接时,提前加载目标路由的数据和 JS 代码,让导航几乎感受不到延迟。

import { Link } from "@remix-run/react";

// "none"   — 不预获取(默认)
// "intent" — 用户悬停/触焦时预获取(推荐)
// "render" — 链接渲染时立即预获取
// "viewport" — 进入视口时预获取

// 推荐:悬停时预获取,无缝跳转体验
<Link to="/blog/hello-world" prefetch="intent">
  阅读文章
</Link>

// 导航菜单:渲染时立即预获取(高优先级路由)
<Link to="/dashboard" prefetch="render">
  仪表盘
</Link>
TSX
ℹ️

prefetch 原理Remix 的 prefetch 会向服务端发起 GET 请求(带特殊头 Purpose: prefetch),触发目标路由的 loader 执行并缓存结果。当用户真正点击时,数据已准备好,几乎零等待。注意:需要确保 loader 是幂等的(无副作用的 GET 请求)。

9.2 defer + Await — 流式加载慢数据

defer() 允许 loader 立即返回(不等待慢查询),将慢速数据作为 Promise 流式传输到客户端。页面骨架立刻显示,慢数据加载完成后自动填充,提升感知性能。

import { defer, type LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData, Await, Suspense } from "@remix-run/react";

export async function loader({ params }: LoaderFunctionArgs) {
  // 快速数据:立即 await(需要渲染标题)
  const post = await getPost(params.slug!);    // 50ms

  // 慢速数据:不 await,作为 Promise 传递
  const relatedPostsPromise = getRelatedPosts(params.slug!); // 800ms
  const commentsPromise = getComments(params.slug!);          // 600ms

  return defer({
    post,                    // 立即可用
    relatedPosts: relatedPostsPromise, // 流式传输
    comments: commentsPromise,         // 流式传输
  });
}

export default function BlogPost() {
  const { post, relatedPosts, comments } = useLoaderData<typeof loader>();

  return (
    <div>
      <h1>{post.title}</h1>
      <div>{post.content}</div>

      {/* 评论区:加载中显示骨架 */}
      <Suspense fallback={<p>加载评论中...</p>}>
        <Await resolve={comments} errorElement={<p>评论加载失败</p>}>
          {(resolvedComments) => (
            <CommentList comments={resolvedComments} />
          )}
        </Await>
      </Suspense>

      {/* 相关文章 */}
      <Suspense fallback={<p>加载推荐...</p>}>
        <Await resolve={relatedPosts}>
          {(posts) => <RelatedPosts posts={posts} />}
        </Await>
      </Suspense>
    </div>
  );
}
TSX

9.3 资源路由 — API 端点

只有 loader/action 没有默认组件的路由称为资源路由,等价于传统框架的 API 路由,可以返回 JSON、RSS、图片、CSV 等任意内容。

// app/routes/api.posts.tsx — JSON API
import { json, type LoaderFunctionArgs } from "@remix-run/node";

export async function loader({ request }: LoaderFunctionArgs) {
  const url = new URL(request.url);
  const q = url.searchParams.get("q") ?? "";
  const posts = await searchPosts(q);
  return json(posts);
}
// GET /api/posts?q=remix → JSON 数组
TSX
// app/routes/feed.rss.tsx — RSS 订阅源
import { type LoaderFunctionArgs } from "@remix-run/node";

export async function loader() {
  const posts = await getLatestPosts(20);

  const rss = `
<rss version="2.0">
  <channel>
    <title>我的博客</title>
    <link>https://myblog.com</link>
    ${posts.map(p => `
    <item>
      <title>${p.title}</title>
      <link>https://myblog.com/blog/${p.slug}</link>
    </item>`).join("")}
  </channel>
</rss>`;

  return new Response(rss, {
    headers: {
      "Content-Type": "application/xml; charset=utf-8",
      "Cache-Control": "public, max-age=3600",
    },
  });
}
TSX

9.4 HTTP 缓存策略

场景Cache-Control说明
静态博客文章public, max-age=3600, s-maxage=86400浏览器1小时,CDN1天
首页文章列表public, max-age=60, stale-while-revalidate=60060秒强缓存,后台刷新
用户个人页面private, max-age=0私有数据,禁止CDN缓存
登录后台页面private, no-store完全禁止缓存
API 搜索结果public, max-age=10短时间共享缓存

9.5 SPA 模式

Remix v2 支持 SPA 模式(Single Page App):构建时生成单个 index.html,没有 Node.js 服务端,所有数据获取在浏览器运行。适合完全部署在 CDN 上的纯前端项目。

// vite.config.ts — 启用 SPA 模式
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [
    remix({
      ssr: false, // 禁用 SSR → SPA 模式
    }),
  ],
});
TS

9.6 Vite 构建优化

// vite.config.ts — 构建优化配置
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";

export default defineConfig({
  plugins: [remix()],
  build: {
    rollupOptions: {
      output: {
        // 手动分包:将大型第三方库单独打包
        manualChunks(id) {
          if (id.includes("node_modules")) {
            if (id.includes("react")) return "react-vendor";
            if (id.includes("@prisma")) return "prisma-vendor";
          }
        },
      },
    },
  },
});
TS
💡

性能优化优先级1. 首先用 prefetch="intent" 让导航体验更流畅(最高收益/最低成本);2. 对慢数据用 defer 拆分加载;3. 对公开页面设置合理的 Cache-Control;4. 最后才考虑 Vite 构建分包等进阶优化。

ℹ️

本章小结Remix 的性能优化工具箱:prefetch="intent" 用户悬停时预取路由数据;defer() + <Await> 将慢查询流式传输不阻塞首屏;资源路由作为 API 端点返回任意 HTTP 响应;Cache-Control 充分利用 HTTP 缓存体系;SPA 模式支持纯前端部署。这套组合在不引入新概念的情况下,充分利用了 Web 平台的原生能力。