元数据与SEO
本教程共 42 篇 · 第 19 篇 · 更新于 2026-07-30 · 约 8 分钟阅读
19. 元数据与SEO
本节目标:学会使用 Next.js Metadata API 为页面添加标题、描述等 SEO 信息,掌握静态与动态元数据配置,以及 Open Graph 图片的生成方式。
元数据基础
元数据是 HTML <head> 标签中的信息,不会直接显示在页面上,但对搜索引擎和社交平台至关重要。Next.js 提供两种定义元数据的方式:静态 metadata 对象和动态 generateMetadata 函数。
仅限服务端组件
metadata对象和generateMetadata函数只能在服务端组件中使用。
无论你选择哪种方式,Next.js 都会自动生成相应的 <head> 标签。
默认元数据
即你没有定义任何元数据,Next.js 也会自动添加两个默认标签:
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
静态元数据
在 layout.js 或 page.js 文件中导出一个 Metadata 对象:
// app/blog/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: '我的博客',
description: '分享前端技术与 Next.js 实战经验',
}
export default function Layout({ children }) {
return <div>{children}</div>
}
也可以使用相对简短的写法(JavaScript 项目中不需要类型注解):
// app/blog/layout.js
export const metadata = {
title: '我的博客',
description: '分享前端技术与 Next.js 实战经验',
}
export default function Layout({ children }) {
return <div>{children}</div>
}
动态元数据
当页面信息依赖外部数据(如数据库查询结果)时,使用 generateMetadata 函数:
// app/blog/[slug]/page.tsx
import type { Metadata, ResolvingMetadata } from 'next'
type Props = {
params: Promise<{ slug: string }>
searchParams: Promise<{ [key: string]: string | string[] | undefined }>
}
export async function generateMetadata(
{ params }: Props,
parent: ResolvingMetadata
): Promise<Metadata> {
const { slug } = await params
// 获取文章信息
const post = await fetch(`https://api.example.com/posts/${slug}`).then(
(res) => res.json()
)
// 可选:继承父级元数据中的图片
const previousImages = (await parent).openGraph?.images || []
return {
title: post.title,
description: post.description,
openGraph: {
images: [post.coverImage, ...previousImages],
},
}
}
export default async function Page({ params }: Props) {
// 页面内容...
}
Next.js 16 写法 vs 旧版写法
在 Next.js 15+ 中,
params和searchParams变成了 Promise,需要使用await获取值。旧版本(Next.js 14 及之前)中它们是直接的对象。
标题模板
通过 title.template 可以为子路由的标题添加统一前缀或后缀:
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: {
template: '%s | 码上学',
default: '码上学', // 使用 template 时必须提供 default
},
}
// app/blog/page.tsx
export const metadata: Metadata = {
title: '博客',
}
// 输出: <title>博客 | 码上学</title>
%s 会被子路由的标题替换。title.template 只对子路由生效,不会影响定义它自身的页面。
如果某个页面想要完全忽略父级的模板,使用 title.absolute:
export const metadata: Metadata = {
title: {
absolute: '关于我们', // 输出: <title>关于我们</title>
},
}
常用元数据字段
export const metadata: Metadata = {
// 基础信息
title: '页面标题',
description: '页面描述,建议 150 字以内',
keywords: ['Next.js', 'React', '前端开发'],
// 作者与版权
authors: [{ name: '码上学', url: 'https://example.com' }],
creator: '码上学',
publisher: '码上学',
// 搜索引擎控制
robots: {
index: true,
follow: true,
},
// 备用链接
alternates: {
canonical: 'https://example.com/blog',
languages: {
'zh-CN': 'https://example.com/zh-CN',
'en-US': 'https://example.com/en-US',
},
},
// Open Graph(社交分享)
openGraph: {
title: '页面标题',
description: '页面描述',
url: 'https://example.com/blog',
siteName: '码上学',
images: [
{
url: '/og-image.png',
width: 1200,
height: 630,
},
],
locale: 'zh_CN',
type: 'website',
},
// Twitter 卡片
twitter: {
card: 'summary_large_image',
title: '页面标题',
description: '页面描述',
images: ['/og-image.png'],
},
}
metadataBase 配置
metadataBase 是一个便捷选项,用于设置 URL 的基础路径。设置后,子路由中的相对路径会自动拼接为绝对 URL:
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
alternates: {
canonical: '/blog', // 自动解析为 https://example.com/blog
},
openGraph: {
images: '/og-image.png', // 自动解析为 https://example.com/og-image.png
},
}
通常在根布局中设置一次
metadataBase一般在根app/layout.tsx中设置,所有子路由都会继承。
流式元数据
Next.js 15.2+ 支持元数据流式传输。对于动态渲染的页面,Next.js 会先发送 UI 内容,等 generateMetadata 解析完成后再注入元数据标签。这样可以提升首屏渲染速度。
对于无法执行 JavaScript 的爬虫(如 Facebook 的爬虫),Next.js 会自动切换为阻塞模式,确保元数据在 <head> 中可用。你也可以通过 htmlLimitedBots 配置完全禁用流式元数据:
// next.config.ts
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
htmlLimitedBots: /.*/,
}
export default nextConfig
避免重复请求
如果同一个数据请求在 generateMetadata 和页面组件中都会用到,使用 React 的 cache 函数避免重复请求:
// app/lib/data.ts
import { cache } from 'react'
export const getPost = cache(async (slug: string) => {
const res = await db.query.posts.findFirst({ where: eq(posts.slug, slug) })
return res
})
// app/blog/[slug]/page.tsx
import { getPost } from '@/app/lib/data'
export async function generateMetadata({ params }) {
const { slug } = await params
const post = await getPost(slug)
return { title: post.title }
}
export default async function Page({ params }) {
const { slug } = await params
const post = await getPost(slug) // 不会重复请求
return <article>{post.content}</article>
}
Open Graph 图片生成
Next.js 提供 ImageResponse 构造函数,可以用 JSX 和 CSS 动态生成 OG 图片:
// app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og'
export const size = {
width: 1200,
height: 630,
}
export const contentType = 'image/png'
export default async function Image({
params,
}: {
params: Promise<{ slug: string }>
}) {
const { slug } = await params
const post = await getPost(slug)
return new ImageResponse(
(
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
backgroundColor: '#1a1a2e',
color: 'white',
padding: '40px',
}}
>
<h1 style={{ fontSize: 64, textAlign: 'center' }}>{post.title}</h1>
<p style={{ fontSize: 28, opacity: 0.8 }}>by 码上学</p>
</div>
)
)
}
ImageResponse 的 CSS 限制
ImageResponse支持 flexbox 和绝对定位,但不支持display: grid。只支持 CSS 属性的子集,布局需保持简单。
元数据的合并与覆盖规则
元数据从根布局向叶子页面逐级评估并浅合并:
app/layout.tsx→app/blog/layout.tsx→app/blog/[slug]/page.tsx- 后定义的字段覆盖先定义的字段
- 嵌套字段(如
openGraph)是整体替换,不是深度合并
如果想在不同路由间共享部分元数据同时覆盖其他字段:
// app/shared-metadata.js
export const openGraphImage = { images: ['/default-og.png'] }
// app/page.js
import { openGraphImage } from './shared-metadata'
export const metadata = {
openGraph: {
...openGraphImage,
title: '首页',
},
}
// app/about/page.js
import { openGraphImage } from '../shared-metadata'
export const metadata = {
openGraph: {
...openGraphImage,
title: '关于',
},
}
小结
- 静态元数据:导出
Metadata对象,适合固定内容 - 动态元数据:导出
generateMetadata函数,适合依赖数据的场景 - 标题模板:
title.template设置子路由标题的统一格式 - 流式传输:Next.js 15.2+ 默认支持,提升首屏速度
- OG 图片:
ImageResponse用 JSX 生成动态分享图片 - 浅合并规则:子路由覆盖父路由,嵌套字段整体替换