元数据 (Metadata)

Next.js 提供了元数据 (Metadata) API,可用于定义应用元数据(例如 HTML head 元素中的 metalink 标签),以提升 SEO 和网页分享能力。

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

通过这两种方式,Next.js 都会自动为页面生成相关的 <head> 元素。您还可以使用 ImageResponse 构造函数创建动态 OG 图片。

静态元数据

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

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: '...',
  description: '...',
}

export default function Page() {}

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

动态元数据

您可以使用 generateMetadata 函数来获取需要动态值的元数据。

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) {}

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

须知

  • 通过 generateMetadata 生成的静态和动态元数据仅支持在服务端组件 (Server Components) 中使用。
  • fetch 请求会在 generateMetadatagenerateStaticParams、布局 (Layouts)、页面 (Pages) 和服务端组件 (Server Components) 之间自动记忆化 (memoized)。如果无法使用 fetch,可以使用 React 的 cache
  • Next.js 会在客户端流式传输 UI 之前等待 generateMetadata 内的数据获取完成。这确保了流式响应 (streamed response) 的第一部分包含 <head> 标签。

基于文件的元数据

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

您可以将这些文件用于静态元数据,也可以通过代码编程生成这些文件。

有关实现和示例,请参阅元数据文件 (Metadata Files) API 参考文档和动态图片生成 (Dynamic Image Generation)

行为

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

默认字段

即使路由未定义元数据,也会始终添加两个默认的 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 卡片等)非常有用。

ImageResponse 使用边缘运行时 (Edge Runtime),Next.js 会自动为缓存在边缘的图片添加正确的标头,有助于提高性能并减少重新计算。

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

app/about/route.js
import { ImageResponse } from 'next/server'

export const runtime = 'edge'

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(包括路由处理器 (Route Handlers) 和基于文件的元数据)集成良好。例如,您可以在 opengraph-image.tsx 文件中使用 ImageResponse,在构建时或请求时动态生成 Open Graph 图片。

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

须知

  • 示例可在 Vercel OG Playground 查看。
  • ImageResponse 使用 @vercel/ogSatori 和 Resvg 将 HTML 和 CSS 转换为 PNG。
  • 仅支持边缘运行时 (Edge Runtime)。默认的 Node.js 运行时无效。
  • 仅支持 flexbox 和部分 CSS 属性。高级布局(如 display: grid)无效。
  • 最大包大小为 500KB。包大小包括 JSX、CSS、字体、图片和其他资源。如果超出限制,请考虑减小资源大小或在运行时获取。
  • 仅支持 ttfotfwoff 字体格式。为了最大化字体解析速度,优先使用 ttfotf 而非 woff

JSON-LD

JSON-LD 是一种结构化数据格式,可被搜索引擎用于理解您的内容。例如,您可以用它来描述人物、事件、组织、电影、书籍、食谱等多种实体类型。

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

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) 或通用的 Schema 标记验证器 (Schema Markup Validator) 验证和测试结构化数据。

您可以使用社区包(如 schema-dts)通过 TypeScript 为 JSON-LD 添加类型:

import { Product, WithContext } from 'schema-dts'

const jsonLd: WithContext<Product> = {
  '@context': 'https://schema.org',
  '@type': 'Product',
  name: 'Next.js Sticker',
  image: 'https://nextjs.org/imgs/sticker.png',
  description: 'Dynamic at the speed of static.',
}

On this page