跳到内容

元数据

Next.js 有一个元数据 API,可用于定义你的应用程序元数据(例如 HTML head 元素内的 metalink 标签),以改进 SEO 和 Web 可共享性。

有两种方法可以向你的应用程序添加元数据

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

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

静态元数据

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

layout.tsx | page.tsx
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: '...',
  description: '...',
}
 
export default function Page() {}

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

动态元数据

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

app/products/[id]/page.tsx
import type { Metadata, ResolvingMetadata } from 'next'
 
type Props = {
  params: Promise<{ id: string }>
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
 
export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // read route params
  const id = (await params).id
 
  // fetch data
  const product = await fetch(`https://.../${id}`).then((res) => res.json())
 
  // optionally access and extend (rather than replace) parent metadata
  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 的静态和动态元数据仅在服务器组件中受支持
  • 对于跨 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',
  },
}
 
// Output:
// <title>Blog</title>
// <meta property="og:title" content="Blog" />

在上面的例子中

  • 来自 app/layout.jstitleapp/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',
}
 
// Output:
// <title>About</title>
// <meta property="og:title" content="Acme" />
// <meta property="og:description" content="Acme is a..." />

注意

  • 来自 app/layout.jstitleapp/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。
  • 仅支持 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((await params).id)
 
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    image: product.image,
    description: product.description,
  }
 
  return (
    <section>
      {/* Add JSON-LD to your page */}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      {/* ... */}
    </section>
  )
}

你可以使用 富结果测试 进行 Google 验证和测试,或使用通用的 Schema Markup Validator 进行验证和测试。

你可以使用 TypeScript 通过社区包(如 schema-dts)键入你的 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.net.cn/imgs/sticker.png',
  description: 'Dynamic at the speed of static.',
}