Metadata 元数据系统
什么是 Metadata?
Metadata(元数据)是描述网页内容的信息,包含在 HTML <head> 标签中,对 SEO 和社交媒体分享至关重要。
示例:
<head>
<title>Nexty.dev - Build and ship your SaaS faster</title>
<meta name="description" content="Next.js SaaS starter template..." />
<meta property="og:title" content="Nexty.dev" />
<meta property="og:image" content="https://nexty.dev/og.png" />
<link rel="canonical" href="https://nexty.dev/" />
<link rel="alternate" hreflang="en-US" href="https://nexty.dev/" />
<link rel="alternate" hreflang="zh-CN" href="https://nexty.dev/zh" />
</head>Nexty.dev 的 Metadata 系统
核心函数:constructMetadata()
位置:lib/metadata.ts
作用:
- 统一生成所有页面的 Metadata
- 自动处理多语言
- 自动生成 canonical 和 hreflang
- 自动生成 Open Graph 和 Twitter 卡片
函数解释
type MetadataProps = {
page?: string // 页面名称(已废弃,无需配置)
title?: string // 页面标题
description?: string // 页面描述
images?: string[] // OG 图片(相对路径或绝对路径)
noIndex?: boolean // 是否禁止索引(默认 false)
locale?: Locale // 当前语言(en, zh, ja)
path?: string // 页面路径(必须以 / 开头)
canonicalUrl?: string // 规范 URL(可选)
availableLocales?: string[] // 可用语言列表(可选)
useDefaultOgImage?: boolean // 默认 true
}1. title - 页面标题
title: "About Us"生成规则:
- 首页:
{title} - {siteConfig.tagLine}- 示例:
Nexty.dev - Build and ship your SaaS faster
- 示例:
- 其他页:
{title} | {siteConfig.name}- 示例:
About Us | Nexty.dev
- 示例:
如果不传,将使用多语言文件中的默认值(Home 命名空间的 title)
2. description - 页面描述
description: "Learn more about our mission and team."作用:
- 显示在搜索结果中
- 显示在社交媒体卡片中
如果不传,将使用多语言文件中的默认值。
3. images - OG 图片
images: post.featuredImageUrl ? [post.featuredImageUrl] : [],支持格式:
- 相对路径:
["images/about.png"]→https://nexty.dev/images/about.png - 绝对路径:
["https://cdn.example.com/about.png"]
大多数情况下无需为页面单独配置 images,系统会自动根据页面语言选择默认图片,例如:
- 英文:
/og.png - 中文:
/og_zh.png - 日文:
/og_ja.png
4. noIndex - 禁止索引
noIndex: true作用:
- 生成
<meta name="robots" content="noindex, nofollow" /> - 告诉搜索引擎不要索引该页面
使用场景:
- 付费内容页
- 登录后才能访问的页面
- 测试页面
- 404 页面
5. locale - 当前语言
传入当前语言标识,用于自动处理 metadata 中的语言信息和必要的多语言 URL。
locale: "zh" // en, zh, ja作用:
- 影响 URL 生成
- 影响 OG 图片选择
- 影响
<html lang="zh">属性
6. path - 页面路径
path 必须以 / 开头,且不要包含语言前缀,例如:
path: "/about"construcMetadata() 会结合 locale 和 path 自动处理多语言信息和 URL。
作用:
- 用于生成 canonical URL,帮助搜索引擎识别页面的规范版本,避免重复内容问题
- 用于生成 hreflang 链接,让搜索引擎了解页面的语言和地区版本,为不同地区的用户提供正确的语言版本
7. canonicalUrl - 规范 URL
canonicalUrl: "/blogs"使用场景:
当多个 URL 指向相同内容时,例如 /blogs 和 /blogs?ref=docs,需要告诉搜索引擎它们的规范版本 URL 都是 /blogs,这样可以避免搜索引擎将带参数的 URL 当作新页面,导致 SEO 权重分散。
通常无需手动配置此项,系统会自动引用 paht 进行配置。只有当你需要将当前页面指向不同 URL 时才需要手动配置。
8. availableLocales - 可用语言列表
availableLocales: ["en", "zh"] // 只有英文和中文版本使用场景:
当某个页面并非所有语言都有对应版本时(比如博客文章)。
作用:
- 仅为存在的语言版本生成 hreflang 链接
- 避免生成指向 404 页面的链接
示例:博客文章
// 检测哪些语言有该文章
const availableLocales: string[] = []
for (const checkLocale of LOCALES) {
const post = await getPostBySlug(slug, checkLocale)
if (post) availableLocales.push(checkLocale)
}
// 只为存在的语言生成 hreflang
constructMetadata({
// ...
availableLocales: availableLocales.length > 0 ? availableLocales : undefined,
})如果不传入该参数,将为所有语言生成 hreflang 链接。
9. useDefaultOgImage - 是否使用默认 OG Image
useDefaultOgImage 默认值是 true,即使用 construcMetadata 自动匹配的 OG Image。
如果你想在动态页面,根据内容自动生成 OG Image 样式和内容,需要将 useDefaultOgImage 设置为 false,与此同时,同时在动态页面的同级目录创建 opengraph-image.tsx 文件,开发自适应内容的 OG Image 样式。
生成的 Metadata 内容
constructMetadata() 会生成以下内容:
1. 基础 Meta 标签
<title>About Us | Nexty.dev</title>
<meta name="description" content="Learn more about our mission..." />
<meta name="keywords" content="" />
<meta name="author" content="nexty.dev" />2. Canonical URL
<link rel="canonical" href="https://nexty.dev/about" />3. 多语言 hreflang
<link rel="alternate" hreflang="en-US" href="https://nexty.dev/about" />
<link rel="alternate" hreflang="zh-CN" href="https://nexty.dev/zh/about" />
<link rel="alternate" hreflang="ja-JP" href="https://nexty.dev/ja/about" />
<link rel="alternate" hreflang="x-default" href="https://nexty.dev/about" />x-default 的作用:
- 告诉搜索引擎,当用户语言不匹配时,显示哪个版本
- 通常指向默认语言(英文)
4. Open Graph 标签
<meta property="og:type" content="website" />
<meta property="og:title" content="About Us | Nexty.dev" />
<meta property="og:description" content="Learn more about..." />
<meta property="og:url" content="https://nexty.dev/about" />
<meta property="og:site_name" content="Nexty.dev" />
<meta property="og:locale" content="en" />
<meta property="og:image" content="https://nexty.dev/og.png" />
<meta property="og:image:alt" content="About Us | Nexty.dev" />5. Twitter Card 标签
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="About Us | Nexty.dev" />
<meta name="twitter:description" content="Learn more about..." />
<meta name="twitter:site" content="https://nexty.dev/about" />
<meta name="twitter:image" content="https://nexty.dev/og.png" />
<meta name="twitter:creator" content="@judewei_dev" />6. Robots 标签
<!-- noIndex = false(默认) -->
<meta name="robots" content="index, follow" />
<!-- noIndex = true -->
<meta name="robots" content="noindex, nofollow" />constructMetadata() 用法示例
场景 1:首页
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: 'Home' })
return constructMetadata({
title: t('title'), // 从多语言文件读取
description: t('description'), // 从多语言文件读取
locale: locale as Locale, // 传入当前语言标识,用于自动处理 metadata 中的语言信息和必要的多语言 URL
path: '/', // 首页路径
})
}场景 2:普通页面
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: 'About' })
return constructMetadata({
title: t('title'),
description: t('description'),
locale: locale as Locale,
path: `/about`,
})
}场景 3:使用自定义 OG 图片
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params
const t = await getTranslations({ locale, namespace: 'Pricing' })
return constructMetadata({
title: t('title'),
description: t('description'),
locale: locale as Locale,
path: `/pricing`,
images: ['images/og/pricing.png'], // 自定义 OG 图片
})
}场景 4:动态 OG 图片
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { slug } = await params;
const result = await getProductBySlug(slug);
if (!result.success || !result.data) {
return constructMetadata({
title: "404",
description: "Product not found",
noIndex: true,
path: `/product/${slug}`,
});
}
const product = result.data;
const fullPath = `/product/${slug}`;
return constructMetadata({
title: product.name,
description: product.tagline,
path: fullPath,
useDefaultOgImage: false, // 🔑 关键:禁用默认图片
// Next.js 会自动查找同级目录下的 opengraph-image.tsx 文件并应用。
})
}场景 5:博客文章(多语言)
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale, slug } = await params
const { post } = await getPostBySlug(slug, locale)
if (!post) {
return constructMetadata({
title: "404",
description: "Page not found",
noIndex: true, // 404 页面不索引
locale: locale as Locale,
path: `/blogs/${slug}`,
})
}
// 检测哪些语言有该文章
const availableLocales: string[] = []
for (const checkLocale of LOCALES) {
const { post: localePost } = await getPostBySlug(slug, checkLocale)
if (localePost) {
availableLocales.push(checkLocale)
}
}
return constructMetadata({
title: post.title,
description: post.description,
images: post.featuredImageUrl ? [post.featuredImageUrl] : [],
locale: locale as Locale,
path: `/blogs/${post.slug.replace(/^\//, '')}`,
availableLocales: availableLocales.length > 0 ? availableLocales : undefined,
})
}场景 6:付费/受限内容
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params
return constructMetadata({
title: "Premium Dashboard",
description: "Access your premium features.",
locale: locale as Locale,
path: `/dashboard/premium`,
noIndex: true, // 付费内容不让搜索引擎索引
})
}场景 7:合并多个 URL 的 Canonical
// 场景1:/free-trial 和 /pricing 指向同一内容,但你想让搜索引擎认为 /pricing 是规范 URL
// 场景2:/blogs 和 /blogs?ref=docs 指向同一页面,你需要告诉搜索引擎 /blogs 是规范 URL
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params
return constructMetadata({
title: "Start Your Free Trial",
description: "Try all features for free.",
locale: locale as Locale,
path: "/free-trial",
canonicalUrl: "/pricing", // 指向 /pricing 作为规范 URL
})
}场景 8:使用外部 CDN 的 OG 图片
export async function generateMetadata({
params,
}: MetadataProps): Promise<Metadata> {
const { locale } = await params
return constructMetadata({
title: "Case Study",
description: "How we helped Company X achieve results.",
locale: locale as Locale,
path: "/case-studies/company-x",
images: [
"https://cdn.yourdomain.com/case-studies/company-x-og.png" // 绝对 URL
],
})
}