原文链接:Routing and SEO Metadata in Next.js 13,2023年6月22日,by Vishwas Gopinath
导读:在本系列的前两篇文章中,作者探讨了 Next.js 13 App Router 架构下, 路由(Routing) 和 布局(Layouts) 的相关知识。本文着眼于网页的搜索引擎优化,帮助你的页面更好的被搜索引擎收录,提高站点的搜索排名。
在构建 Web 应用程序时,确保正确的搜索引擎优化(SEO)和 Web 共享性对于增加可见性和吸引用户至关重要。Next.js 13 引入了 Metadata API,允许我们为每个页面定义元数据,确保在共享或索引页面时显示准确的相关信息。本文中,我们将探讨如何利用 Metadata API 来增强路由元数据。
路由元数据的重要性
当用户在 Next.js 应用程序中导航到不同页面时,能提供适当的文档标题和描述非常重要。使用 create-next-app 创建 Next.js 应用程序时,默认情况下,在 layout.tsx
文件中包含元数据对象。
// app/layout.tsx
export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
}
我们将要学习关于元数据的内容。上述元数据对象包含一个 title
属性(其值为“Create Next App”)以及一个 description
属性(其值为“Generated by create next app”)。title
属性控制浏览器中显示的文档标题,而 description
属性对应于文档头部区域中的 <meta name="description" content="xxx"/>
标签。
当然,这些预定义的元数据可能无法为应用程序中的各个页面提供所需的 SEO 优势。目前为止,站点中的所有页面的文档标题和描述都是一样的。
比如,如果引导页(landing page)标题是“Builder.io – Visual Headless CMS”,应用程序中每个页面都显示这个标题。从搜索引擎角度来看,如果每个网页具有适当的文档标题和描述对于增加用户发现你网站机会至关重要。
配置与路由相关的元数据
在 layout.tsx
或 page.tsx
文件中配置元数据有两种方法:
- 导出一个静态的
metadata
对象,或者 - 导出一个动态的
generateMetadata
函数
静态元数据
要定义静态元数据,要从 layout.tsx
或 page.tsx
文件中导出 Metadata
类型对象。以下是在app/page.tsx
中定义元数据对象的示例:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Builder.io - Visual Headless CMS',
description: 'Build digital experiences for any tech stack, visually.',
}
export default function Home() {
// Home component
}
当访问 localhost:3000
并检查文档头部时,你会看到如下的产出物:
<title>Next.js</title>
<meta name="description" content="Build digital experiences for any tech stack, visually.">
动态元数据
动态元数据取决于动态信息,例如当前路由参数、外部数据或父路由段中的 Metadata
。
要定义动态元数据,要从 layout.tsx
或 page.tsx
文件导出一个返回 Metadata
对象的 generateMetadata
函数。
以下是在 app/products/[productId]/page.tsx
中定义 generateMetadata
函数的示例:
import type { Metadata } from "next";
type Props = {
params: { productId: string };
};
export const generateMetadata = ({ params }: Props): Metadata => {
return {
title: `Product - ${params.productId}`,
};
};
export default function ProductDetails() {
// Product details component
}
当访问 localhost:3000/products/camera
并检查文档的 <head>
部分时,你会看到如下的产出物:
<title>Product - Camera</title>
当我们需要外部数据时,就需要使用异步 generateMetadata
函数:
import type { Metadata } from "next";
type Props = {
params: { productId: string };
};
export const generateMetadata = async (
props: Props
): Promise<Metadata> => {
const { params } = props
const product = await fetchProductById(params.productId)
return {
title: product.title,
};
};
generateMetadata
函数接受一个 props 参数,是一个包含当前路由参数的对象。props
对象包含两个属性:
params
:一个对象,其中包含从根路由段到调用generateMetadata
路由段中的动态路由参数。例如,如果路由为app/products/[productId]/page.js
,并且 URL 为/products/1
,则params
对象会是{ productId: '1' }
。searchParams
:一个对象,其中包含当前 URL 的查询参数(search parameters)。例如,如果 URL为/products?productId=1
,则searchParams
对象会是{ a: '1' }
。
本示例使用了 params
,但你也可以同样轻松地使用 searchParams
。
如果元数据不依赖于运行时信息,则应使用静态 metadata
对象而不是 generateMetadata
函数来定义。
元数据合并
元数据可以从 layout.tsx
文件和 page.tsx
文件中导出。因此,在同一路由的各个段中可能会有多个元数据导出。
这种情况下,元数据对象将会合并以便产生最终输出的元数据。重复键名从根段开始替换,直到最接近最终page.js
的段位。
我们以下述 app/layout.tsx
文件导出的元数据为例:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Builder.io - Visual Headless CMS',
description: 'Build digital experiences for any tech stack, visually.',
}
并且在 app/blog/page.tsx
中导出以下元数据:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Builder.io Blog'
}
当访问 localhost:3000/blog
时,生成的元数据输出如下:
<title>Builder.io Blog</title>
<meta name="description" content="Build digital experiences for any tech stack, visually.">
这种情况下,根布局中的标题被位于 blog
文件夹内的 page.tsx
文件中的标题覆盖。但是,根布局中的描述信息由于存在于合并后的元数据对象中,导致博客页面也包含相同的描述。
要配置与路由相关联的元数据,请注意以下几点:
- 元数据可以从
layout.tsx
文件和page.tsx
文件导出。在布局中定义的元数据适用于该布局内所有页面,而在页面中定义的元数据仅适用于这个特定页面 - 按顺序解析元数据,从根段位开始到最接近最终
page.js
的段位 - 来自同一路由上的多个段位导出的元数据将会合并,基于出现顺序替换重复键。换句话说,页面中的元数据会覆盖布局中的元数据
默认情况下,在创建 Next.js 应用程序时生成根布局中的 Metadata 对象。但是,我们可以灵活地在布局和页面中都定义 Metadata 对象,页面中的元数据优先于布局中的元数据。
title
字段
有很多我们可以设置的元数据字段,有一个字段从路由角度来看具有重要意义,那就是 title
。title
字段主要目的是定义文档标题,它可以被定义为简单字符串或可选模板对象。
字符串
设置标题属性最直接的方法是使用字符串。例如,在文件 layout.tsx
或 page.tsx
中,你可以按以下方式定义标题:
import { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Builder.io - Visual Headless CMS'
}
这将在 <head>
标签内输出以下 HTML 代码:
<title>Builder.io - Visual Headless CMS</title>
模板对象
Next.js 还允许你使用模板对象定义标题字段,这提供了更大的灵活性。例如,在文件 layout.tsx
或 page.tsx
中,你可以按以下方式定义标题:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: {
template: '...',
default: '...',
absolute: '...',
},
}
template
属性用于为子路由段定义的标题添加前缀或后缀default
属性会在没有定义标题时,充当子路由段的默认标题- 如果定义了
absolute
属性,则会覆盖父级段中设置的template
属性
默认 title
当你想要为未明确定义标题的子路由段提供默认标题时,就要用 title.default
属性。
例如,在 app/layout.tsx
文件中,我们可以设置如下的默认标题:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: {
default: 'Builder.io - Visual Headless CMS',
},
}
如果子路由段(例如 app/blog/page.tsx
)没有定义标题,则会回退到默认标题。因此,输出会是:
<title>'Builder.io - Visual Headless CMS'</title>
模板 title
要通过添加前缀或后缀来创建动态标题,你可以使用 title.template 属性。这个属性适用于子路由段而不是定义它的段。
在 app/layout.tsx
文件中,将元数据定义如下:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: {
template: '%s | Builder.io',
default: 'Builder.io - Visual Headless CMS',
},
}
在 app/blog/page.tsx
文件中,你可以像往常一样定义标题:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: 'Blog',
}
输出会是:
<title>Blog | Builder.io</title>
绝对 title
如果你想提供一个完全忽略父段中设置的 title.template
的标题,可以使用 title.absolute
属性。
在 app/layout.tsx
文件中,将元数据定义如下:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: {
template: '%s | Builder.io',
default: 'Builder.io - Visual Headless CMS',
},
}
在 app/blog/page.tsx
文件中,将标题定义如下:
import type { Metadata } from 'next'
export const metadata: Metadata = {
title: {
absolute: 'Blog',
},
}
输出会是:
<title>Blog</title>
让我们总结一下标题字段的行为。
在 layout.js
文件中:
title
(作为字符串)和title.default
用于为没有设置标题的子区域(child sections)设置默认标题。进一步,如果父路由段中存在标题template
属性,子区域也会以它作为基础模板(前缀或后缀),补全标题title.absolute
也用于为子区域设置默认标题,但它忽略了父区域(parent sections)中设置的template
属性
在 page.js
文件中:
- 如果页面没有自己的标题,则使用最近父级标题
title
(作为字符串)用于定义某个页面的标题。与子区域类似,如果页面没有标题,则它从最近父区域继承标题template
并将其用作基础,补全标题title.absolute
用于定义某个页面的标题,并且忽略了父区域中设定的标题template
- 由于页面始终是路由的最终部分(不再有子区域),因此在
page.js
中使用title.template
没有任何意义
总结
Next.js 提供了强大的工具来管理路由元数据,使我们能够优化应用程序的 SEO 和 Web 共享性(web shareability)。
通过利用 Metadata API,我们可以为每个页面定义元数据,确保在共享或索引页面时显示准确的相关信息。
与路由相关联配置元数据对于优化单个页面至关重要。我们可以使用静态元数据或动态元数据,这取决于应用程序的需求。静态元数据允许我们直接定义元数据对象,而动态元数据则依赖于动态信息。
合并元数据可以将同一路由下的不同段的元数据结合起来,从而得出最终元数据输出。让我们可以在应用程序不同层级上对元数据进行精细控制。
title
字段可以被定义为字符串或模板对象,在设置文档标题方面提供灵活性。模板对象允许使用前缀或后缀、没有明确定义标题的路由段的默认标题以及覆盖父模板的绝对标题等方式创建动态标题。
通过理解和利用 Next.js 中 Metadata API 的功能,我们可以优化 Web 应用程序以适配搜索引擎、改善 Web 共享性,并提供更好的用户体验。