第8章:现代框架对SEO的深度影响
1. 引言
Next 和 Nuxt 是两个 🔥热度和使用度都最高 的现代 Web 开发框架,它们分别基于 ⚛️React 和 🖖Vue 构建,也代表了这两个生态的 🌐全栈框架。
- Next 是由 Vercel 公司 开发的 React 框架,
- Nuxt 则是 Vue 的 服务器端渲染框架。
共同目标: - 简化开发流程
- 提升应用性能
为 SEO 优化 提供丰富的内置功能
2. Next和Nuxt页面渲染机制
Next和Nuxt都提供了常见的四种渲染方式,SSR、SSG、IRR、SCR。但在细节上有非常大的区别。
1. 服务器端渲染(SSR)
- ✅ 优点:
📈 搜索引擎可以直接抓取完整的HTML内容,有利于 SEO快速建立索引 - 实现:
- Next.js (App Router):默认所有组件都是服务器组件,自动SSR
Nuxt.js:通过
asyncData
或fetch
2. 静态站点生成(SSG)
- ✅ 优点:
预渲染页面,加载速度快,对 SEO非常有利 - 实现:
Next.js (App Router):使用generateStaticParams
函数进行静态生成
Nuxt.js:在nuxt.config.ts
中的routeRules
可直接配置
3. 增量静态再生(ISR)
- ✅ 优点:
结合了 SSG的性能优势 和 动态内容的新鲜度,有利于SEO 实现:
Next.js (App Router):通过在页面文件中导出revalidate
变量实现
Nuxt.js:在nuxt.config.ts
中的routeRules
可直接配置4. 客户端渲染(CSR)
- 缺点:
不利于SEO,因为初始HTML内容较少 实现:
Next.js (App Router):使用use client
指令将组件标记为客户端组件
Nuxt.js:默认支持2.1 Nuxt(Vue)的优势
✨自然混合渲染:
- 只需使用
await useFetch + Lazy
配置,服务端/客户端渲染边界自动处理 - 开发者无需刻意区分渲染环境,按常规写法即可完成混合渲染
- 传统艺能:Vue生态的强DX(开发体验)设计,简单易用
潜在问题: - 高度封装可能在复杂场景引发底层不可控问题
- 特殊需求需深入框架底层,魔法失效时调试困难
2.2Next.js (React) 的挑战
显式边界处理:
- 必须手动标记
use client
区分客户端组件 - 需主动处理水合错误(如
useEffect
依赖问题) - 流式渲染需手动包裹
<Suspense>
边界
优势代价: - 显式控制带来更高灵活性,适合复杂场景
- 但开发心智负担显著增加
在页面中只要使用await useFetch + Lazy的属性配置,而哪些组件在客户端渲染,哪些组件在服务端渲染是自动处理的,我们只需要想往常往下写就可以。
这确实也是Vue生态的传统艺能,简单易用强DX,但也会在某些更复杂和特殊场景下这种高度化的封装会带来一些从根本上无法解决的问题
Next相比之下,写法上会难受不少,一个原因是边界的写法,另外一个是有时候要手动处理水合问题。
3. 两个框架的体验以及Seo问题大合集
3.1. Next App SSR路由跳转缓慢
🔀 Next.js 路由变更全流程解析流程:
- 触发路由变更:用户点击链接或通过编程方式触发路由变更。🔗
2. 请求 RSC Payload:Next.js 首先请求新路由的 RSC(React Server Components)payload,它包含了服务器组件的结构和数据。📡 - 显示加载状态:在 RSC payload 请求完成前,如果存在 loading.js 文件,Next.js 会显示其中定义的 加载 UI。⏳
- 处理 RSC Payload:客户端接收 RSC payload 并开始处理。
- 流式渲染:如果使用了 流式渲染(例如通过 React Suspense),部分内容可能会提前显示。💧
- 客户端组件加载:开始加载和执行标记为 'use client' 的 客户端组件。
- 水合(Hydration):客户端 React 开始水合过程,使 静态 HTML 变为可交互组件。💡
- 数据获取:如有客户端数据请求(例如在 useEffect 中),这些操作会在此时执行。
- 路由更新完成:新页面完全加载并可交互,loading.js 中的 UI 被 新页面内容完全替换。✅
这就会导致一个非常严重的问题,因为RSC Payload是直接来自服务端,在Rsc Payload完成前是不会有Loading的,就会导致页面停滞一会,尤其是第一次渲染,且prefetch在这种场景也没用,而这段时间是对于用户是没有任何反馈的(如果没有加进度条之类的过度效果),也就是说路由反应晦涩(点一下需要反应)。如图(3G网速):
3.2 Next SSR动态路由时的TDK问题
在使用 generateMetadata
的时候,在目前的正式版本中(15canary有了)会导致 loading 直接不加载(因为会提前完全准备完页面),那么在之前我是怎么解决的那?
import { Metadata } from 'next'
import { headers } from 'next/headers'
function isSSR() {
return headers().get('accept')?.includes('text/html') // for RSC navigations, it uses either `Accept: text/x-component` or `Accept: */*`, for SSR browsers and other client use `Accept: text/html`
}
const fallback: Metadata = {
title: 'Loading...'
}
type GenerateMetadata<T> = (params: T) => Promise<Metadata>
const getMetadataWithFallback =
<Params>(generateMetadata: GenerateMetadata<Params>, staticMetadata?: Partial<Metadata>) =>
(params: Params) => {
return isSSR() ? generateMetadata(params) : Promise.resolve({ ...fallback, ...staticMetadata })
}
export default getMetadataWithFallback
这是种非常丑陋的写法把源数据的更新放在客户端去更新,还是不会让loading加载出来。那么更好的解决方案是什么那?
是通过 experimental.streamingMetadata
流式Meta选项,这个实验性选项,会让generateMetadata
流式生成,让loading可以加载出来。但同样也有个问题了。
显然Meta源数据标签被放在了Body里,就是比较靠后的位置,但是seo应该是只会分析head里的meta标签(这里我有点不太确定)。
这是一个大问题,解决的方式同样是实验属性中的htmlLimitedBots
它传入一个正则去匹配爬虫的UserAgent,当爬虫的时候就不进行这种源数据流式传输了。
3.3 Nuxt如何绕过这两个问题的,以及造成的新问题
Nuxt的框架非常的爽,它的客户端组件和服务端组件并不会分开,而是绑定在一起的。
const { data: SeoData } = await useFetch('/api/seo', {
query: { url: path, ...query },
lazy: true
})
在组件和页面中,仅仅通过这种方式就可以了。当是在 服务端请求渲染(比如刷新或新打开页面)时,系统会 自动 await 请求,而当是 客户端导航 时,请求将 异步执行,从而直接避免了客户端导航时组件的等待问题。
✨好处是:
- 用户不会感觉有延迟;
- 不会再次从服务端加载结构性文件。
⚠️但代价是什么?
因为在 客户端导航时是异步的,如果有些组件中的 meta 标签依赖接口返回的数据,就可能出现undefined
的情况。
不过——这 并不影响爬虫和 SEO,因为爬虫不会执行 Nuxt 的客户端导航逻辑。
但如果你确实希望 meta 标签中包含完整的数据,有两种方式可以考虑:
使用一些 hack 手段(如延迟渲染 meta); - 移除 lazy,不管什么情况都等数据加载完后再渲染 —— 但这会让体验比 Next 的做法还糟糕。🥴
3.4 Nuxt 的首屏问题 🚨
Nuxt 的首屏加载太重了,加载了大量的 框架基础文件,这导致:
- 首屏性能差;
- 服务端渲染也慢;
- LCP(最大内容绘制)指标难以达标📉。
并且 Nuxt 的文档流也不像 Next 那样支持 流式渲染,所以首次渲染体验更差。
3.5 Nuxt 的致命问题 💥
Nuxt 有两个 严重影响 SEO 的问题,我也因此直接将项目迁移到了 Next —— Nuxt 在 SEO 方面确实没法做下去。
3.5.1 NUXT_DATA 的 JSON 化
在服务端渲染时,无论是 Next 还是 Nuxt,都会将服务端返回的数据注入到 HTML 中。但两者方式差异巨大:
Next 是将数据作为 序列化字符串 注入;
Nuxt 则是将数据直接 JSON 格式化 放入 HTML 中。
⚠️ 这导致了几个问题:
- 容易被非法爬虫误解析;
- 体积更大,加载慢;
- 不利于细粒度的懒加载。
3.5.1 NUXT_DATA JSON化
首先服务端渲染,不管是Next还是Nuxt都会把服务端的数据添加到HTML Document里,但他们放的方式区别很大,Next是放序列化字符串,Nuxt是放JSON格式。
大家可能觉得放JSON还更好一些(JSON会有性能优势 https://v8.dev/blog/cost-of-javascript-2019#json )。
,并且这还是更利于爬虫爬取的内容。而后端的接口,其实爬虫不需要的信息会特别多,比如id、用户信息、ip信息这种。
当这些不需要内容太多了的时候,本身要突出的内容和关键词都无法突出了,这是个压倒性的错误。
另外一方面是有数据安全的问题(这意味着把数据往互联网公开了),另外一方面是当然可以设置payloadExtraction
为false,也会变成序列化字符串,但却会造成更多的问题(除非在一开始就是false,而默认值为true这个选项).
3.5.2 秘钥暴露
它把环境变量会直接暴露出来,使用NUXT_PUBLIC的,还会暴露打包的设备信息。
可能这也是压垮我的最后一根稻草吧,即使Nuxt的用户体验、DX、客户端性能都是远大于Next的,但为了流量和长期考虑不得不重构应用换成Next,实际上在重构后整个应用的SEO流量几乎翻倍了。
结论
关键结论
- Next.js 优势:
- 首屏加载速度、SEO优化、大型项目扩展性表现突出 🚀
- 更适合高性能要求和复杂应用场景
- Nuxt 亮点:
- 开发体验更友好,学习成本较低 ✨
- 适合快速迭代的中小型项目
- 共同优势:
全栈渲染方案支持(SSR/SSG/ISR/CSR) 💡
无论选择哪个框架,深入理解其工作原理和潜在问题都是至关重要的,这样才能在开发过程中做出正确的优化决策,并在必要时采取适当的措施来弥补框架的不足。最终,成功的项目不仅取决于所选择的工具,还取决于如何巧妙地运用这些工具来满足特定的项目需求。
欢迎加入群聊,我们一起讨论一些更有趣的技术、商业、闲聊。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。