React Router v7 已发布。 查看文档
meta
在此页面

meta

通过 meta 导出,你可以为应用程序中的每个路由添加元数据 HTML 标签。这些标签对于搜索引擎优化 (SEO) 和浏览器指令以确定某些行为至关重要。它们还可以被社交媒体网站用来显示你应用程序的丰富预览。

meta 函数应返回一个 MetaDescriptor 对象数组。这些对象与 HTML 标签一一对应。因此,这个 meta 函数

export const meta: MetaFunction = () => {
  return [
    { title: "Very cool app | Remix" },
    {
      property: "og:title",
      content: "Very cool app",
    },
    {
      name: "description",
      content: "This app is the best",
    },
  ];
};

会生成此 HTML

<title>Very cool app | Remix</title>
<meta property="og:title" content="Very cool app" />;
<meta name="description" content="This app is the best" />

默认情况下,元描述符在大多数情况下将呈现一个 <meta> 标签。以下两种情况是例外:

  • { title } 呈现一个 <title> 标签
  • { "script:ld+json" } 呈现一个 <script type="application/ld+json"> 标签,其值应为可序列化的对象,该对象将被字符串化并注入到标签中。
export const meta: MetaFunction = () => {
  return [
    {
      "script:ld+json": {
        "@context": "https://schema.org",
        "@type": "Organization",
        name: "Remix",
        url: "https://remix.org.cn",
      },
    },
  ];
};

通过将 tagName 属性设置为 "link",元描述符也可以呈现一个 <link> 标签。这对于与 SEO 相关的 <link> 标签(如 canonical URL)非常有用。对于样式表和网站图标等资产链接,应使用links 导出

export const meta: MetaFunction = () => {
  return [
    {
      tagName: "link",
      rel: "canonical",
      href: "https://remix.org.cn",
    },
  ];
};

meta 函数参数

location

这是当前的路由器 Location 对象。这对于为特定路径或查询参数的路由生成标签很有用。

export const meta: MetaFunction = ({ location }) => {
  const searchQuery = new URLSearchParams(
    location.search
  ).get("q");
  return [{ title: `Search results for "${searchQuery}"` }];
};

matches

这是当前路由匹配项的数组。你可以访问许多内容,特别是来自父匹配项和数据中的 meta。

matches 的接口类似于 useMatches 的返回值,但每个匹配项都会包含其 meta 函数的输出。这对于在路由层级结构中合并元数据非常有用。

data

这是来自你的路由 loader 的数据。

export async function loader({
  params,
}: LoaderFunctionArgs) {
  return json({
    task: await getTask(params.projectId, params.taskId),
  });
}

export const meta: MetaFunction<typeof loader> = ({
  data,
}) => {
  return [{ title: data.task.name }];
};

params

路由的 URL 参数。请参阅路由指南中的动态片段

error

触发错误边界的抛出错误将传递给 meta 函数。这对于为错误页面生成元数据非常有用。

export const meta: MetaFunction = ({ error }) => {
  return [{ title: error ? "oops!" : "Actual title" }];
};

从父路由加载器访问数据

除了当前路由的数据外,通常你还希望访问路由层级结构中更高层的路由的数据。你可以在 matches 中通过其路由 ID 查找它。

import type { loader as projectDetailsLoader } from "./project.$pid";

export async function loader({
  params,
}: LoaderFunctionArgs) {
  return json({ task: await getTask(params.tid) });
}

export const meta: MetaFunction<
  typeof loader,
  { "routes/project.$pid": typeof projectDetailsLoader }
> = ({ data, matches }) => {
  const project = matches.find(
    (match) => match.id === "routes/project.$pid"
  ).data.project;
  const task = data.task;
  return [{ title: `${project.name}: ${task.name}` }];
};

meta 和嵌套路由的陷阱

由于多个嵌套路由同时渲染,因此需要进行一些合并来确定最终渲染的元标签。Remix 允许你完全控制此合并,因为没有明显的默认值。

Remix 将采用具有 meta 导出的最后一个匹配路由并使用它。这允许你覆盖诸如 title 之类的东西,删除父路由添加的诸如 og:image 之类的东西,或者保留父路由中的所有内容并为子路由添加新的元数据。

当你刚开始使用时,这可能会很棘手。

考虑像 /projects/123 这样的路由,可能有三个匹配的路由:app/root.tsxapp/routes/projects.tsxapp/routes/projects.$id.tsx。所有三个都可能导出元描述符。

export const meta: MetaFunction = () => {
  return [
    {
      name: "viewport",
      content: "width=device-width,initial-scale=1",
    },
    { title: "New Remix App" },
  ];
};
export const meta: MetaFunction = () => {
  return [{ title: "Projects" }];
};
export const meta: MetaFunction<typeof loader> = ({
  data,
}) => {
  return [{ title: data.project.name }];
};

使用此代码,我们将丢失 /projects/projects/123 处的 viewport 元标签,因为仅使用最后一个 meta 并且该代码不与父级合并。

全局 meta

几乎每个应用程序都将具有诸如 viewportcharSet 之类的全局元数据。我们建议在 根路由 中使用普通的 <meta> 标签,而不是 meta 导出,这样你就不必处理合并

import {
  Links,
  Meta,
  Outlet,
  Scripts,
} from "@remix-run/react";

export default function App() {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1"
        />
        <Meta />
        <Links />
      </head>
      <body>
        <Outlet />
        <Scripts />
      </body>
    </html>
  );
}

避免在父路由中使用 meta

你还可以通过简单地不从你想要覆盖的父路由导出 meta 来避免合并问题。不要在父路由上定义 meta,而是使用索引路由。这样,你可以避免诸如标题之类的复杂合并逻辑。否则,你需要找到父标题描述符并将其替换为子标题。通过使用索引路由,可以更轻松地避免需要覆盖的情况。

与父 meta 合并

通常,你只需要将 meta 添加到父级已定义的内容中。你可以使用展开运算符和 matches 参数合并父 meta

export const meta: MetaFunction = ({ matches }) => {
  const parentMeta = matches.flatMap(
    (match) => match.meta ?? []
  );
  return [...parentMeta, { title: "Projects" }];
};

请注意,这不会覆盖诸如 title 之类的东西。这只是附加。如果继承的路由 meta 包含 title 标签,你可以使用 Array.prototype.filter 进行覆盖

export const meta: MetaFunction = ({ matches }) => {
  const parentMeta = matches
    .flatMap((match) => match.meta ?? [])
    .filter((meta) => !("title" in meta));
  return [...parentMeta, { title: "Projects" }];
};

meta 合并助手

如果你无法避免使用全局 meta 或索引路由来解决合并问题,我们创建了一个助手,你可以将其放在你的应用程序中,以便轻松地覆盖和附加到父 meta。

文档和示例在 MIT