元数据

Next.js 提供了 元数据 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 生成的静态和动态元数据仅支持在服务端组件中使用
  • fetch 请求会在 generateMetadatagenerateStaticParams、布局、页面和服务端组件之间自动记忆化。如果无法使用 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 卡片等)非常有用。

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

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

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

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

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

须知

  • 示例可在 Vercel OG Playground 中查看。
  • ImageResponse 使用 @vercel/ogSatori 和 Resvg 将 HTML 和 CSS 转换为 PNG。
  • 仅支持边缘运行时。默认的 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 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 Sticker',
  image: 'https://nextjs.org/imgs/sticker.png',
  description: 'Dynamic at the speed of static.',
}

On this page