headers

headers

每个路由都可以定义自己的 HTTP 标头。常用的标头之一是 Cache-Control 标头,它指示浏览器和 CDN 缓存页面的缓存位置和缓存时长。

¥Each route can define its own HTTP headers. One of the common headers is the Cache-Control header that indicates to browser and CDN caches where and for how long a page is able to be cached.

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

export const headers: HeadersFunction = ({
  actionHeaders,
  errorHeaders,
  loaderHeaders,
  parentHeaders,
}) => ({
  "X-Stretchy-Pants": "its for fun",
  "Cache-Control": "max-age=300, s-maxage=3600",
});

通常,你的数据比路由模块更能指示缓存时长(数据往往比标记更具动态性),因此 actionloader 的标头也会传递给 headers()

¥Usually your data is a better indicator of your cache duration than your route module (data tends to be more dynamic than markup), so the action's & loader's headers are passed in to headers() too:

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

export const headers: HeadersFunction = ({
  loaderHeaders,
}) => ({
  "Cache-Control": loaderHeaders.get("Cache-Control"),
});

注意:actionHeadersloaderHeadersWeb Fetch API Headers 类的实例。

¥Note: actionHeaders & loaderHeaders are an instance of the Web Fetch API Headers class.

如果 actionloader 抛出了 Response 错误,并且我们正在渲染边界,则抛出的 Response 中的任何标头都将在 errorHeaders 中可用。这允许你从抛出父级错误边界的子加载器访问标头。

¥If an action or a loader threw a Response and we're rendering a boundary, any headers from the thrown Response will be available in errorHeaders. This allows you to access headers from a child loader that threw in a parent error boundary.

嵌套路由

¥Nested Routes

由于 Remix 具有嵌套路由,因此当嵌套路由匹配时,需要争夺标头。默认行为是 Remix 仅利用其在可渲染匹配项中找到的最深 headers 函数的结果标头(如果出现错误,则最多包含边界路由)。

¥Because Remix has nested routes, there's a battle of the headers to be won when nested routes match. The default behavior is that Remix only leverages the resulting headers from the deepest headers function it finds in the renderable matches (up to and including the boundary route if an error is present).

├── users.tsx
├── users.$userId.tsx
└── users.$userId.profile.tsx

如果我们查看 /users/123/profile,则渲染三个路由:

¥If we are looking at /users/123/profile then three routes are rendering:

<Users>
  <UserId>
    <Profile />
  </UserId>
</Users>

如果用户正在查看 /users/123/profile,而 users.$userId.profile.tsx 没有导出 headers 函数,那么 Remix 将使用 users.$userId.tsxheaders 函数的返回值。如果该文件未导出任何内容,那么它将使用 users.tsx 中导出的内容,依此类推。

¥If a user is looking at /users/123/profile and users.$userId.profile.tsx does not export a headers function, then Remix will use the return value of users.$userId.tsx's headers function. If that file doesn't export one, then it will use the result of the one in users.tsx, and so on.

如果三个模块都定义了 headers,则最深的模块将获胜,在本例中为 users.$userId.profile.tsx。但是,如果你的 users.$userId.profile.tsxloader 抛出异常并冒泡到 users.$userId.tsx 的边界,则 - 那么 users.$userId.tsxheaders 函数将被使用,因为它是叶子渲染路由。

¥If all three define headers, the deepest module wins, in this case users.$userId.profile.tsx. However, if your users.$userId.profile.tsx's loader threw and bubbled to a boundary in users.$userId.tsx - then users.$userId.tsx's headers function would be used as it is the leaf-rendered route.

我们不希望你的响应中出现意外的标头,因此如果你愿意,你可以自行合并它们。Remix 将 parentHeaders 传递给你的 headers 函数。因此,users.tsx 的标头会传递给 users.$userId.tsx,然后 users.$userId.tsxheaders 会传递给 users.$userId.profile.tsxheaders

¥We don't want surprise headers in your responses, so it's your job to merge them if you'd like. Remix passes in the parentHeaders to your headers function. So users.tsx headers get passed to users.$userId.tsx, and then users.$userId.tsx's headers are passed to users.$userId.profile.tsx's headers.

总之,Remix 给了你一把巨大的“枪”,让你可以随心所欲地攻击自己。你需要注意不要从比父路由更具侵略性的子路由模块发送 Cache-Control。以下是一些代码,用于在这些情况下选择最不激进的缓存:

¥That is all to say that Remix has given you a huge gun with which to shoot your foot. You need to be careful not to send a Cache-Control from a child route module that is more aggressive than a parent route. Here's some code that picks the least aggressive caching in these cases:

import type { HeadersFunction } from "@remix-run/node"; // or cloudflare/deno
import parseCacheControl from "parse-cache-control";

export const headers: HeadersFunction = ({
  loaderHeaders,
  parentHeaders,
}) => {
  const loaderCache = parseCacheControl(
    loaderHeaders.get("Cache-Control")
  );
  const parentCache = parseCacheControl(
    parentHeaders.get("Cache-Control")
  );

  // take the most conservative between the parent and loader, otherwise
  // we'll be too aggressive for one of them.
  const maxAge = Math.min(
    loaderCache["max-age"],
    parentCache["max-age"]
  );

  return {
    "Cache-Control": `max-age=${maxAge}`,
  };
};

综上所述,你可以通过不在父路由中定义标头,而仅在叶路由中定义标头来避免整个问题。每个可以直接访问的布局都可能包含一个 "索引路由" 文件。如果你只在叶路由上定义标头,而不是在父路由上定义,那么你永远不必担心合并标头。

¥All that said, you can avoid this entire problem by not defining headers in parent routes and only in leaf routes. Every layout that can be visited directly will likely have an "index route". If you only define headers on your leaf routes, not your parent routes, you will never have to worry about merging headers.

请注意,你还可以在 entry.server.tsx 文件中添加标头,用于全局内容,例如:

¥Note that you can also add headers in your entry.server.tsx file for things that should be global, for example:

import type {
  AppLoadContext,
  EntryContext,
} from "@remix-run/node"; // or cloudflare/deno
import { RemixServer } from "@remix-run/react";
import { renderToString } from "react-dom/server";

export default function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext,
  loadContext: AppLoadContext
) {
  const markup = renderToString(
    <RemixServer context={remixContext} url={request.url} />
  );

  responseHeaders.set("Content-Type", "text/html");
  responseHeaders.set("X-Powered-By", "Hugs");

  return new Response("<!DOCTYPE html>" + markup, {
    headers: responseHeaders,
    status: responseStatusCode,
  });
}

请记住,执行此操作将应用于所有文档请求,但不适用于 data 请求(例如,对于客户端转换)。对于这些,请使用 handleDataRequest

¥Keep in mind that doing this will apply to all document requests, but does not apply to data requests (for client-side transitions, for example). For those, use handleDataRequest.

Remix v2.17 中文网 - 粤ICP备13048890号