Menu

元数据

Next.js 提供了一个元数据 API,可用于定义你的应用程序元数据 (例如 HTML head 元素中的 metalink 标签),以改善 SEO 和网络分享效果。

有两种方式可以为你的应用程序添加元数据:

  • 基于配置的元数据:在 layout.jspage.js 文件中导出一个 静态 metadata 对象 或一个动态的 generateMetadata 函数
  • 基于文件的元数据:向路由段添加静态或动态生成的特殊文件。

通过这两种选项,Next.js 将自动为你的页面生成相关的 <head> 元素。你还可以使用 ImageResponse 构造函数创建动态 OG 图像。

静态元数据

要定义静态元数据,从 layout.js 或静态 page.js 文件中导出一个 Metadata 对象

layout.tsx
import type { Metadata } from "next";
 
export const metadata: Metadata = {
  title: "...",
  description: "...",
};
 
export default function Page() {}
layout.js
export const metadata = {
  title: "...",
  description: "...",
};
 
export default function Page() {}

有关所有可用选项,请参阅 API 参考

动态元数据

你可以使用 generateMetadata 函数来 fetch 需要动态值的元数据。

app/products/[id]/page.tsx
import type { Metadata, ResolvingMetadata } from "next";
 
type Props = {
  params: { id: string };
  searchParams: { [key: string]: string | string[] | undefined };
};
 
export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // 读取路由参数
  const id = params.id;
 
  // 获取数据
  const product = await fetch(`https://.../${id}`).then((res) => res.json());
 
  // 可选地访问和扩展 (而不是替换) 父元数据
  const previousImages = (await parent).openGraph?.images || [];
 
  return {
    title: product.title,
    openGraph: {
      images: ["/some-specific-page-image.jpg", ...previousImages],
    },
  };
}
 
export default function Page({ params, searchParams }: Props) {}
app/products/[id]/page.js
export async function generateMetadata({ params, searchParams }, parent) {
  // 读取路由参数
  const id = params.id;
 
  // 获取数据
  const product = await fetch(`https://.../${id}`).then((res) => res.json());
 
  // 可选地访问和扩展 (而不是替换) 父元数据
  const previousImages = (await parent).openGraph?.images || [];
 
  return {
    title: product.title,
    openGraph: {
      images: ["/some-specific-page-image.jpg", ...previousImages],
    },
  };
}
 
export default function Page({ params, searchParams }) {}

有关所有可用参数,请参阅 API 参考

值得注意的是

  • 通过 generateMetadata 生成的静态和动态元数据仅支持服务器组件
  • generateMetadatagenerateStaticParams、布局、页面和服务器组件中,对相同数据的 fetch 请求会自动进行 记忆化。如果无法使用 fetch,可以使用 React 的 cache 函数
  • Next.js 会等待 generateMetadata 中的数据获取完成后,才会将 UI 流式传输到客户端。这确保了 流式响应 的第一部分包含 <head> 标签。

基于文件的元数据

以下是可用于元数据的特殊文件:

你可以使用这些文件来定义静态元数据,也可以通过代码以编程方式生成这些文件。

有关实现和示例,请参阅 元数据文件 API 参考和 动态图像生成

行为

基于文件的元数据具有更高的优先级,会覆盖任何基于配置的元数据。

默认字段

即使路由没有定义元数据,也会始终添加两个默认的 meta 标签:

<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

值得注意的是:你可以覆盖默认的 viewport meta 标签。

排序

元数据按顺序评估,从根段开始,到最接近最终 page.js 段的段结束。例如:

  1. app/layout.tsx (根布局)
  2. app/blog/layout.tsx (嵌套博客布局)
  3. app/blog/[slug]/page.tsx (博客页面)

合并

按照 评估顺序,从同一路由的多个段导出的元数据对象会浅层合并,形成该路由的最终元数据输出。重复的键会根据它们的顺序被替换

这意味着在较早段中定义的具有嵌套字段的元数据 (例如 openGraphrobots) 会被最后定义它们的段覆盖

覆盖字段

app/layout.js
export const metadata = {
  title: "Acme",
  openGraph: {
    title: "Acme",
    description: "Acme is a...",
  },
};
app/blog/page.js
export const metadata = {
  title: "Blog",
  openGraph: {
    title: "Blog",
  },
};
 
// 输出:
// <title>Blog</title>
// <meta property="og:title" content="Blog" />

在上面的例子中:

  • app/layout.js 中的 titleapp/blog/page.js 中的 title 替换
  • app/layout.js 中的所有 openGraph 字段都被 app/blog/page.js 中的字段替换,因为 app/blog/page.js 设置了 openGraph 元数据。注意 openGraph.description 的缺失。

如果你想在段之间共享一些嵌套字段,同时覆盖其他字段,可以将它们提取到一个单独的变量中:

app/shared-metadata.js
export const openGraphImage = { images: ["http://..."] };
app/page.js
import { openGraphImage } from "./shared-metadata";
 
export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: "Home",
  },
};
app/about/page.js
import { openGraphImage } from "../shared-metadata";
 
export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: "About",
  },
};

在上面的例子中,OG 图像在 app/layout.jsapp/about/page.js 之间共享,而标题不同。

继承字段

app/layout.js
export const metadata = {
  title: "Acme",
  openGraph: {
    title: "Acme",
    description: "Acme is a...",
  },
};
app/about/page.js
export const metadata = {
  title: "About",
};
 
// 输出:
// <title>About</title>
// <meta property="og:title" content="Acme" />
// <meta property="og:description" content="Acme is a..." />

值得注意的是

  • app/layout.js 中的 titleapp/about/page.js 中的 title 替换
  • app/layout.js 中的所有 openGraph 字段都被 app/about/page.js 继承,因为 app/about/page.js 没有设置 openGraph 元数据。

动态图像生成

ImageResponse 构造函数允许你使用 JSX 和 CSS 生成动态图像。这对于创建社交媒体图像 (如 Open Graph 图像、Twitter 卡片等) 非常有用。

要使用它,你可以从 next/og 导入 ImageResponse

app/about/route.js
import { ImageResponse } from "next/og";
 
export async function GET() {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 128,
          background: "white",
          width: "100%",
          height: "100%",
          display: "flex",
          textAlign: "center",
          alignItems: "center",
          justifyContent: "center",
        }}
      >
        Hello world!
      </div>
    ),
    {
      width: 1200,
      height: 600,
    }
  );
}

ImageResponse 与其他 Next.js API 集成得很好,包括 路由处理程序 和基于文件的元数据。例如,你可以在 opengraph-image.tsx 文件中使用 ImageResponse 在构建时或请求时动态生成 Open Graph 图像。

ImageResponse 支持常见的 CSS 属性,包括 flexbox 和绝对定位、自定义字体、文本换行、居中和嵌套图像。查看支持的 CSS 属性完整列表

值得注意的是

  • 示例可在 Vercel OG Playground 中查看。
  • ImageResponse 使用 @vercel/ogSatori 和 Resvg 将 HTML 和 CSS 转换为 PNG。
  • 仅支持 Edge 运行时。默认的 Node.js 运行时将不起作用。
  • 仅支持 flexbox 和 CSS 属性的子集。高级布局 (例如 display: grid) 将不起作用。
  • 最大包大小为 500KB。包大小包括你的 JSX、CSS、字体、图像和任何其他资源。如果超出限制,请考虑减小任何资源的大小或在运行时获取。
  • 仅支持 ttfotfwoff 字体格式。为了最大化字体解析速度,推荐使用 ttfotf 而不是 woff

JSON-LD

JSON-LD 是一种结构化数据格式,可以被搜索引擎用来理解你的内容。例如,你可以用它来描述一个人、一个事件、一个组织、一部电影、一本书、一个食谱,以及许多其他类型的实体。

我们目前对 JSON-LD 的建议是在你的 layout.jspage.js 组件中将结构化数据渲染为 <script> 标签。例如:

app/products/[id]/page.tsx
export default async function Page({ params }) {
  const product = await getProduct(params.id);
 
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "Product",
    name: product.name,
    image: product.image,
    description: product.description,
  };
 
  return (
    <section>
      {/* 将 JSON-LD 添加到你的页面 */}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      {/* ... */}
    </section>
  );
}
app/products/[id]/page.js
export default async function Page({ params }) {
  const product = await getProduct(params.id);
 
  const jsonLd = {
    "@context": "https://schema.org",
    "@type": "Product",
    name: product.name,
    image: product.image,
    description: product.description,
  };
 
  return (
    <section>
      {/* 将 JSON-LD 添加到你的页面 */}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      {/* ... */}
    </section>
  );
}

你可以使用 Rich Results Test 针对 Google 或通用的 Schema Markup Validator 来验证和测试你的结构化数据。

你可以使用社区包如 schema-dts 来为你的 JSON-LD 添加 TypeScript 类型:

import { Product, WithContext } from "schema-dts";
 
const jsonLd: WithContext<Product> = {
  "@context": "https://schema.org",
  "@type": "Product",
  name: "Next.js 贴纸",
  image: "https://nextjs.org/imgs/sticker.png",
  description: "以静态速度实现动态。",
};