Use Cases (更新: 2026/6/3)

用 Claude Code 开发 Remix/React Router:loader 与 action 实战

用 Claude Code 构建 Remix 风格应用:loader、action、错误边界、SEO 与审查提示。

用 Claude Code 开发 Remix/React Router:loader 与 action 实战

2026 年理解 Remix 的方式

现在谈 Remix 开发,不能只看旧的 Remix v2 示例。Remix 的很多框架能力已经进入 React Router v7,官方 Remix 文档也会把最新框架能力指向 React Router。因此,用 Claude Code 写 Remix 风格应用时,最好先说明项目是“现有 Remix v2 维护”,还是“React Router v7 Framework Mode 的新项目”。如果只说“帮我写 Remix”,Claude Code 很容易混用旧的@remix-run/*导入、新的react-router导入,以及普通前端fetch写法。

给初学者的理解方式是:Remix/React Router 的核心,是按路由放置loaderaction,让服务器逻辑和 UI 靠得更近。loader负责页面显示前读取数据,action负责表单提交和数据变更,组件负责渲染,错误边界负责把失败限制在当前路由。这样做的好处是,代码生成后可以直接要求 Claude Code 审查“这个路由的读取、写入、错误和 SEO 是否完整”。

本文以 React Router v7 Framework Mode 为前提,构建一个可以复制的迷你应用:商品列表、商品详情和联系表单。示例覆盖三个常见场景:用loader读取数据、用action处理表单、用ErrorBoundary处理 404 和异常,并加入 SEO、审查提示、常见坑和商业转化入口。本文更新时参考了Remix DocsReact Router v7 发布说明Route Module docsError Boundaries docsForm API

flowchart LR
  A["URL / route"] --> B["loader 读取数据"]
  B --> C["UI component 渲染"]
  C --> D["Form 提交"]
  D --> E["action 校验并写入"]
  E --> B
  B --> F["ErrorBoundary 处理读取失败"]
  E --> F

先决定使用哪种模式

React Router v7 可以用 Declarative、Data、Framework 三种方式。接近 Remix 体验的是 Framework Mode:路由模块、服务端渲染、loaderaction、类型生成和部署结构都在框架内组织。如果是新业务系统,这通常是最清晰的选择。

场景建议给 Claude Code 的说法
新业务应用React Router v7 Framework Mode使用create-react-router和 route module
现有 Remix v2保持现有导入方式按当前@remix-run/*风格修改
现有 React SPA先局部引入 Data Mode先把一个页面迁到 loader/action
带表单的营销页Framework 或 SPA Mode先确认是否需要 SSR 和服务端 action

这个决定非常重要。Claude Code 写得很快,但如果边界不清,它会把不同年代的写法合在一起。本文示例全部按新 React Router v7 Framework Mode 写,同时用 Remix 风格解释。

创建可运行的小项目

先创建项目,再添加路由。商品列表、商品详情和联系表单足够验证核心模式。

npx create-react-router@latest rr-claude-shop
cd rr-claude-shop
npm install
npm run dev
// app/routes.ts
import { type RouteConfig, index, route } from "@react-router/dev/routes";

export default [
  index("routes/home.tsx"),
  route("products", "routes/products.tsx"),
  route("products/:productId", "routes/products.$productId.tsx"),
  route("contact", "routes/contact.tsx"),
] satisfies RouteConfig;

数据层先放在服务器专用文件中,之后可以替换成数据库或内部 API。

// app/data/products.server.ts
export type Product = {
  id: string;
  name: string;
  description: string;
  price: number;
};

const products: Product[] = [
  {
    id: "starter",
    name: "Claude Code Starter Kit",
    description: "Small prompts and review checklists for the first team rollout.",
    price: 9800,
  },
  {
    id: "team",
    name: "Team Workflow Pack",
    description: "Route reviews, test prompts, and deployment checklists for teams.",
    price: 29800,
  },
];

const leads: Array<{ id: string; email: string; message: string }> = [];

export async function listProducts(query = "") {
  const q = query.trim().toLowerCase();
  if (!q) return products;
  return products.filter((product) =>
    `${product.name} ${product.description}`.toLowerCase().includes(q),
  );
}

export async function getProduct(productId: string) {
  return products.find((product) => product.id === productId) ?? null;
}

export async function saveLead(input: { email: string; message: string }) {
  const lead = { id: crypto.randomUUID(), ...input };
  leads.push(lead);
  return lead;
}

场景1:用 loader 读取列表

loader适合读取页面初次渲染所需的数据。它比在组件里用useEffect再请求更容易管理初始状态、错误和 SEO。下面的列表页读取查询参数,在服务端过滤商品,并在同一路由中放置titlemeta

// app/routes/products.tsx
import { Form, Link, useLoaderData, useNavigation } from "react-router";
import { listProducts } from "~/data/products.server";

export async function loader({ request }: { request: Request }) {
  const url = new URL(request.url);
  const q = url.searchParams.get("q") ?? "";
  const products = await listProducts(q);
  return { q, products };
}

export default function ProductsRoute() {
  const { q, products } = useLoaderData<typeof loader>();
  const navigation = useNavigation();
  const searching = navigation.location?.pathname === "/products";

  return (
    <main>
      <title>Products | Claude Code Shop</title>
      <meta
        name="description"
        content="Browse Claude Code workflow products and team enablement kits."
      />
      <h1>Products</h1>
      <Form method="get" role="search">
        <label>
          Search
          <input name="q" defaultValue={q} placeholder="workflow" />
        </label>
        <button type="submit">{searching ? "Searching..." : "Search"}</button>
      </Form>
      <ul>
        {products.map((product) => (
          <li key={product.id}>
            <Link to={`/products/${product.id}`}>{product.name}</Link>
            <p>{product.description}</p>
            <strong>{product.price.toLocaleString()} JPY</strong>
          </li>
        ))}
      </ul>
    </main>
  );
}

场景2:用 action 处理表单

action负责表单提交和数据写入。<Form>在 JavaScript 加载前也能作为普通 HTML 表单工作,加载后再由 React Router 增强。请让 Claude Code 同时处理服务端校验、字段错误、提交中状态和成功状态。

// app/routes/contact.tsx
import { Form, useActionData, useNavigation } from "react-router";
import { saveLead } from "~/data/products.server";

type ActionData =
  | { ok: true; leadId: string }
  | { ok: false; errors: { email?: string; message?: string } };

export async function action({ request }: { request: Request }): Promise<ActionData> {
  const formData = await request.formData();
  const email = String(formData.get("email") ?? "").trim();
  const message = String(formData.get("message") ?? "").trim();
  const errors: { email?: string; message?: string } = {};

  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
    errors.email = "Enter a valid email address.";
  }
  if (message.length < 20) {
    errors.message = "Tell us at least 20 characters about your situation.";
  }
  if (Object.keys(errors).length > 0) {
    return { ok: false, errors };
  }

  const lead = await saveLead({ email, message });
  return { ok: true, leadId: lead.id };
}

真实项目还需要 CSRF、限流、垃圾信息过滤、邮件通知和 CRM 同步。提示词不要只写“做一个表单”,而要写“用 action 校验输入、显示字段错误、提交中禁用按钮、成功后显示结果”。

场景3:用错误边界控制失败范围

如果商品不存在,当前商品详情路由应该显示 404,而不是让整个页面崩掉。路由级错误边界正是 Remix 风格设计的优势。

// app/routes/products.$productId.tsx
import { data, isRouteErrorResponse, Link, useLoaderData } from "react-router";
import { getProduct } from "~/data/products.server";

export async function loader({ params }: { params: { productId?: string } }) {
  const productId = params.productId;
  if (!productId) {
    throw data("Missing product id", { status: 400 });
  }
  const product = await getProduct(productId);
  if (!product) {
    throw data("Product not found", { status: 404 });
  }
  return { product };
}

export function ErrorBoundary({ error }: { error: unknown }) {
  if (isRouteErrorResponse(error)) {
    return (
      <main>
        <h1>{error.status === 404 ? "Product not found" : "Could not load product"}</h1>
        <p>{error.data}</p>
        <Link to="/products">Back to products</Link>
      </main>
    );
  }
  return <main><h1>Unexpected error</h1><p>Please try again later.</p></main>;
}

审查时要确认两点:404、400 这种预期错误要给用户下一步;未知错误不能把error.stack、环境变量、数据库信息暴露到浏览器。

SEO 与内部链接

Remix/React Router 的 route module 很适合把 SEO 和页面逻辑放在一起。列表页写清楚“可以比较什么”,详情页把商品名放在标题前面,联系页说明可以咨询什么。React 19 之后的新项目可以优先使用组件中的<title><meta>,旧项目如果已使用meta()导出,也应保持一致。

内部链接也要在内容设计中考虑。本文可以连接到Claude Code React 开发Claude Code API 开发错误处理模式,让读者从一个路由示例自然进入更大的工程主题。

Claude Code 审查提示词

请审查这个 React Router v7 Framework Mode 的 route module。
重点检查:
1. loader 是否返回了秘密信息、内部字段或过大的数据
2. action 是否有服务端校验、字段错误、提交中状态和成功状态
3. ErrorBoundary 是否区分 404/400/未知错误,且没有泄露 stack
4. Form 是否在没有 JavaScript 时仍可基本提交
5. title/description 是否唯一且适合搜索
6. 是否混用了旧 Remix v2 import 与 React Router v7 import
请按严重程度列出问题,并给出最小修复差异。

常见失败

第一,混用旧导入和新导入。现有 Remix v2 项目应尊重原结构,新项目则应跟随 React Router v7 文档。第二,loader返回过多数据。凡是不需要显示给用户的成本、内部备注、管理员标记,都不该进入返回值。第三,只相信浏览器校验。type="email"不是安全边界,action必须重新校验。第四,把错误边界写成调试页面,把 stack 直接显示给用户。第五,把 SEO 当成自动发生的事情,导致重复标题和空 description。第六,不理解 action 后的 loader 重新验证,导致一个小表单触发太多重请求。

转化路径与实际结果

这个主题适合作为商业入口,因为它连接了表单、SEO、数据读取、错误处理和部署,读者通常已经接近真实项目。个人练习可以先看免费速查表,团队需要 React Router v7、Remix v2 维护、表单设计和 Claude Code 审查流程时,可以进入Claude Code 培训与导入咨询。需要可复用提示词和清单时,可以查看产品列表

Masa 在本地小项目中测试了商品列表搜索、商品详情 404、联系表单校验和成功显示。模糊地说“用 Remix 做表单”时,Claude Code 容易混入旧导入和客户端fetch。明确说“Framework Mode、route module、loader/action、ErrorBoundary、title/meta”后,生成的差异更小,也更容易审查。结论是:先固定路由职责,再让 Claude Code 写代码,后续返工会少很多。

#Claude Code #Remix #React #full-stack #frontend
免费

免费 PDF: Claude Code 速查表

输入邮箱即可获取一页 PDF,整理常用命令、审查习惯和安全工作流。

我们会妥善保护你的信息,不发送垃圾邮件。

把 Claude Code 变成真正能带来结果的工作流

先领取中文说明的免费 PDF,再进入英文商品页选择合适的教材。如果你需要团队落地、流程设计或内容变现支持,也可以直接咨询。

Masa

关于作者

Masa

专注 Claude Code 实务流程、团队导入和内容转化的工程师。