资源路由
本页内容

资源路由

资源路由不是您应用程序 UI 的一部分,但仍然是您应用程序的一部分。它们可以发送任何类型的响应。

Remix 中的大多数路由都是 UI 路由,或实际呈现组件的路由。但路由并不总是必须呈现组件。在某些情况下,您希望将路由用作网站的一般用途端点。以下是一些示例

  • 与 Remix UI 重用服务器端代码的移动应用程序的 JSON API
  • 动态生成 PDF
  • 为博客文章或其他页面动态生成社交图片
  • 针对 Stripe 或 GitHub 等其他服务的 Webhook
  • 动态呈现用户首选主题的自定义属性的 CSS 文件

创建资源路由

如果路由没有导出默认组件,则它可以用作资源路由。如果使用 GET 调用,则返回加载程序的响应,并且也不调用任何父路由加载程序(因为这些加载程序是 UI 所需的,但这并非 UI)。如果使用 POST 调用,则调用操作的响应。

例如,考虑渲染报告的 UI 路由,请注意链接

export async function loader({
  params,
}: LoaderFunctionArgs) {
  return json(await getReport(params.id));
}

export default function Report() {
  const report = useLoaderData<typeof loader>();
  return (
    <div>
      <h1>{report.name}</h1>
      <Link to="pdf" reloadDocument>
        View as PDF
      </Link>
      {/* ... */}
    </div>
  );
}

它链接到页面的 PDF 版本。为了使此工作正常进行,我们可以在它下面创建一个资源路由。请注意,它没有组件:这使其成为资源路由。

export async function loader({
  params,
}: LoaderFunctionArgs) {
  const report = await getReport(params.id);
  const pdf = await generateReportPDF(report);
  return new Response(pdf, {
    status: 200,
    headers: {
      "Content-Type": "application/pdf",
    },
  });
}

当用户从 UI 路由中单击链接时,他们将导航到 PDF。

链接到资源路由

您必须在所有指向资源路由的链接上使用 reloadDocument

在链接到资源路由时需要注意一个微妙的细节。您需要使用 <Link reloadDocument> 或普通的 <a href> 链接到它。如果您使用没有 reloadDocument 的普通 <Link to="pdf"> 链接到它,则资源路由将被视为 UI 路由。Remix 将尝试使用 fetch 获取数据并渲染组件。不必过分担心,如果您犯了这个错误,您会收到一条有用的错误消息。

URL 转义

您可能希望在资源路由中添加文件扩展名。这很棘手,因为 Remix 的路由文件名约定之一是 . 变成 /,因此您可以在不嵌套 UI 的情况下嵌套 URL。

要将 . 添加到路由的路径,请使用 [] 转义字符。我们的 PDF 路由文件名将更改如下

# original
# /reports/123/pdf
app/routes/reports.$id.pdf.ts

# with a file extension
# /reports/123.pdf
app/routes/reports.$id[.pdf].ts

# or like this, the resulting URL is the same
app/routes/reports.$id[.]pdf.ts

处理不同的请求方法

要处理 GET 请求,请导出一个加载程序函数

import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno

export const loader = async ({
  request,
}: LoaderFunctionArgs) => {
  // handle "GET" request

  return json({ success: true }, 200);
};

要处理 POSTPUTPATCHDELETE 请求,请导出一个操作函数

import type { ActionFunctionArgs } from "@remix-run/node"; // or cloudflare/deno

export const action = async ({
  request,
}: ActionFunctionArgs) => {
  switch (request.method) {
    case "POST": {
      /* handle "POST" */
    }
    case "PUT": {
      /* handle "PUT" */
    }
    case "PATCH": {
      /* handle "PATCH" */
    }
    case "DELETE": {
      /* handle "DELETE" */
    }
  }
};

Webhook

资源路由可用于处理 webhook。例如,您可以创建一个 webhook,该 webhook 在将新提交推送到存储库时接收来自 GitHub 的通知

import crypto from "node:crypto";

import type { ActionFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno

export const action = async ({
  request,
}: ActionFunctionArgs) => {
  if (request.method !== "POST") {
    return json({ message: "Method not allowed" }, 405);
  }
  const payload = await request.json();

  /* Validate the webhook */
  const signature = request.headers.get(
    "X-Hub-Signature-256"
  );
  const generatedSignature = `sha256=${crypto
    .createHmac("sha256", process.env.GITHUB_WEBHOOK_SECRET)
    .update(JSON.stringify(payload))
    .digest("hex")}`;
  if (signature !== generatedSignature) {
    return json({ message: "Signature mismatch" }, 401);
  }

  /* process the webhook (e.g. enqueue a background job) */

  return json({ success: true }, 200);
};
文档和示例在以下许可下发布 MIT