跳到内容
API 参考函数generateMetadata

generateMetadata

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

metadata 对象

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

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

有关所有支持选项的完整列表,请参阅Metadata 字段

generateMetadata 函数

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

解析 generateMetadata 是页面渲染的一部分。如果页面可以预渲染并且 generateMetadata 不会引入动态行为,则生成的元数据将包含在页面的初始 HTML 中。

否则,从 generateMetadata 解析的元数据在发送初始 UI 后可以进行流式传输

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

为了完成 paramssearchParams 的类型,你可以使用 PageProps<'/route'>LayoutProps<'/route'>(分别用于页面和布局)为第一个参数指定类型。

须知:

  • 元数据可以添加到 layout.jspage.js 文件中。
  • Next.js 会自动解析元数据,并为页面创建相关的 <head> 标签。
  • metadata 对象和 generateMetadata 函数的导出仅在服务器组件中支持
  • 你不能从同一个路由段同时导出 metadata 对象和 generateMetadata 函数。
  • generateMetadata 内部的 fetch 请求会自动记忆,以便在 generateMetadatagenerateStaticParams、布局、页面和服务器组件之间使用相同的数据。
  • 如果 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、布局、页面和服务器组件之间自动记忆化相同的数据。如果 fetch 不可用,可以使用 React cache
  • searchParams 仅在 page.js 段中可用。
  • Next.js 的 redirect()notFound() 方法也可以在 generateMetadata 中使用。

Metadata 字段

支持以下字段

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 = {}
 
// Output: <title>Acme</title>
template

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

app/layout.tsx
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: {
    template: '%s | Acme',
    default: 'Acme', // a default is required when creating a template
  },
}
app/about/page.tsx
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'About',
}
 
// Output: <title>About | Acme</title>

须知:

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

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

app/layout.tsx
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: {
    template: '%s | Acme',
  },
}
app/about/page.tsx
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: {
    absolute: 'About',
  },
}
 
// Output: <title>About</title>

须知:

  • 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: 'The React Framework for the Web',
}
<head> 输出
<meta name="description" content="The React Framework for the Web" />

其他字段

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.net.cn' }],
  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.net.cn" />
<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

app/layout.tsx
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: 'The React Framework for the Web',
    url: 'https://nextjs.net.cn',
    siteName: 'Next.js',
    images: [
      {
        url: 'https://nextjs.net.cn/og.png', // Must be an absolute URL
        width: 800,
        height: 600,
      },
      {
        url: 'https://nextjs.net.cn/og-alt.png', // Must be an absolute URL
        width: 1800,
        height: 1600,
        alt: 'My custom alt',
      },
    ],
    videos: [
      {
        url: 'https://nextjs.net.cn/video.mp4', // Must be an absolute URL
        width: 800,
        height: 600,
      },
    ],
    audio: [
      {
        url: 'https://nextjs.net.cn/audio.mp3', // Must be an absolute URL
      },
    ],
    locale: 'en_US',
    type: 'website',
  },
}
<head> 输出
<meta property="og:title" content="Next.js" />
<meta property="og:description" content="The React Framework for the Web" />
<meta property="og:url" content="https://nextjs.net.cn/" />
<meta property="og:site_name" content="Next.js" />
<meta property="og:locale" content="en_US" />
<meta property="og:image" content="https://nextjs.net.cn/og.png" />
<meta property="og:image:width" content="800" />
<meta property="og:image:height" content="600" />
<meta property="og:image" content="https://nextjs.net.cn/og-alt.png" />
<meta property="og:image:width" content="1800" />
<meta property="og:image:height" content="1600" />
<meta property="og:image:alt" content="My custom alt" />
<meta property="og:video" content="https://nextjs.net.cn/video.mp4" />
<meta property="og:video:width" content="800" />
<meta property="og:video:height" content="600" />
<meta property="og:audio" content="https://nextjs.net.cn/audio.mp3" />
<meta property="og:type" content="website" />
layout.js | page.js
export const metadata = {
  openGraph: {
    title: 'Next.js',
    description: 'The React Framework for the Web',
    type: 'article',
    publishedTime: '2023-01-01T00:00:00.000Z',
    authors: ['Seb', 'Josh'],
  },
}
<head> 输出
<meta property="og:title" content="Next.js" />
<meta property="og:description" content="The React Framework for the Web" />
<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" />

须知:

  • 使用基于文件的元数据 API 生成 Open Graph 图像可能更方便。基于文件的 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> 输出
<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> 输出
<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> 输出
<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-* meta 标签,因此不再需要。

themeColor

已弃用metadata 中的 themeColor 选项已在 Next.js 14 中弃用。请改用viewport 配置

colorScheme

已弃用metadata 中的 colorScheme 选项已在 Next.js 14 中弃用。请改用viewport 配置

manifest

一个 Web 应用清单,如 Web 应用清单规范中定义的。

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

twitter

Twitter 规范(令人惊讶地)不仅仅用于 X(以前称为 Twitter)。

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

layout.js | page.js
export const metadata = {
  twitter: {
    card: 'summary_large_image',
    title: 'Next.js',
    description: 'The React Framework for the Web',
    siteId: '1467726470533754880',
    creator: '@nextjs',
    creatorId: '1467726470533754880',
    images: ['https://nextjs.net.cn/og.png'], // Must be an absolute URL
  },
}
<head> 输出
<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="The React Framework for the Web" />
<meta name="twitter:image" content="https://nextjs.net.cn/og.png" />
layout.js | page.js
export const metadata = {
  twitter: {
    card: 'app',
    title: 'Next.js',
    description: 'The React Framework for the Web',
    siteId: '1467726470533754880',
    creator: '@nextjs',
    creatorId: '1467726470533754880',
    images: {
      url: 'https://nextjs.net.cn/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> 输出
<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="The React Framework for the Web" />
<meta name="twitter:card" content="app" />
<meta name="twitter:image" content="https://nextjs.net.cn/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

已弃用metadata 中的 viewport 选项已在 Next.js 14 中弃用。请改用viewport 配置

verification

layout.js | page.js
export const metadata = {
  verification: {
    google: 'google',
    yandex: 'yandex',
    yahoo: 'yahoo',
    other: {
      me: ['my-email', 'my-link'],
    },
  },
}
<head> 输出
<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> 输出
<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.net.cn',
    languages: {
      'en-US': 'https://nextjs.net.cn/en-US',
      'de-DE': 'https://nextjs.net.cn/de-DE',
    },
    media: {
      'only screen and (max-width: 600px)': 'https://nextjs.net.cn/mobile',
    },
    types: {
      'application/rss+xml': 'https://nextjs.net.cn/rss',
    },
  },
}
<head> 输出
<link rel="canonical" href="https://nextjs.net.cn" />
<link rel="alternate" hreflang="en-US" href="https://nextjs.net.cn/en-US" />
<link rel="alternate" hreflang="de-DE" href="https://nextjs.net.cn/de-DE" />
<link
  rel="alternate"
  media="only screen and (max-width: 600px)"
  href="https://nextjs.net.cn/mobile"
/>
<link
  rel="alternate"
  type="application/rss+xml"
  href="https://nextjs.net.cn/rss"
/>
layout.js | page.js
export const metadata = {
  appLinks: {
    ios: {
      url: 'https://nextjs.net.cn/ios',
      app_store_id: 'app_store_id',
    },
    android: {
      package: 'com.example.android/package',
      app_name: 'app_name_android',
    },
    web: {
      url: 'https://nextjs.net.cn/web',
      should_fallback: true,
    },
  },
}
<head> 输出
<meta property="al:ios:url" content="https://nextjs.net.cn/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.net.cn/web" />
<meta property="al:web:should_fallback" content="true" />

archives

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

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

assets

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

bookmarks

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

category

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

facebook

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

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

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

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

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

pinterest

你可以在网页上启用或禁用 Pinterest 富媒体 Pin 码

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

other

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

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

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

layout.js | page.js
export const metadata = {
  other: {
    custom: ['meta1', 'meta2'],
  },
}
<head> 输出
<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',
  }
}
带 segment props
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) {}
带父级 metadata
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',
}

不支持的 Metadata

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

元数据建议
<meta http-equiv="...">通过redirect()代理安全头使用适当的 HTTP 头。
<base>在布局或页面本身中渲染标签。
<noscript>在布局或页面本身中渲染标签。
<style>了解更多关于 Next.js 中的样式设置
<script>了解更多关于 使用脚本
<link rel="stylesheet" />直接在布局或页面本身中 import 样式表。
<link rel="preload />使用 ReactDOM 预加载方法
<link rel="preconnect" />使用 ReactDOM 预连接方法
<link rel="dns-prefetch" />使用 ReactDOM prefetchDNS 方法

资源提示

<link> 元素有许多 rel 关键字,可用于向浏览器提示可能需要外部资源。浏览器利用此信息根据关键字应用预加载优化。

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

app/preload-resources.tsx
'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> 输出
<link rel="preload" href="..." as="..." />

预先启动到源的连接。MDN 文档

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

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

ReactDOM.prefetchDNS(href: string)
<head> 输出
<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 meta 标签。

流式元数据

流式元数据允许 Next.js 渲染并将初始 UI 发送到浏览器,而无需等待 generateMetadata 完成。

generateMetadata 解析时,生成的元数据标签将附加到 <body> 标签。我们已验证执行 JavaScript 并检查完整 DOM(例如 Googlebot)的机器人可以正确解释元数据。

对于受 HTML 限制的机器人(无法执行 JavaScript,例如 facebookexternalhit),元数据将继续阻止页面渲染。生成的元数据将在 <head> 标签中可用。

Next.js 通过查看 User Agent 标头自动检测受 HTML 限制的机器人。你可以在 Next.js 配置文件中使用 htmlLimitedBots 选项来覆盖默认的 User Agent 列表

要完全禁用流式元数据

next.config.ts
import type { NextConfig } from 'next'
 
const config: NextConfig = {
  htmlLimitedBots: /.*/,
}
 
export default config

流式元数据通过减少TTFB并有助于降低LCP时间来提高感知性能。

覆盖 htmlLimitedBots 可能会导致更长的响应时间。流式元数据是一项高级功能,默认设置足以满足大多数情况。

排序

元数据按顺序评估,从根段到最接近最终 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.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',
}
 
// Output:
// <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.0引入了 generateMetadata 的流式传输支持。
v13.2.0viewportthemeColorcolorScheme 已弃用,取而代之的是 viewport 配置
v13.2.0引入了 metadatagenerateMetadata