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=600 | 60秒强缓存,后台刷新 |
| 用户个人页面 | 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 平台的原生能力。