跳到主要内容

元数据

Next.js 提供了一个 Metadata API, 可用于定义应用程序元数据(例如 HTML head 元素内的 meta 和 link 标签), 以提高 SEO 和 Web 共享性能

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

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

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

静态元数据

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

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

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

export default function Page() {}

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

动态元数据

您可以使用 generateMetadata 函数获取需要动态值的元数据。

app/products/[id]/page.tsx
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 动态获取的元数据仅在 Server Components 中受支持。
  • 对于相同数据,generateMetadatagenerateStaticParamsLayoutsPages 和 Server Components 中的 fetch 请求会自动进行记忆。如果无法使用 fetch,则可以使用 React 缓存。
  • Next.js 将等待 generateMetadata 中的数据获取完成,然后再将 UI 流式传输到客户端。这确保了流式响应的第一部分包含 <head> 标签。

基于文件的元数据

这些特殊文件用于元数据:

您可以将这些用于静态元数据,或者您可以使用代码以编程方式生成这些文件。

有关实现和示例,请参见 Metadata Files API 参考 和 Dynamic Image Generation

行为

基于文件的元数据具有更高的优先级,并将覆盖任何基于配置的元数据。

默认字段

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

  1. <meta charset> 标签设置网站的字符编码。
  2. <meta name="viewport" content="width=device-width, initial-scale=1"> 标签设置网站的视口宽度和缩放,以适应不同设备。
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
备注

您可以覆盖默认的viewport meta 标签。

排序

元数据按顺序评估,从根段开始到最终 page.js 段最近的段。例如:

  • app/layout.tsx(根布局)
  • app/blog/layout.tsx(嵌套的博客布局)
  • app/blog/[slug]/page.tsx(博客页面)

合并

根据评估顺序,从同一路由中导出的多个段的 Metadata 对象将被浅合并在一起, 形成路由的最终元数据输出。 基于其顺序替换重复的键。

这意味着在较早的段中定义的包含嵌套字段(例如 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.jsopenGraph 字段在 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 元数据。

动态图像生成

使用 JSX 和 CSS,ImageResponse 构造函数允许您生成动态图像。 这对于创建社交媒体图像(如 Open Graph 图像、Twitter 卡等)非常有用。

ImageResponse 使用 Edge Runtime, 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 集成良好, 包括 Route Handlers 和基于文件的 Metadata。 例如,您可以在 opengraph-image.tsx 文件中使用 ImageResponse, 在构建时或在请求时动态生成 Open Graph 图像。

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

备注
  • Vercel OG Playground 中提供了示例。
  • ImageResponse 使用 @vercel/ogSatori 和 Resvg 将 HTML 和 CSS 转换为 PNG。
  • 仅支持 Edge Runtime。默认的 Node.js 运行时将不起作用。
  • 仅支持 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(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>
)
}

您可以使用像 schema-dts 这样的社区包,使用 TypeScript 类型化您的 JSON-LD:

app/products/[id]/page.tsx
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.',
}