Sponsor
ntab.devntab.dev 提升效率的新标签页组件
点击查看
Menu

generateMetadata

本页面涵盖了所有使用 generateMetadata 和静态元数据对象的基于配置的元数据选项。

layout.tsx
TypeScript
import type { Metadata } from 'next'
 
// 静态元数据
export const metadata: Metadata = {
  title: '...',
}
 
// 或动态元数据
export async function generateMetadata({ params }) {
  return {
    title: '...',
  }
}

值得注意的是

  • metadata 对象和 generateMetadata 函数导出仅在服务器组件中支持
  • 你不能在同一路由段中同时导出 metadata 对象和 generateMetadata 函数。

metadata 对象

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

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

请查看 元数据字段 了解所有支持的选项列表。

generateMetadata 函数

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

app/products/[id]/page.tsx
TypeScript
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).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) {}

参数

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
  • generateMetadatagenerateStaticParams、Layouts、Pages 和 Server Components 中对相同数据的 fetch 请求会自动被记忆化。如果无法使用 fetch,可以使用 React 的 cache 函数
  • searchParams 仅在 page.js 段中可用。
  • Next.js 的 redirect()notFound() 方法也可以在 generateMetadata 中使用。

Metadata 字段

title

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

字符串

layout.js
export const metadata = {
  title: 'Next.js',
}
<head>
<title>Next.js</title>

模板对象

app/layout.tsx
TypeScript
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: {
    template: '...',
    default: '...',
    absolute: '...',
  },
}
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 添加前缀或后缀。

app/layout.tsx
TypeScript
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: {
    template: '%s | Acme',
    default: 'Acme', // 创建模板时需要一个默认值
  },
}
app/about/page.tsx
TypeScript
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'About',
}
 
// 输出:<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
TypeScript
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: {
    template: '%s | Acme',
  },
}
app/about/page.tsx
TypeScript
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: {
    absolute: 'About',
  },
}
 
// 输出:<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
export const metadata = {
  description: '用于 Web 的 React 框架',
}
<head>
<meta name="description" content="用于 Web 的 React 框架" />

基本字段

layout.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。
  • 如果未配置,metadataBase 将自动填充默认值
layout.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 将被忽略。
  • 在基于 URL 的 metadata 字段中使用相对路径而不配置 metadataBase 将导致构建错误。
  • Next.js 会将 metadataBase (例如 https://acme.com/) 和相对字段 (例如 /path) 之间的重复斜杠规范化为单个斜杠 (例如 https://acme.com/path)。

默认值

如果未配置,metadataBase 有一个默认值

在 Vercel 上:

  • 对于生产部署,将使用 VERCEL_PROJECT_PRODUCTION_URL
  • 对于预览部署,VERCEL_BRANCH_URL 将优先,如果不存在则回退到 VERCEL_URL

如果这些值存在,它们将被用作 metadataBase默认值,否则将回退到 http://localhost:${process.env.PORT || 3000}。这使得 Open Graph 图像在本地构建和 Vercel 预览及生产部署上都能正常工作。在覆盖默认值时,我们建议使用环境变量来计算 URL。这允许为本地开发、测试和生产环境配置不同的 URL。

有关这些环境变量的更多详细信息,请参阅系统环境变量文档。

URL 组合

URL 组合倾向于遵循开发者意图而不是默认的目录遍历语义。

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

例如,给定以下 metadataBase

app/layout.tsx
TypeScript
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
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: 'zh_CN',
    type: 'website',
  },
}
<head>
<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="zh_CN" />
<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
export const metadata = {
  openGraph: {
    title: 'Next.js',
    description: '用于 Web 的 React 框架',
    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="用于 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
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
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
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"
/>

值得注意的是:Microsoft Edge 的 Chromium 版本不再支持 msapplication-* meta 标签,因此不再需要这些标签。

themeColor

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

colorScheme

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

manifest

Web 应用程序清单,如 Web 应用程序清单规范中定义。

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

twitter

Twitter 规范(令人惊讶的是)不仅用于 X(前身为 Twitter)。

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

layout.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>
<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
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>
<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
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
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
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>
<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
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>
<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
export const metadata = {
  archives: ['https://nextjs.org/13'],
}
<head>
<link rel="archives" href="https://nextjs.org/13" />

assets

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

bookmarks

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

category

layout.js
export const metadata = {
  category: '技术',
}
<head>
<meta name="category" content="技术" />

facebook

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

值得注意的是:你可以指定 appId 或 admins,但不能同时指定两者。

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

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

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

other

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

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

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

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

不支持的元数据

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

元数据建议
<meta http-equiv="...">通过 redirect()Middleware安全头部 使用适当的 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> 中。

app/preload-resources.tsx
TypeScript
'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 会自动处理相关的资源提示。

类型

你可以使用 Metadata 类型为你的元数据添加类型安全。如果你在 IDE 中使用内置的 TypeScript 插件,则无需手动添加类型,但如果你想的话,你仍然可以显式添加它。

metadata 对象

layout.tsx
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'Next.js',
}

generateMetadata 函数

常规函数

layout.tsx
import type { Metadata } from 'next'
 
export function generateMetadata(): Metadata {
  return {
    title: 'Next.js',
  }
}

异步函数

layout.tsx
import type { Metadata } from 'next'
 
export async function generateMetadata(): Promise<Metadata> {
  return {
    title: 'Next.js',
  }
}

带有段参数

layout.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
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
/** @type {import("next").Metadata} */
export const metadata = {
  title: 'Next.js',
}

流式传输元数据

从 v15.2 开始,由 generateMetadata 返回的元数据将被流式传输到客户端。这允许 Next.js 在元数据解析完成后立即将其注入到 HTML 中。

由于页面元数据主要针对机器人和爬虫,Next.js 将继续阻塞渲染,直到HTML 受限机器人的元数据被解析完成。

一些机器人,如 Googlebot,可以执行 JavaScript 并能够检查完整的页面 DOM,这意味着它们需要阻塞元数据。然而,像 Twitterbot 这样的机器人在爬取页面时无法执行 JavaScript——它们属于HTML 受限类别。

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

如果你需要自定义这个列表,可以使用 next.config.js 中的 htmlLimitedBots 选项手动定义它们。Next.js 将确保与此正则表达式匹配的用户代理在请求你的网页时接收阻塞元数据。 指定 htmlLimitedBots 配置将覆盖 Next.js 的默认列表,让你完全控制哪些用户代理应该采用这种行为。这是高级行为,默认设置对大多数情况应该足够了。

next.config.js
module.exports = {
  htmlLimitedBots: 'MySpecialBot|MyAnotherSpecialBot|SimpleCrawler',
}

注意: Next.js 包含一个 HTML 受限机器人的默认列表

版本历史

版本变更
v15.2.0引入对 generateMetadata 的流式支持。
v13.2.0viewportthemeColorcolorScheme 已被弃用,改用 viewport 配置
v13.2.0引入 metadatagenerateMetadata