观察基于 create-react-doc 搭建的文档站点, 发现网页代码光秃秃的一片(见下图)。这显然是单页应用 (SPA) 站点的通病 —— 不利于文档被搜索引擎搜索 (SEO)。
难道 SPA 站点就无法进行 SEO 了么, 那么 Gatsby、nuxt 等框架又为何能作为不少博主搭建博客的首选方案呢, 此类框架赋能 SEO 的技术原理是什么呢? 在好奇心的驱动下, 笔者尝试对 creat-react-doc 进行赋能 SEO 之旅。
搜索引擎优化在实践之前, 先从理论上分析为何单页应用不能被搜索引擎搜索到。核心在于 爬虫蜘蛛在执行爬取的过程中, 不会去执行网页中的 JS 逻辑, 所以隐藏在 JS 中的跳转逻辑也不会被执行。
查看当前 SPA 站点打包后的代码, 除了一个根目录 index.html 外, 其它都是注入的 JS 逻辑, 因此浏览器自然不会对其进行 SEO。
此外, 搜索引擎详优化是一门较复杂的学问。如果你对 SEO 优化比较陌生, 建议阅读搜索引擎优化 (SEO) 新手指南 一文, Google 搜索中心给出了全面的 17 个最佳做法, 以及 33 个应避免的做法, 这也是笔者近期在实践的部分。
SEO 在 SPA 站点中的实践案例在轻文档站点的背景前提下, 我们暂不考虑 SSR 方案。
对市面上文档站点的 SEO 方案调研后, 笔者总结为如下四类:
静态模板渲染方案
404 重定向方案
SSG 方案
预渲染方案
静态模板渲染方案静态模板渲染方案以 hexo 最为典型, 此类框架需要指定特定的模板语言(比如 pug)来开发主题, 从而达到网页内容直出的目的。
404 重定向方案404 重定向方案的原理主要是利用 GitHub Pages 的 404 机制进行重定向。比较典型的案例有 spa-github-pages、sghpa。
但是遗憾的是 2019 年 Google , 因此此类重定向方案在当下是无利于 SEO 的。spa-github-pages 作者也表示如果需要 SEO 的话, 使用 SSG 方案或者付费方案 Netlify。
SSG 方案SSG 方案全称为 static site generator, 中文可译为路由静态化方案。社区上 nuxt、Gatsby 等框架赋能 SEO 的技术无一例外可以归类此类 SSG 方案。
以 nuxt 框架为例, 在约定式路由的基础上, 其通过执行 nuxt generate 命令将 vue 文件转化为静态网页。
例子:
-| pages/ ---| about.vue/ ---| index.vue/静态化后变成:
-| dist/ ---| about/ -----| index.html ---| index.html经过路由静态化后, 此时的文档目录结构可以托管于任何一个静态站点服务商。
预渲染方案经过上文对 SSG 方案的分析, 此时 SPA 站点的优化关键已经跃然纸上 —— 静态化路由。相较于 nuxt、Gatsby 等框架存在约定式路由的限制, create-react-doc 在目录结构上的组织灵活自由。它的建站理念是文件即站点, 同时它对存量 markdown 文档的迁移也十分便捷。
以 blog 项目结构为例, 其文档结构如下:
-| BasicSkill/ ---| basic/ -----| DOM.md -----| HTML5.md静态化后应该变成:
-| BasicSkill/ ---| basic/ -----| DOM -------| index.html -----| HTML5 -------| index.html经过调研, 该构思与 prerender-spa-plugin 预渲染方案一拍即合。预渲染方案的原理可以见如下图:
至此技术选型定下为使用预渲染方案实现 SSG。
预渲染方案实践create-react-doc 在预渲染方案实践的步骤简单概况如下(完整改动可见 mr):
改造 hash 路由为 history 路由。因为 history 路由结构与文档静态化目录结构天然匹配。
export default function RouterRoot() { return ( - <HashRouter> + <BrowserRouter> <RoutersContainer /> - </HashRouter> + </BrowserRouter> ) }在开发环境、生成环境的基础上新增预渲染环境, 同时对路由进行环境匹配。其主要解决了资源文件与主域名下的子路径的对应关系。过程比较曲折, 感兴趣的同学可以见 。
const ifProd = env === 'prod' + const ifPrerender = window.__PRERENDER_INJECTED && window.__PRERENDER_INJECTED.prerender + const ifAddPrefix = ifProd && !ifPrerender <Route key={item.path} exact - path={item.path} + path={ifAddPrefix ? `/${repo}${item.path}` : item.path} render={() => { ... }} />兼容 prerender-spa-plugin 在 webpack 5 的使用。
官方版本当前未支持 webpack 5, 详见 issue, 同时笔者存在对预渲染后执行回调的需求。因此当前 fork 了一份版本 出来, 解决了以上问题。
经过上述步骤的实践, 终于在 SPA 站点中实现了静态化路由。