Next.js 14 SEO 最佳实践(2)Next.js 14 SEO 要点

继续分享 Next.js 14 SEO 最佳实践。上次主要介绍 SEO 基础知识,以及一般原则,这次重点放在 Next.js 14 上。我先武断暴言一句:Vercel 在 Next.js 14 上的整了个烂活儿,给开发者留下了很大的坑要填。

页面组件避免 'use client'

Next.js 14(也可能更早,待查)引入了 serverclient 组件的区分。前者没有状态,渲染一次后就不会变化;后者则可以与用户互动,响应用户操作,并将变化体现在视图之中。

SEO 有一些必备信息,包括 title, description,需要通过 export const metadatagetMetadata 返回。它们都不支持 client 组件,所以,我们应该避免让页面组件(page.tsx)成为 client

按照官方文档的说法,client 组件也会在服务器端进行渲染,然后发给客户端,所以比较合适的做法是:

  1. 组件化颗粒度可以细一些,以便将不需要状态的 server 组件和响应用户操作的 client 组件进行隔离。
  2. 必要的数据,尤其是希望搜索引擎抓取并索引的数据,在页面组件里完成读取,然后传递给 client 组件,作为初始值,完成第一次 SSR。
  3. getMetadata 时,可以使用 React.cache 函数将结果缓存起来,重复使用,降低网络请求成本
  4. 限制 client 的功能比较多,比如 createContext,所以工具函数文件不妨也分离得细一些,避免产生影响。

确保返回页面 metadata

SEO 需要一些信息来理解页面,并在搜索时呈现给用户,这些信息基本上都要通过 metadata 提交给搜索引擎,所以我们应该保证在 page.tsx 里都有返回合适的 metadata

一般来说,metadata 需要包含以下数据,后面的代码简单演示了动态获取并返回信息的方式:

  1. 页面 title
  2. 页面内容 description
  3. OpenGraph 信息
  4. Canonical link
  5. 如果有多语言,还应该有 alternate link
export async function generateMetadata(
{ params, searchParams }: PracticeDetailPageProps,
parent: any
): Promise<Metadata> {
// 读取路由参数
const courseId = params.courseId;

// 这里的加载函数可以提前 cache,以避免重复请求 API
const courseDetail =
await webApi.courseApi.fetchCourseDetail<CourseDetailType>(courseId);

const metadata: Metadata = {
title: courseDetail.title,
description: courseDetail.description,
alternates: {
canonical: `https://www.hackquest.io/practices/${courseId}`
},
openGraph: {
title: courseDetail.title,
description: courseDetail.description,
image: courseDetail.thumbnail,
},
};

return metadata;
}

增加组件颗粒度,创造条件使用 server 组件

因为静态内容更有利于 SEO,而 server 组件是生成静态内容的最好方式,所以我们应该尽量多使用 server 组件。

问题在于 client 组件具有传染性,当某个组件内部使用状态的时候,它就必须是 client 组件,所以我们应该尝试让组件颗粒化的程度更细,仅在需要交互的组件里使用状态。

搜索引擎的爬虫可以沿着超链接爬取一个又一个页面,而它们并不知道某个 <div> 上绑定了跳转事件,所以应该使用 <Link> 帮助搜索引擎索引整个网站。

很多时候,为满足埋点等需求,我们会使用 onClick 事件,没关系,因为事件会先于链接跳转触发,所以我们只需要正常使用埋点功能即可。

全站使用统一的通用导航

使用全站通用的顶部导航和底部导航是提升 SEO 和网页收录的好办法。

通用导航里面可以包含大量站内链接,方便搜索引擎爬虫遍历整个网站,所以我们应该在通用导航里大量使用 <Link> ,让爬虫可以顺藤摸瓜,找到我们网站里的全部内容。当然,从用户角度的来说,通用导航可能并不是每次打开页面的主要目的,所以,通过 CSS 控制通用导航的显示状态,让爬虫可以按图索骥,但是不影响普通用户阅读网页,是我们应该做的。

所以,我们应该让整个网站使用统一的导航内容,只调整显示状态,而不是每个页面显示不同的导航。

分离工具函数和依赖,避免污染

影响 client 组件的因素很多,比如各种 hooks,createContext,等。如果我们在 a 函数里使用了这些功能,然后在 bimport a,会导致 b 也必须是 use client。这就要求我们尽量将引用的工具函数进行分离,比如单独写一个 utils.client.ts 用来存放客户端才需要使用的工具函数。

使用 [[...slug]] 处理 URL

如同前面所说,我们应该给所有资源配置好合适的 URL。比如我们有一些博客内容,需要建立博客页面,那么,我们就应该给翻页功能分配好独立的 URL,如 /p/${page},而不是把翻页状态保存在页面状态里。同样,如果我们支持按标签、分类筛选,就可以提供 /tag/${tag-slug} 这样的 URL。

此时,为了能更好的复用代码、复用组件,我们可以用可选通配符组件 [[...slug]] 来制作页面。

const Blog: React.FC<BlogProps> = async function ({
searchParams = {},
params: { slug = [] }
}) {
const limit = 12;
const minPage = Number(slug[1]) < 1 ? 1 : Number(slug[1]);
const page = slug[0] === 'p' ? minPage : 1;

// 处理加载分页后的博客数据
....
}

制作 robots.txtsitemap.xml

制作 robots.txt 比较简单,很多时候手写一个放到 /public 目录里即可,所以我不再赘述。

制作 sitemap.xml 就复杂很多,因为网站可能会有很多页面,其中更有不少需要动态生成,所以我们一般需要创建 sitemap.ts 才能完成。部署的时候,构建脚本会自动调用这个文件,生成新的 sitemap.xml

详情请参考官方文档:Generating a sitemap using code (.js, .ts)

小结

如果你对 Next.js 或者 SEO 有什么问题和想法,欢迎留言讨论。

本站目前还在招商:本站广告 2024 年招商,欢迎感兴趣的老板私信洽谈。

系列文章

系列长文好更不好看,所以我决定以后都在 notion 上做汇总页,方便日后整体阅读。本文实际已经基本写完了,感兴趣的同学可以先去围观:https://meathill.notion.site/Next-js-14-SEO-90d31eee22ff4621af6620524b4b2773?pvs=4

如果您觉得文章内容对您有用,不妨支持我创作更多有价值的分享:


已发布

分类

来自

标签:

评论

欢迎吐槽,共同进步

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据