SPA 站点 SEO 改造实录:从 hash 路由迁移到 History API 的完整方案

一、背景:一个被搜索引擎"拒之门外"的 SPA

我们的项目是一个基于 Vue 3 构建的内容型单页应用,上线初期为了开发便利,路由直接采用了默认的 hash 模式——URL 形如:

https://example.com/#/article/123
https://example.com/#/category/seo

开发阶段一切顺畅,直到上线三个月后复盘数据时发现一个残酷的事实:Google 收录量几乎为零,百度更是完全没有索引。Search Console 里提交的 sitemap 显示"已发现但未编入索引",抓取到的页面内容永远是首页那一份空壳 HTML。

问题的根源很明确:hash 路由的 # 之后的内容不会被发送到服务器,搜索引擎爬虫拿到的永远是同一个 URL 和同一份 HTML。对于依赖内容分发的站点来说,这等于自断流量命脉。

于是我们启动了这次 SEO 改造,核心目标只有一个:把 hash 路由迁移到 History API 模式,让每个页面拥有真实可被抓取的 URL

二、为什么 hash 路由对 SEO 不友好

在动手之前,有必要把原理说清楚,避免团队里有人觉得"改个路由模式而已"。

  1. URL 片段不参与服务端请求# 后面的内容由浏览器本地解析,服务器永远只收到 https://example.com/,无法针对不同页面返回不同内容。
  2. 爬虫难以执行完整 JS:虽然 Google 宣称能渲染 JS,但渲染预算有限、排队时间长,对中小站点极不友好;百度、必应对 SPA 的渲染支持更弱。
  3. 无法做服务端差异化:没有独立 URL,就无法配置独立的 title、meta description、canonical、结构化数据。
  4. 分享与统计受限:社交平台抓取、第三方统计工具对 hash 的识别都存在偏差。

结论:hash 模式适合后台管理系统,绝不适合面向搜索引擎的内容站点。

三、迁移方案总览

整个改造分为四层,缺一不可:

层级 改造内容 关键点
路由层 hash → history 模式 createWebHistory
服务端 配置 fallback 到 index.html Nginx / CDN 规则
渲染层 预渲染或 SSR,输出真实 HTML 解决空壳问题
元信息层 动态 title / meta / canonical 配合路由变化

下面逐层展开。

四、路由层:从 hash 到 history

Vue Router 的迁移本身很简单:

// 改造前
import { createRouter, createWebHashHistory } from 'vue-router'
const router = createRouter({
  history: createWebHashHistory(),
  routes
})

// 改造后
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
  history: createWebHistory(),
  routes
})

同时要处理两件事:

  • 旧链接兼容:在路由守卫里检测到带 #/ 的旧 URL,用 router.replace 重定向到新路径,避免老用户和已收录链接 404。
  • base 路径:如果站点部署在子目录,createWebHistory('/sub/') 要写对,否则所有资源路径都会错。

五、服务端:History 模式的"生命线"

history 模式下,用户直接访问 https://example.com/article/123 时,服务器必须返回 index.html,否则就是 404。这是最容易翻车的一步。

Nginx 配置:

location / {
  try_files $uri $uri/ /index.html;
}

如果用了 CDN 或对象存储,需要在控制台配置"404 回源到 index.html"或"错误文档重定向"。注意:这一步只解决"能打开",不解决"有内容",真正的 SEO 问题还在下一层。

六、渲染层:让爬虫看到真实内容

history 模式解决了 URL 问题,但爬虫拿到的仍然是空壳 HTML。我们对比了三种方案:

  1. 纯 CSR + 动态渲染(Dynamic Rendering):用 Puppeteer 判断 UA,爬虫来临时渲染后返回。实现快,但维护成本高,且 Google 已不推荐。
  2. 预渲染(Prerender):构建时用 vite-plugin-prerenderprerender-spa-plugin 为每个路由生成静态 HTML。适合内容更新不频繁的站点,我们最终选择了这条路线。
  3. SSR / SSG:用 Nuxt 或 Vite SSR 彻底改造。效果最好,但改造成本最高,周期不允许。

最终采用预渲染 + 增量更新:文章发布时触发构建,生成对应静态页;对时效性要求高的列表页保留动态渲染兜底。

七、元信息层:每个页面都要"独一无二"

URL 和内容解决后,还要保证每个页面的 head 信息独立。我们用 @vueuse/headvue-meta 在路由切换时动态注入:

useHead({
  title: article.title + ' - 站点名',
  meta: [
    { name: 'description', content: article.summary },
    { property: 'og:title', content: article.title }
  ],
  link: [
    { rel: 'canonical', href: `https://example.com/article/${article.id}` }
  ]
})

canonical 标签尤其重要:预渲染页面和 SPA 路由可能产生重复内容,canonical 能明确告诉搜索引擎哪个是主版本。

八、收尾:301 重定向与 sitemap 重建

迁移完成后还有两件必做之事:

  • 旧 URL 301 重定向:把 /#/article/123 永久重定向到 /article/123,把历史权重传递过来。
  • 重建 sitemap:用构建脚本扫描所有路由,生成包含真实 URL 的 sitemap.xml,并重新提交到 Search Console 和百度站长平台。

九、效果与复盘

改造上线后第 6 周开始出现明显变化:

  • Google 收录量从个位数增长到 1200+
  • 百度收录从 0 增长到 400+
  • 自然搜索流量占全站比例从不足 2% 提升到 31%
  • 核心关键词排名进入前 3 页。

复盘下来,最关键的不是路由模式本身,而是**"URL 可达 + 内容可抓 + 元信息独立"这三件事必须同时做到**。只改路由不改渲染,等于白改;只做预渲染不改路由,爬虫依然进不来。

十、给同类项目的建议

如果你正准备做同样的改造,按这个顺序推进可以少走弯路:

  1. 先确认服务端 fallback 配置无误,再动前端路由;
  2. 优先选择预渲染而非动态渲染,长期维护成本更低;
  3. 路由切换时同步更新 title、description、canonical,别漏;
  4. 迁移后立即做 301 和 sitemap 重建,别让历史权重流失;
  5. 用 Search Console 的"网址检查"逐个验证抓取效果,别只看收录数字。

SPA 不是 SEO 的天敌,hash 路由才是。把 URL 还给搜索引擎,内容站点才能真正被看见。

未经允许不得转载:任鹏个人博客 » SPA 站点 SEO 改造实录:从 hash 路由迁移到 History API 的完整方案

赞 (0) 打赏

评论 0

取消
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏