generateMetadata

您可以使用 metadata 对象或 generateMetadata 函数来定义元数据。

metadata 对象

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

import type { Metadata } from 'next'

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

export default function Page() {}

完整支持的选项列表请参阅 元数据字段

generateMetadata 函数

依赖于动态信息的元数据,例如当前路由参数、外部数据或父级段中的 metadata,可以通过导出一个返回 Metadata 对象generateMetadata 函数来设置。

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> {
  // 读取路由参数
  const { id } = await params

  // 获取数据
  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) {}

须知

  • 元数据可以添加到 layout.jspage.js 文件中。
  • Next.js 会自动解析元数据,并为页面创建相关的 <head> 标签。
  • metadata 对象和 generateMetadata 函数导出仅支持在服务端组件 (Server Components) 中使用。
  • 不能从同一个路由段同时导出 metadata 对象和 generateMetadata 函数。
  • generateMetadata 中的 fetch 请求会自动记忆化,以便在 generateMetadatagenerateStaticParams、布局 (Layouts)、页面 (Pages) 和服务端组件 (Server Components) 之间共享相同数据。
  • 如果无法使用 fetch,可以使用 React 的 cache 函数
  • 基于文件的元数据 具有更高优先级,会覆盖 metadata 对象和 generateMetadata 函数。

参考

参数

generateMetadata 函数接受以下参数:

  • props - 包含当前路由参数的对象:

    • params - 包含从根段到调用 generateMetadata 的段的动态路由参数 对象。示例:

      路由URLparams
      app/shop/[slug]/page.js/shop/1{ slug: '1' }
      app/shop/[tag]/[item]/page.js/shop/1/2{ tag: '1', item: '2' }
      app/shop/[...slug]/page.js/shop/1/2{ slug: ['1', '2'] }
    • searchParams - 包含当前 URL 的查询参数 的对象。示例:

      URLsearchParams
      /shop?a=1{ a: '1' }
      /shop?a=1&b=2{ a: '1', b: '2' }
      /shop?a=1&a=2{ a: ['1', '2'] }
  • parent - 父级路由段解析后的元数据的 Promise。

返回值

generateMetadata 应返回包含一个或多个元数据字段的 Metadata 对象

须知

  • 如果元数据不依赖于运行时信息,应使用静态 metadata 对象 而非 generateMetadata 来定义。
  • fetch 请求会自动记忆化,以便在 generateMetadatagenerateStaticParams、布局 (Layouts)、页面 (Pages) 和服务端组件 (Server Components) 之间共享相同数据。如果无法使用 fetch,可以使用 React 的 cache 函数
  • searchParams 仅在 page.js 段中可用。
  • Next.js 的 redirect()notFound() 方法也可以在 generateMetadata 中使用。

元数据字段

支持以下字段:

title

title 属性用于设置文档标题。可以定义为简单的字符串 或可选的模板对象

字符串
layout.js | page.js
export const metadata = {
  title: 'Next.js',
}
<head> 输出
<title>Next.js</title>
default

title.default 可用于为未定义 title 的子路由段提供回退标题

app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    default: 'Acme',
  },
}
app/about/page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {}

// 输出: <title>Acme</title>
template

title.template 可用于为路由段中定义的 titles 添加前缀或后缀。

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    template: '%s | Acme',
    default: 'Acme', // 创建模板时需要提供默认值
  },
}

须知

  • title.template 应用于路由段,而非定义它的段。这意味着:
    • 添加 title.template 时,title.default必需的
    • layout.js 中定义的 title.template 不会应用于同一路由段的 page.js 中定义的 title
    • page.js 中定义的 title.template 无效,因为页面始终是路由的终止段(它没有任何子路由段)。
  • 如果路由未定义 titletitle.defaulttitle.template 无效
absolute

title.absolute 可用于提供忽略父级段中设置的 title.template 的标题。

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: {
    template: '%s | Acme',
  },
}

须知

  • layout.js
    • title (字符串) 和 title.default 为子段(未定义自己的 title 时)定义默认标题。如果存在最近的父级段的 title.template,它将增强该模板。
    • title.absolute 为子段定义默认标题。它会忽略父级段的 title.template
    • title.template 为子段定义新的标题模板。
  • page.js
    • 如果页面未定义自己的标题,将使用最近的父级解析后的标题。
    • title (字符串) 定义路由标题。如果存在最近的父级段的 title.template,它将增强该模板。
    • title.absolute 定义路由标题。它会忽略父级段的 title.template
    • title.templatepage.js 中无效,因为页面始终是路由的终止段。

description

layout.js | page.js
export const metadata = {
  description: '面向 Web 的 React 框架',
}
<head> 输出
<meta name="description" content="面向 Web 的 React 框架" />

其他字段

layout.js | page.js
export const metadata = {
  generator: 'Next.js',
  applicationName: 'Next.js',
  referrer: 'origin-when-cross-origin',
  keywords: ['Next.js', 'React', 'JavaScript'],
  authors: [{ name: 'Seb' }, { name: 'Josh', url: 'https://nextjs.org' }],
  creator: 'Jiachi Liu',
  publisher: 'Sebastian Markbåge',
  formatDetection: {
    email: false,
    address: false,
    telephone: false,
  },
}
<head> 输出
<meta name="application-name" content="Next.js" />
<meta name="author" content="Seb" />
<link rel="author" href="https://nextjs.org" />
<meta name="author" content="Josh" />
<meta name="generator" content="Next.js" />
<meta name="keywords" content="Next.js,React,JavaScript" />
<meta name="referrer" content="origin-when-cross-origin" />
<meta name="color-scheme" content="dark" />
<meta name="creator" content="Jiachi Liu" />
<meta name="publisher" content="Sebastian Markbåge" />
<meta name="format-detection" content="telephone=no, address=no, email=no" />

metadataBase

metadataBase 是一个便捷选项,用于为需要完全限定 URL 的 metadata 字段设置基础 URL 前缀。

  • metadataBase 允许当前路由段及以下的基于 URL 的 metadata 字段使用相对路径,而非必须的绝对 URL。
  • 字段的相对路径将与 metadataBase 组合形成完全限定的 URL。
layout.js | page.js
export const metadata = {
  metadataBase: new URL('https://acme.com'),
  alternates: {
    canonical: '/',
    languages: {
      'en-US': '/en-US',
      'de-DE': '/de-DE',
    },
  },
  openGraph: {
    images: '/og-image.png',
  },
}
<head> 输出
<link rel="canonical" href="https://acme.com" />
<link rel="alternate" hreflang="en-US" href="https://acme.com/en-US" />
<link rel="alternate" hreflang="de-DE" href="https://acme.com/de-DE" />
<meta property="og:image" content="https://acme.com/og-image.png" />

须知

  • metadataBase 通常设置在根 app/layout.js 中,以应用于所有路由的基于 URL 的 metadata 字段。
  • 所有需要绝对 URL 的基于 URL 的 metadata 字段都可以通过 metadataBase 选项进行配置。
  • metadataBase 可以包含子域名(例如 https://app.acme.com)或基础路径(例如 https://acme.com/start/from/here)。
  • 如果 metadata 字段提供绝对 URL,metadataBase 将被忽略。
  • 在未配置 metadataBase 的情况下在基于 URL 的 metadata 字段中使用相对路径会导致构建错误。
  • Next.js 会规范化 metadataBase(例如 https://acme.com/)和相对字段(例如 /path)之间的重复斜杠为单斜杠(例如 https://acme.com/path)。

URL 组合

URL 组合优先考虑开发者的意图而非默认的目录遍历语义。

  • metadataBasemetadata 字段之间的尾部斜杠会被规范化。
  • metadata 字段中的"绝对"路径(通常替换整个 URL 路径)被视为"相对"路径(从 metadataBase 的末尾开始)。

例如,给定以下 metadataBase

import type { Metadata } from 'next'

export const metadata: Metadata = {
  metadataBase: new URL('https://acme.com'),
}

继承上述 metadataBase 并设置自身值的任何 metadata 字段将按以下方式解析:

metadata 字段解析后的 URL
/https://acme.com
./https://acme.com
paymentshttps://acme.com/payments
/paymentshttps://acme.com/payments
./paymentshttps://acme.com/payments
../paymentshttps://acme.com/payments
https://beta.acme.com/paymentshttps://beta.acme.com/payments

openGraph

layout.js | page.js
export const metadata = {
  openGraph: {
    title: 'Next.js',
    description: '面向 Web 的 React 框架',
    url: 'https://nextjs.org',
    siteName: 'Next.js',
    images: [
      {
        url: 'https://nextjs.org/og.png', // 必须是绝对 URL
        width: 800,
        height: 600,
      },
      {
        url: 'https://nextjs.org/og-alt.png', // 必须是绝对 URL
        width: 1800,
        height: 1600,
        alt: '自定义替代文本',
      },
    ],
    videos: [
      {
        url: 'https://nextjs.org/video.mp4', // 必须是绝对 URL
        width: 800,
        height: 600,
      },
    ],
    audio: [
      {
        url: 'https://nextjs.org/audio.mp3', // 必须是绝对 URL
      },
    ],
    locale: 'en_US',
    type: 'website',
  },
}
<head> output
<meta property="og:title" content="Next.js" />
<meta property="og:description" content="面向 Web 的 React 框架" />
<meta property="og:url" content="https://nextjs.org/" />
<meta property="og:site_name" content="Next.js" />
<meta property="og:locale" content="en_US" />
<meta property="og:image" content="https://nextjs.org/og.png" />
<meta property="og:image:width" content="800" />
<meta property="og:image:height" content="600" />
<meta property="og:image" content="https://nextjs.org/og-alt.png" />
<meta property="og:image:width" content="1800" />
<meta property="og:image:height" content="1600" />
<meta property="og:image:alt" content="自定义替代文本" />
<meta property="og:video" content="https://nextjs.org/video.mp4" />
<meta property="og:video:width" content="800" />
<meta property="og:video:height" content="600" />
<meta property="og:audio" content="https://nextjs.org/audio.mp3" />
<meta property="og:type" content="website" />
layout.js | page.js
export const metadata = {
  openGraph: {
    title: 'Next.js',
    description: '面向 Web 的 React 框架',
    type: 'article',
    publishedTime: '2023-01-01T00:00:00.000Z',
    authors: ['Seb', 'Josh'],
  },
}
<head> output
<meta property="og:title" content="Next.js" />
<meta property="og:description" content="面向 Web 的 React 框架" />
<meta property="og:type" content="article" />
<meta property="article:published_time" content="2023-01-01T00:00:00.000Z" />
<meta property="article:author" content="Seb" />
<meta property="article:author" content="Josh" />

须知:

  • 对于 Open Graph 图片,使用基于文件的元数据 API可能更方便。无需手动同步配置导出与实际文件,基于文件的 API 会自动生成正确的元数据。

robots

layout.tsx | page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  robots: {
    index: true,
    follow: true,
    nocache: false,
    googleBot: {
      index: true,
      follow: true,
      noimageindex: false,
      'max-video-preview': -1,
      'max-image-preview': 'large',
      'max-snippet': -1,
    },
  },
}
<head> output
<meta name="robots" content="index, follow" />
<meta
  name="googlebot"
  content="index, follow, max-video-preview:-1, max-image-preview:large, max-snippet:-1"
/>

icons

须知: 建议尽可能使用基于文件的元数据 API来管理图标。无需手动同步配置导出与实际文件,基于文件的 API 会自动生成正确的元数据。

layout.js | page.js
export const metadata = {
  icons: {
    icon: '/icon.png',
    shortcut: '/shortcut-icon.png',
    apple: '/apple-icon.png',
    other: {
      rel: 'apple-touch-icon-precomposed',
      url: '/apple-touch-icon-precomposed.png',
    },
  },
}
<head> output
<link rel="shortcut icon" href="/shortcut-icon.png" />
<link rel="icon" href="/icon.png" />
<link rel="apple-touch-icon" href="/apple-icon.png" />
<link
  rel="apple-touch-icon-precomposed"
  href="/apple-touch-icon-precomposed.png"
/>
layout.js | page.js
export const metadata = {
  icons: {
    icon: [
      { url: '/icon.png' },
      new URL('/icon.png', 'https://example.com'),
      { url: '/icon-dark.png', media: '(prefers-color-scheme: dark)' },
    ],
    shortcut: ['/shortcut-icon.png'],
    apple: [
      { url: '/apple-icon.png' },
      { url: '/apple-icon-x3.png', sizes: '180x180', type: 'image/png' },
    ],
    other: [
      {
        rel: 'apple-touch-icon-precomposed',
        url: '/apple-touch-icon-precomposed.png',
      },
    ],
  },
}
<head> output
<link rel="shortcut icon" href="/shortcut-icon.png" />
<link rel="icon" href="/icon.png" />
<link rel="icon" href="https://example.com/icon.png" />
<link rel="icon" href="/icon-dark.png" media="(prefers-color-scheme: dark)" />
<link rel="apple-touch-icon" href="/apple-icon.png" />
<link
  rel="apple-touch-icon-precomposed"
  href="/apple-touch-icon-precomposed.png"
/>
<link
  rel="apple-touch-icon"
  href="/apple-icon-x3.png"
  sizes="180x180"
  type="image/png"
/>

须知: Chromium 构建的 Microsoft Edge 不再支持 msapplication-* 元标签,因此不再需要。

themeColor

已弃用: 从 Next.js 14 开始,metadata 中的 themeColor 选项已被弃用。请改用 viewport 配置

colorScheme

已弃用: 从 Next.js 14 开始,metadata 中的 colorScheme 选项已被弃用。请改用 viewport 配置

manifest

Web 应用清单,定义在 Web 应用清单规范中。

layout.js | page.js
export const metadata = {
  manifest: 'https://nextjs.org/manifest.json',
}
<head> output
<link rel="manifest" href="https://nextjs.org/manifest.json" />

twitter

Twitter 规范(令人惊讶地)不仅用于 X(原 Twitter)。

了解更多关于 Twitter 卡片标记参考

layout.js | page.js
export const metadata = {
  twitter: {
    card: 'summary_large_image',
    title: 'Next.js',
    description: '面向 Web 的 React 框架',
    siteId: '1467726470533754880',
    creator: '@nextjs',
    creatorId: '1467726470533754880',
    images: ['https://nextjs.org/og.png'], // 必须是绝对 URL
  },
}
<head> output
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site:id" content="1467726470533754880" />
<meta name="twitter:creator" content="@nextjs" />
<meta name="twitter:creator:id" content="1467726470533754880" />
<meta name="twitter:title" content="Next.js" />
<meta name="twitter:description" content="面向 Web 的 React 框架" />
<meta name="twitter:image" content="https://nextjs.org/og.png" />
layout.js | page.js
export const metadata = {
  twitter: {
    card: 'app',
    title: 'Next.js',
    description: '面向 Web 的 React 框架',
    siteId: '1467726470533754880',
    creator: '@nextjs',
    creatorId: '1467726470533754880',
    images: {
      url: 'https://nextjs.org/og.png',
      alt: 'Next.js Logo',
    },
    app: {
      name: 'twitter_app',
      id: {
        iphone: 'twitter_app://iphone',
        ipad: 'twitter_app://ipad',
        googleplay: 'twitter_app://googleplay',
      },
      url: {
        iphone: 'https://iphone_url',
        ipad: 'https://ipad_url',
      },
    },
  },
}
<head> output
<meta name="twitter:site:id" content="1467726470533754880" />
<meta name="twitter:creator" content="@nextjs" />
<meta name="twitter:creator:id" content="1467726470533754880" />
<meta name="twitter:title" content="Next.js" />
<meta name="twitter:description" content="面向 Web 的 React 框架" />
<meta name="twitter:card" content="app" />
<meta name="twitter:image" content="https://nextjs.org/og.png" />
<meta name="twitter:image:alt" content="Next.js Logo" />
<meta name="twitter:app:name:iphone" content="twitter_app" />
<meta name="twitter:app:id:iphone" content="twitter_app://iphone" />
<meta name="twitter:app:id:ipad" content="twitter_app://ipad" />
<meta name="twitter:app:id:googleplay" content="twitter_app://googleplay" />
<meta name="twitter:app:url:iphone" content="https://iphone_url" />
<meta name="twitter:app:url:ipad" content="https://ipad_url" />
<meta name="twitter:app:name:ipad" content="twitter_app" />
<meta name="twitter:app:name:googleplay" content="twitter_app" />

viewport

已弃用: 从 Next.js 14 开始,metadata 中的 viewport 选项已被弃用。请改用 viewport 配置

verification

layout.js | page.js
export const metadata = {
  verification: {
    google: 'google',
    yandex: 'yandex',
    yahoo: 'yahoo',
    other: {
      me: ['my-email', 'my-link'],
    },
  },
}
<head> output
<meta name="google-site-verification" content="google" />
<meta name="y_key" content="yahoo" />
<meta name="yandex-verification" content="yandex" />
<meta name="me" content="my-email" />
<meta name="me" content="my-link" />

appleWebApp

layout.js | page.js
export const metadata = {
  itunes: {
    appId: 'myAppStoreID',
    appArgument: 'myAppArgument',
  },
  appleWebApp: {
    title: 'Apple Web App',
    statusBarStyle: 'black-translucent',
    startupImage: [
      '/assets/startup/apple-touch-startup-image-768x1004.png',
      {
        url: '/assets/startup/apple-touch-startup-image-1536x2008.png',
        media: '(device-width: 768px) and (device-height: 1024px)',
      },
    ],
  },
}
<head> output
<meta
  name="apple-itunes-app"
  content="app-id=myAppStoreID, app-argument=myAppArgument"
/>
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="Apple Web App" />
<link
  href="/assets/startup/apple-touch-startup-image-768x1004.png"
  rel="apple-touch-startup-image"
/>
<link
  href="/assets/startup/apple-touch-startup-image-1536x2008.png"
  media="(device-width: 768px) and (device-height: 1024px)"
  rel="apple-touch-startup-image"
/>
<meta
  name="apple-mobile-web-app-status-bar-style"
  content="black-translucent"
/>

alternates

layout.js | page.js
export const metadata = {
  alternates: {
    canonical: 'https://nextjs.org',
    languages: {
      'en-US': 'https://nextjs.org/en-US',
      'de-DE': 'https://nextjs.org/de-DE',
    },
    media: {
      'only screen and (max-width: 600px)': 'https://nextjs.org/mobile',
    },
    types: {
      'application/rss+xml': 'https://nextjs.org/rss',
    },
  },
}
<head> output
<link rel="canonical" href="https://nextjs.org" />
<link rel="alternate" hreflang="en-US" href="https://nextjs.org/en-US" />
<link rel="alternate" hreflang="de-DE" href="https://nextjs.org/de-DE" />
<link
  rel="alternate"
  media="only screen and (max-width: 600px)"
  href="https://nextjs.org/mobile"
/>
<link
  rel="alternate"
  type="application/rss+xml"
  href="https://nextjs.org/rss"
/>
layout.js | page.js
export const metadata = {
  appLinks: {
    ios: {
      url: 'https://nextjs.org/ios',
      app_store_id: 'app_store_id',
    },
    android: {
      package: 'com.example.android/package',
      app_name: 'app_name_android',
    },
    web: {
      url: 'https://nextjs.org/web',
      should_fallback: true,
    },
  },
}
<head> output
<meta property="al:ios:url" content="https://nextjs.org/ios" />
<meta property="al:ios:app_store_id" content="app_store_id" />
<meta property="al:android:package" content="com.example.android/package" />
<meta property="al:android:app_name" content="app_name_android" />
<meta property="al:web:url" content="https://nextjs.org/web" />
<meta property="al:web:should_fallback" content="true" />

archives

描述具有历史价值的记录、文档或其他材料的集合(来源)。

layout.js | page.js
export const metadata = {
  archives: ['https://nextjs.org/13'],
}
<head> output
<link rel="archives" href="https://nextjs.org/13" />

assets

layout.js | page.js
export const metadata = {
  assets: ['https://nextjs.org/assets'],
}
<head> output
<link rel="assets" href="https://nextjs.org/assets" />

bookmarks

layout.js | page.js
export const metadata = {
  bookmarks: ['https://nextjs.org/13'],
}
<head> output
<link rel="bookmarks" href="https://nextjs.org/13" />

category

layout.js | page.js
export const metadata = {
  category: 'technology',
}
<head> output
<meta name="category" content="technology" />

facebook

您可以将 Facebook 应用或 Facebook 帐户连接到网页,以使用某些 Facebook 社交插件 Facebook 文档

须知: 您可以指定 appId 或 admins,但不能同时指定两者。

layout.js | page.js
export const metadata = {
  facebook: {
    appId: '12345678',
  },
}
<head> output
<meta property="fb:app_id" content="12345678" />
layout.js | page.js
export const metadata = {
  facebook: {
    admins: '12345678',
  },
}
<head> output
<meta property="fb:admins" content="12345678" />

如果要生成多个 fb:admins 元标签,可以使用数组值。

layout.js | page.js
export const metadata = {
  facebook: {
    admins: ['12345678', '87654321'],
  },
}
<head> output
<meta property="fb:admins" content="12345678" />
<meta property="fb:admins" content="87654321" />

pinterest

您可以在网页上启用或禁用 Pinterest Rich Pins

layout.js | page.js
export const metadata = {
  pinterest: {
    richPin: true,
  },
}
<head> output
<meta name="pinterest-rich-pin" content="true" />

other

所有元数据选项都应通过内置支持覆盖。但是,可能存在特定于您网站的自定义元数据标签,或刚刚发布的全新元数据标签。您可以使用 other 选项渲染任何自定义元数据标签。

layout.js | page.js
export const metadata = {
  other: {
    custom: 'meta',
  },
}
<head> output
<meta name="custom" content="meta" />

如果要生成多个相同键的元标签,可以使用数组值。

layout.js | page.js
export const metadata = {
  other: {
    custom: ['meta1', 'meta2'],
  },
}
<head> output
<meta name="custom" content="meta1" /> <meta name="custom" content="meta2" />

不支持的元数据

以下元数据类型目前没有内置支持。但是,它们仍然可以在布局或页面本身中渲染。

类型

您可以通过使用 Metadata 类型为元数据添加类型安全。如果您的 IDE 中使用了内置 TypeScript 插件,则无需手动添加类型,但仍可显式添加。

metadata 对象

layout.tsx | page.tsx
import type { Metadata } from 'next'

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

generateMetadata 函数

常规函数
layout.tsx | page.tsx
import type { Metadata } from 'next'

export function generateMetadata(): Metadata {
  return {
    title: 'Next.js',
  }
}
异步函数
layout.tsx | page.tsx
import type { Metadata } from 'next'

export async function generateMetadata(): Promise<Metadata> {
  return {
    title: 'Next.js',
  }
}
带分段属性
layout.tsx | page.tsx
import type { Metadata } from 'next'

type Props = {
  params: Promise<{ id: string }>
  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}

export function generateMetadata({ params, searchParams }: Props): Metadata {
  return {
    title: 'Next.js',
  }
}

export default function Page({ params, searchParams }: Props) {}
带父级元数据
layout.tsx | page.tsx
import type { Metadata, ResolvingMetadata } from 'next'

export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  return {
    title: 'Next.js',
  }
}
JavaScript 项目

对于 JavaScript 项目,可以使用 JSDoc 添加类型安全。

layout.js | page.js
/** @type {import("next").Metadata} */
export const metadata = {
  title: 'Next.js',
}
元数据推荐方案
<meta http-equiv="...">通过 redirect()中间件安全标头 设置适当的 HTTP 标头
<base>在布局或页面中直接渲染该标签
<noscript>在布局或页面中直接渲染该标签
<style>了解更多关于 Next.js 中的样式
<script>了解更多关于 使用脚本
<link rel="stylesheet" />直接在布局或页面中 import 样式表
<link rel="preload />使用 ReactDOM preload 方法
<link rel="preconnect" />使用 ReactDOM preconnect 方法
<link rel="dns-prefetch" />使用 ReactDOM prefetchDNS 方法

资源提示

<link> 元素有一系列 rel 关键字,可用于向浏览器提示可能需要外部资源。浏览器根据这些关键字应用预加载优化。

虽然元数据 API 不直接支持这些提示,但您可以使用新的 ReactDOM 方法 安全地将它们插入文档的 <head> 中。

'use client'

import ReactDOM from 'react-dom'

export function PreloadResources() {
  ReactDOM.preload('...', { as: '...' })
  ReactDOM.preconnect('...', { crossOrigin: '...' })
  ReactDOM.prefetchDNS('...')

  return '...'
}

在页面渲染 (浏览器) 生命周期的早期开始加载资源。MDN 文档

ReactDOM.preload(href: string, options: { as: string })
<head> output
<link rel="preload" href="..." as="..." />

预先发起与源的连接。MDN 文档

ReactDOM.preconnect(href: string, options?: { crossOrigin?: string })
<head> output
<link rel="preconnect" href="..." crossorigin />

在资源被请求之前尝试解析域名。MDN 文档

ReactDOM.prefetchDNS(href: string)
<head> output
<link rel="dns-prefetch" href="..." />

须知:

  • 这些方法目前仅在客户端组件中受支持,这些组件在初始页面加载时仍会进行服务端渲染。
  • Next.js 内置功能如 next/fontnext/imagenext/script 会自动处理相关资源提示。

行为

默认字段

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

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

须知:您可以覆盖默认的 viewport 元标签。

流式元数据

generateMetadata 返回的元数据会流式传输到客户端。这使得 Next.js 能够在元数据解析后立即将其注入 HTML。

由于页面元数据主要面向机器人和爬虫,Next.js 会对能执行 JavaScript 并检查完整页面 DOM 的机器人 (如 Googlebot) 流式传输元数据。但对于 HTML 受限 的机器人 (如 Twitterbot),元数据会继续阻塞页面渲染,因为这些机器人在爬取时无法执行 JavaScript。

Next.js 会自动检测传入请求的用户代理,以决定是提供流式元数据还是回退到阻塞式元数据。

如果需要自定义此列表,可以在 next.config.js 中使用 htmlLimitedBots 选项手动定义。Next.js 会确保匹配此正则表达式的用户代理在请求您的网页时收到阻塞式元数据。

import type { NextConfig } from 'next'

const config: NextConfig = {
  htmlLimitedBots: /MySpecialBot|MyAnotherSpecialBot|SimpleCrawler/,
}

export default config

指定 htmlLimitedBots 配置将覆盖 Next.js 的默认列表,让您完全控制哪些用户代理应选择此行为。这是高级行为,默认设置应能满足大多数情况。

顺序

元数据按顺序评估,从根段开始,一直到最接近最终 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 元数据。

版本历史

版本变更
v15.2.0generateMetadata 引入流式支持。
v13.2.0viewportthemeColorcolorScheme 已弃用,推荐使用 viewport 配置
v13.2.0引入 metadatagenerateMetadata