资源路由
On this page

资源路由

¥Resource Routes

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

¥Resource Routes are not part of your application UI but are still part of your application. They can send any kind of Response.

Remix 中的大多数路由都是 UI 路由,或者说是实际渲染组件的路由。但路由并不总是需要渲染组件。在少数情况下,你需要将路由用作网站的通用端点。以下是一些示例:

¥Most routes in Remix are UI Routes, or routes that actually render a component. But routes don't always have to render components. There are a handful of cases where you want to use route as a general-purpose endpoint to your website. Here are a few examples:

  • 适用于移动应用的 JSON API,该应用使用 Remix UI 复用服务器端代码。

    ¥JSON API for a mobile app that reuses server-side code with the Remix UI

  • 动态生成 PDF

    ¥Dynamically generating PDFs

  • 为博客文章或其他页面动态生成社交图片

    ¥Dynamically generating social images for blog posts or other pages

  • 用于 Stripe 或 GitHub 等其他服务的 Webhook

    ¥Webhooks for other services like Stripe or GitHub

  • 一个 CSS 文件,用于动态渲染用户首选主题的自定义属性。

    ¥a CSS file that dynamically renders custom properties for a user's preferred theme

创建资源路由

¥Creating Resource Routes

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

¥If a route doesn't export a default component, it can be used as a Resource Route. If called with GET, the loader's response is returned and none of the parent route loaders are called either (because those are needed for the UI, but this is not the UI). If called with POST, the action's response is called.

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

¥For example, consider a UI Route that renders a report, note the link:

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 版本。为了实现这一点,我们可以在其下方创建一个资源路由。请注意,它没有任何组件:这使其成为资源路由。

¥It's linking to a PDF version of the page. To make this work, we can create a Resource Route below it. Notice that it has no component: that makes it a Resource Route.

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 文件。

¥When the user clicks the link from the UI route, they will navigate to the PDF.

链接到资源路由

¥Linking to Resource Routes

务必在任何资源路由链接中使用 reloadDocument

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

¥There's a subtle detail to be aware of when linking to resource routes. You need to link to it with <Link reloadDocument> or a plain <a href>. If you link to it with a normal <Link to="pdf"> without reloadDocument, then the resource route will be treated as a UI route. Remix will try to get the data with fetch and render the component. Don't sweat it too much, you'll get a helpful error message if you make this mistake.

URL 转义

¥URL Escaping

你可能需要在资源路由中添加文件扩展名。这比较棘手,因为 Remix 的路由文件命名约定之一是 . 变成 /,这样你就可以嵌套 URL 而不嵌套 UI。

¥You'll probably want to add a file extension to your resource routes. This is tricky because one of Remix's route file naming conventions is that . becomes / so you can nest the URL without nesting the UI.

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

¥To add a . to a route's path, use the [] escape characters. Our PDF route file name would change like so:

# 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

处理不同的请求方法

¥Handling different request methods

要处理 GET 请求,请导出加载器函数:

¥To handle GET requests export a loader function:

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 请求,请导出操作函数:

¥To handle POST, PUT, PATCH or DELETE requests export an action function:

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,当新的提交推送到存储库时,它会接收来自 GitHub 的通知:

¥Resource routes can be used to handle webhooks. For example, you can create a webhook that receives notifications from GitHub when a new commit is pushed to a repository:

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);
};
Remix v2.17 中文网 - 粤ICP备13048890号