DevToolBox免费
博客

Remix 完全指南:基于 Web 标准的全栈框架 (2026)

18 分钟阅读作者 DevToolBox Team

Remix 是一个基于 Web 标准构建的全栈 React 框架,拥抱渐进增强和服务端渲染。Remix 不发明新抽象,而是利用浏览器平台——使用标准 HTML 表单、HTTP 缓存和 Fetch API——来构建快速、弹性的 Web 应用。无论你是构建 SaaS 仪表盘、电商网站还是内容平台,Remix 都能提供即使在 JavaScript 加载之前也能工作的应用。

TL;DR

Remix 是一个全栈 React 框架,使用 Web 标准(HTML 表单、HTTP 缓存、Fetch API)通过 loader 加载数据、通过 action 处理变更、通过嵌套路由并行获取数据。它开箱支持渐进增强,在每个路由层级都有内置错误边界,可部署到任何 JavaScript 运行时,包括 Node.js、Cloudflare Workers、Deno 和 Vercel Edge Functions。

Key Takeaways
  • Remix 使用 HTML 表单和 HTTP 缓存等 Web 标准,而非客户端状态管理,使应用在禁用 JavaScript 时也能工作。
  • 嵌套路由支持并行数据加载和细粒度错误边界,消除了其他框架中常见的瀑布请求。
  • Loader 在服务端处理 GET 请求,Action 处理 POST/PUT/DELETE,提供读写操作的清晰分离。
  • 渐进增强意味着表单和导航在没有 JavaScript 时也能工作,当 JS 加载后增强为客户端过渡。
  • Remix 可以部署到任何运行 JavaScript 的地方——Node.js、Cloudflare Workers、Deno、Vercel、Fly.io 和 AWS Lambda。
  • 内置的 defer 流式传输允许你立即发送关键数据,同时并行加载较慢的数据。

什么是 Remix 及其设计理念?

Remix 是由 React Router 团队创建的 React 框架。其核心理念是在 Web 平台上构建,而非将其抽象化。Remix 将操作直接映射到 HTTP 原语——loader 对应 GET 请求,action 对应 POST 请求,headers 控制缓存。

这种方式意味着 Remix 应用本质上是一个渲染 React 组件的 HTTP 服务器。框架处理服务端渲染、代码分割、预取和重新验证的复杂性,同时暴露简单的心智模型:每个路由都是一个模块,有 loader 读取数据、action 写入数据、默认导出组件作为 UI。

# Create a new Remix project
npx create-remix@latest my-remix-app

# Project structure
my-remix-app/
  app/
    entry.client.tsx    # Client entry point
    entry.server.tsx    # Server entry point
    root.tsx            # Root layout route
    routes/             # File-based routes
  public/               # Static assets
  remix.config.js       # Remix configuration
  package.json

Remix vs Next.js 对比

Remix 和 Next.js 都是全栈 React 框架,但在数据加载、路由和渲染策略上有显著差异。

FeatureRemixNext.js
Data LoadingLoaders (server-only)RSC / getServerSideProps
MutationsActions + HTML FormsServer Actions / API Routes
RoutingNested routes with OutletFlat + layout groups
RenderingSSR + StreamingSSR / SSG / ISR / RSC
Progressive EnhancementBuilt-in (works without JS)Manual implementation
Image OptimizationNo built-in solutionnext/image built-in
Caching StrategyHTTP Cache-Control headersISR + Data Cache
Runtime TargetsAny JS runtime (edge/node)Node.js + Edge Runtime
Error HandlingPer-route ErrorBoundaryerror.tsx per segment
Form HandlingNative Form + useActionDataServer Actions + useFormState

基于文件的路由与嵌套路由

Remix 使用基于文件的路由系统,app/routes 目录中的文件成为 URL 路由。关键创新是嵌套路由——路由可以嵌套在父路由内,URL 的每个段映射到特定的路由模块。

嵌套路由不仅仅是组织模式。每个嵌套路由与兄弟路由并行加载数据,消除了父路由必须先加载完毕子路由才能开始的瀑布模式。

// File-based routing examples
// app/routes/_index.tsx        -> /
// app/routes/about.tsx         -> /about
// app/routes/blog._index.tsx   -> /blog
// app/routes/blog.\$slug.tsx   -> /blog/:slug
// app/routes/blog.tsx          -> /blog (layout)

// app/routes/blog.tsx — Parent layout route
import { Outlet } from "@remix-run/react";

export default function BlogLayout() {
  return (
    <div>
      <h1>My Blog</h1>
      <nav>/* shared blog navigation */</nav>
      <Outlet />  {/* Child route renders here */}
    </div>
  );
}

// app/routes/blog._index.tsx — Blog listing
export default function BlogIndex() {
  return <p>Select a blog post from the list.</p>;
}

Loader 与服务端数据加载

Loader 是在服务端运行的函数,为路由组件提供数据。它们在每个 GET 请求时执行,返回的数据通过 useLoaderData hook 自动提供给组件。Loader 永远不在浏览器中运行。

因为 loader 在服务端运行,你可以安全地访问数据库、调用内部 API、读取环境变量和使用服务端专用依赖。无需中间 API 层或客户端获取库。

import { json } from "@remix-run/node";
import type { LoaderFunctionArgs } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { db } from "~/utils/db.server";

// Loader runs on the server for every GET request
export async function loader({ params }: LoaderFunctionArgs) {
  const post = await db.post.findUnique({
    where: { slug: params.slug },
  });

  if (!post) {
    throw new Response("Not Found", { status: 404 });
  }

  return json({ post });
}

// Component receives loader data via hook
export default function BlogPost() {
  const { post } = useLoaderData<typeof loader>();
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

Action 与表单处理

Action 是 loader 的对应物。Loader 处理数据读取(GET 请求),action 处理数据变更(POST、PUT、PATCH、DELETE)。表单提交时,action 在服务端运行,处理表单数据,然后 Remix 自动重新验证页面上的所有 loader。

这种模式的灵感来自传统 HTML 表单的工作方式。当 JavaScript 可用时,Remix 用客户端过渡增强此流程,但核心行为无需 JS 也能工作。

import { json, redirect } from "@remix-run/node";
import type { ActionFunctionArgs } from "@remix-run/node";
import { Form, useActionData } from "@remix-run/react";

export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData();
  const title = formData.get("title");
  const content = formData.get("content");

  const errors: Record<string, string> = {};
  if (!title) errors.title = "Title is required";
  if (!content) errors.content = "Content is required";

  if (Object.keys(errors).length > 0) {
    return json({ errors }, { status: 400 });
  }

  const post = await db.post.create({
    data: { title: String(title), content: String(content) },
  });

  return redirect("/blog/" + post.slug);
}

export default function NewPost() {
  const actionData = useActionData<typeof action>();
  return (
    <Form method="post">
      <label>
        Title
        <input type="text" name="title" />
      </label>
      {actionData?.errors?.title && (
        <p style={{ color: "red" }}>{actionData.errors.title}</p>
      )}
      <label>
        Content
        <textarea name="content" rows={10} />
      </label>
      {actionData?.errors?.content && (
        <p style={{ color: "red" }}>{actionData.errors.content}</p>
      )}
      <button type="submit">Create Post</button>
    </Form>
  );
}

错误边界与错误处理

Remix 使用 React 错误边界在每个路由层级内置错误处理。每个路由模块可以导出一个 ErrorBoundary 组件来捕获 loader、action 或渲染中的错误。由于路由是嵌套的,子路由的错误只替换页面的那部分。

这种细粒度的错误处理意味着评论区的失败不会导致整个页面崩溃。用户仍然可以与应用的其余部分交互。

import { useRouteError, isRouteErrorResponse } from "@remix-run/react";

// Each route can export its own ErrorBoundary
export function ErrorBoundary() {
  const error = useRouteError();

  if (isRouteErrorResponse(error)) {
    return (
      <div>
        <h2>{error.status} {error.statusText}</h2>
        <p>{error.data}</p>
      </div>
    );
  }

  return (
    <div>
      <h2>Something went wrong</h2>
      <p>{error instanceof Error ? error.message : "Unknown error"}</p>
    </div>
  );
}

// Errors bubble up to nearest ErrorBoundary
// Parent layouts keep working when child errors
// app/routes/dashboard.tsx (layout with error boundary)
// app/routes/dashboard.analytics.tsx (if this errors,
//   only the analytics panel shows error UI,
//   sidebar and header keep working)

资源路由与 API 端点

资源路由是不渲染 UI 组件的路由。它们返回 JSON、XML、图片或 PDF 等非 HTML 响应。通过导出 loader 或 action 而不导出默认组件来创建资源路由。

资源路由适用于构建移动应用消费的 API 端点、生成 RSS feed、创建 webhook 处理器、提供动态图片和处理文件下载。

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

export async function loader({ request }: LoaderFunctionArgs) {
  const url = new URL(request.url);
  const page = Number(url.searchParams.get("page") || "1");
  const posts = await db.post.findMany({
    take: 20,
    skip: (page - 1) * 20,
  });
  return json({ posts, page });
}

// app/routes/rss[.]xml.tsx — RSS feed
export async function loader() {
  const posts = await db.post.findMany({ take: 25 });
  const rss = generateRssFeed(posts);
  return new Response(rss, {
    headers: {
      "Content-Type": "application/xml",
      "Cache-Control": "public, max-age=3600",
    },
  });
}

流式传输与延迟数据

Remix 使用 defer 工具和 Await 组件支持流式响应。你可以立即发送关键数据,而较慢的数据在后台加载。浏览器在数据到达时逐步渲染页面。

当你有快慢数据源混合时,这种模式特别有价值。用户立即看到页面骨架和关键内容,而不太关键的部分显示加载状态并在数据到达时解析。

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

export async function loader() {
  // Critical data — awaited before sending response
  const product = await db.product.findUnique({ where: { id: 1 } });

  // Non-critical data — streams in after initial render
  const reviewsPromise = db.review.findMany({ where: { productId: 1 } });
  const relatedPromise = db.product.findMany({ where: { categoryId: product.categoryId } });

  return defer({
    product,                   // Resolved — available immediately
    reviews: reviewsPromise,    // Promise — streams in later
    related: relatedPromise,    // Promise — streams in later
  });
}

export default function ProductPage() {
  const { product, reviews, related } = useLoaderData<typeof loader>();
  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>

      <Suspense fallback={<p>Loading reviews...</p>}>
        <Await resolve={reviews}>
          {(resolvedReviews) => (
            <ul>
              {resolvedReviews.map((r) => (
                <li key={r.id}>{r.text}</li>
              ))}
            </ul>
          )}
        </Await>
      </Suspense>
    </div>
  );
}

CSS 与样式方案

Remix 支持多种 CSS 策略。你可以使用纯 CSS 文件配合 links 导出、CSS Modules、Tailwind CSS、CSS-in-JS 库等。路由模块中的 links 导出让你仅为需要的路由加载 CSS 文件。

路由作用域的 CSS 是独特优势。当用户离开一个路由时,Remix 会从页面移除该路由的 CSS,防止大型应用中样式累积导致的性能问题。

// Route-scoped CSS with links export
import type { LinksFunction } from "@remix-run/node";
import styles from "~/styles/dashboard.css?url";

export const links: LinksFunction = () => [
  { rel: "stylesheet", href: styles },
];

// Tailwind CSS setup
// 1. Install: npm install -D tailwindcss postcss autoprefixer
// 2. Add tailwind.config.ts
// 3. Import in app/root.tsx:
import stylesheet from "~/tailwind.css?url";

export const links: LinksFunction = () => [
  { rel: "stylesheet", href: stylesheet },
];

// CSS Modules
import styles from "./button.module.css";

export function Button({ children }) {
  return <button className={styles.primary}>{children}</button>;
}

认证模式

Remix 中的认证通常使用基于 Cookie 的会话,符合 Web 标准。Remix 提供支持 Cookie、基于文件的会话和自定义会话后端的会话存储 API。

模式很直观:创建会话存储实例,在 loader 中读取会话检查认证,重定向未认证用户,在 action 中登录后设置会话。

// app/utils/session.server.ts
import { createCookieSessionStorage, redirect } from "@remix-run/node";

const sessionStorage = createCookieSessionStorage({
  cookie: {
    name: "__session",
    httpOnly: true,
    maxAge: 60 * 60 * 24 * 7, // 1 week
    path: "/",
    sameSite: "lax",
    secrets: [process.env.SESSION_SECRET!],
    secure: process.env.NODE_ENV === "production",
  },
});

export async function requireUser(request: Request) {
  const session = await sessionStorage.getSession(
    request.headers.get("Cookie")
  );
  const userId = session.get("userId");
  if (!userId) throw redirect("/login");
  return userId;
}

export async function createUserSession(userId: string, redirectTo: string) {
  const session = await sessionStorage.getSession();
  session.set("userId", userId);
  return redirect(redirectTo, {
    headers: {
      "Set-Cookie": await sessionStorage.commitSession(session),
    },
  });
}

部署目标

Remix 最大的优势之一是运行时灵活性。因为构建在 Web Fetch API 标准上,它可以在任何支持此 API 的地方运行。

部署到 Vercel

# Create Remix app with Vercel template
npx create-remix@latest --template remix-run/remix/templates/vercel

# remix.config.js
module.exports = {
  serverBuildTarget: "vercel",
  server: process.env.NODE_ENV === "development"
    ? undefined
    : "./server.js",
};

# Deploy
npx vercel --prod

部署到 Cloudflare Workers

# Create Remix app for Cloudflare Workers
npx create-remix@latest --template remix-run/remix/templates/cloudflare-workers

# wrangler.toml
name = "my-remix-app"
main = "./build/index.js"
compatibility_date = "2024-01-01"

[site]
bucket = "./public"

# Access Cloudflare bindings in loaders
# export async function loader({ context }) {
#   const kv = context.env.MY_KV;
#   const value = await kv.get("key");
#   return json({ value });
# }

# Deploy
npx wrangler deploy

部署到 Fly.io

# Create Remix app with Fly.io template
npx create-remix@latest --template remix-run/remix/templates/fly

# fly.toml
app = "my-remix-app"
primary_region = "iad"

[http_service]
  internal_port = 3000
  force_https = true
  auto_stop_machines = true
  auto_start_machines = true

# Dockerfile (included in template)
# Multi-stage build for minimal image size

# Deploy
fly launch
fly deploy

从 Next.js 迁移到 Remix

从 Next.js 迁移到 Remix 涉及几个概念转变。数据加载模型从 getServerSideProps/getStaticProps 变为 loader,API 路由变为 action 和资源路由。

  • 1. 用 loader 函数替换 getServerSideProps——数据通过 useLoaderData 而非页面 props 流转。
  • 2. 将 API 路由转换为 action(变更)或资源路由(GET 端点)。
  • 3. 将 _app.tsx 和 _document.tsx 逻辑移入 root.tsx——Remix 使用单个根路由作为文档结构。
  • 4. 用 Remix Link 组件替换 next/link,用 useNavigate 或 useLocation 替换 next/router。
  • 5. 用标准 img 标签或第三方图片优化服务替换 next/image——Remix 不包含内置图片优化。
  • 6. 将基于 useEffect 的数据获取转换为 loader(初始数据)和 useFetcher(页面内变更)。
// Next.js — getServerSideProps
export async function getServerSideProps({ params }) {
  const post = await db.post.findUnique({ where: { slug: params.slug } });
  return { props: { post } };
}
export default function Post({ post }) {
  return <h1>{post.title}</h1>;
}

// Remix equivalent — loader
import { json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";

export async function loader({ params }) {
  const post = await db.post.findUnique({ where: { slug: params.slug } });
  if (!post) throw new Response("Not Found", { status: 404 });
  return json({ post });
}
export default function Post() {
  const { post } = useLoaderData<typeof loader>();
  return <h1>{post.title}</h1>;
}

最佳实践与性能

遵循这些实践将帮助你构建高性能、可维护的 Remix 应用。

  • 使用 loader 进行所有初始数据加载,而非 useEffect 或客户端获取库。
  • 利用嵌套路由并行化数据加载并提供细粒度错误边界。
  • 使用原生 Form 组件而非 fetch 调用进行变更——它自动启用渐进增强。
  • 在 loader 中通过 headers 导出实现 HTTP 缓存头以启用 CDN 和浏览器缓存。
  • 对非关键数据使用 defer,允许它在初始页面渲染后加载。
  • 认证优先使用基于 Cookie 的会话,而非存储在 localStorage 中的 JWT 令牌。
  • 使用资源路由作为 API 端点,而非创建单独的 API 服务器。
  • 利用 Link 组件的 prefetch 属性在悬停或进入视口时预加载路由数据。
  • 保持路由模块的专注——将共享逻辑提取到工具文件,共享 UI 提取到组件文件。
  • 使用 useFetcher 进行不应引起导航的页面内变更和数据加载。
// HTTP caching with headers export
import type { HeadersFunction } from "@remix-run/node";

export const headers: HeadersFunction = () => ({
  "Cache-Control": "public, max-age=300, s-maxage=3600",
});

// Prefetching links for faster navigation
import { Link } from "@remix-run/react";

function Nav() {
  return (
    <nav>
      <Link to="/dashboard" prefetch="intent">
        Dashboard
      </Link>
      <Link to="/settings" prefetch="viewport">
        Settings
      </Link>
    </nav>
  );
}

// useFetcher for non-navigation mutations
import { useFetcher } from "@remix-run/react";

function LikeButton({ postId }) {
  const fetcher = useFetcher();
  const isLiking = fetcher.state !== "idle";
  return (
    <fetcher.Form method="post" action="/api/like">
      <input type="hidden" name="postId" value={postId} />
      <button type="submit" disabled={isLiking}>
        {isLiking ? "Liking..." : "Like"}
      </button>
    </fetcher.Form>
  );
}

常见问题

Remix 用来做什么?

Remix 用于构建全栈 React Web 应用,包括 SaaS 平台、电商网站、内容管理系统、仪表盘等。其 Web 标准方式使其特别适合需要在不同网络条件下可靠工作的应用。

Remix 和 Next.js 有什么不同?

Remix 专注于 Web 标准和渐进增强,使用 HTML 表单进行变更,HTTP 缓存进行性能优化。Next.js 提供更多渲染策略(SSG、ISR、SSR、RSC)并包含内置图片优化。Remix 的嵌套路由支持并行数据加载,Next.js 使用 React Server Components 进行流式传输。

Remix 在禁用 JavaScript 时能工作吗?

可以。Remix 应用从服务端渲染 HTML 和标准表单提交的基线渐进增强。导航、表单提交和核心功能无需客户端 JavaScript 即可工作。

什么是 Remix 中的 Loader?

Loader 是从路由模块导出的服务端函数,在每个 GET 请求时运行。它从数据库、API 或其他源获取数据并返回给路由组件。Loader 永远不在浏览器中运行。

什么是 Remix 中的 Action?

Action 是处理非 GET 请求(POST、PUT、PATCH、DELETE)的服务端函数。Action 处理表单提交和变更,然后 Remix 自动重新验证页面上的所有 loader 以反映更新的数据。

可以将 Remix 部署到 Cloudflare Workers 吗?

可以。Remix 有官方 Cloudflare 适配器。因为 Remix 构建在 Web Fetch API 上,它原生运行在边缘运行时,提供全球分发、低延迟和 Cloudflare KV、D1、R2 等服务的访问。

Remix 中的嵌套路由如何工作?

嵌套路由将 URL 段映射到按层次排列的路由模块。父路由渲染带有 Outlet 组件的布局,子路由出现在其中。每个嵌套路由有自己的 loader、action 和错误边界,所有匹配路由的数据并行加载。

Remix 在与 React Router 合并后还在维护吗?

是的。Remix 团队将框架功能合并到了 React Router v7 中,这意味着 React Router 现在包含所有 Remix 功能。团队在 React Router 名称下继续积极开发。

𝕏 Twitterin LinkedIn
这篇文章有帮助吗?

保持更新

获取每周开发技巧和新工具通知。

无垃圾邮件,随时退订。

试试这些相关工具

{ }JSON Formatter%20URL Encoder/Decoder

相关文章

Next.js 高级指南:App Router、服务端组件、数据获取、中间件与性能优化

完整的 Next.js 高级指南,涵盖 App Router 架构、React 服务端组件、流式 SSR、数据获取模式、中间件、路由处理器、并行和拦截路由、缓存策略、ISR、图片优化和部署最佳实践。

React 设计模式指南:复合组件、自定义 Hook、HOC、Render Props 与状态机

完整的 React 设计模式指南,涵盖复合组件、render props、自定义 hooks、高阶组件、Provider 模式、状态机、受控与非受控、组合模式、观察者模式、错误边界和模块模式。

Web 性能优化:2026 Core Web Vitals 指南

全面的 Web 性能优化和 Core Web Vitals 指南。学习如何通过图片、JavaScript、CSS 和缓存的实用技术改善 LCP、INP 和 CLS。