背景

观察基于 create-react-doc 搭建的文档站点, 发现网页代码光秃秃的一片(见下图)。这显然是单页应用 (SPA) 站点的通病 —— 不利于文档被搜索引擎搜索 (SEO)。

难道 SPA 站点就无法进行 SEO 了么, 那么 Gatsbynuxt 等框架又为何能作为不少博主搭建博客的首选方案呢, 此类框架赋能 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-pagessghpa

但是遗憾的是 2019 年 Google 调整了爬虫算法, 因此此类重定向方案在当下是无利于 SEO 的。spa-github-pages 作者也表示如果需要 SEO 的话, 使用 SSG 方案或者付费方案 Netlify

SSG 方案

SSG 方案全称为 static site generator, 中文可译为路由静态化方案。社区上 nuxtGatsby 等框架赋能 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>
)
}
  • 在开发环境、生成环境的基础上新增预渲染环境, 同时对路由进行环境匹配。其主要解决了资源文件主域名下的子路径的对应关系。过程比较曲折, 感兴趣的同学可以见 issue
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 站点中实现了静态化路由

SEO 优化附加 buff, 站点秒开?

SEO 优化至此, 来看下站点优化前后 FP、FCP、LCP 等指标数据的变化。

blog 站点为例, 优化前后的指标数据如下(数据指标统计来自未使用梯子访问 gh-pages):

优化前: 接入预渲染方案前, 首次绘制(FP、FCP) 的时间节点在 8s 左右, LCP 在 17s 左右。

优化后: 接入预渲染方案后, 首次绘制时间节点在 1s 之内开始, LCP 在 1.5s 之内。

对比优化前后: 首屏绘制速度提升了 8 倍, 最大内容绘制速度提升 11 倍。本想优化 SEO, 结果站点性能优化的方式又 get 了一个。

生成站点地图 Sitemap

在完成预渲染实现站点路由静态化后, 距离 SEO 的目标又近了一步。暂且抛开 SEO 优化细节, 单刀直入 SEO 核心腹地 站点地图

站点地图 Sitemap 格式与各字段含义简单说明如下:

<?xml version="1.0" encoding="utf-8"?>
<urlset>
<!-- 必填标签, 这是具体某一个链接的定义入口,每一条数据都要用 <url> 和 </url> 包含在里面, 这是必须的 -->
<url>
<!-- 必填, URL 链接地址,长度不得超过 256 字节 -->
<loc>http://www.yoursite.com/yoursite.html</loc>
<!-- 可以不提交该标签, 用来指定该链接的最后更新时间 -->
<lastmod>2021-03-06</lastmod>
<!-- 可以不提交该标签, 用这个标签告诉此链接可能会出现的更新频率 -->
<changefreq>daily</changefreq>
<!-- 可以不提交该标签, 用来指定此链接相对于其他链接的优先权比值,此值定于 0.0-1.0 之间 -->
<priority>0.8</priority>
</url>
</urlset>

上述 sitemap 中, lastmod、changefreq、priority 字段对 SEO 没那么重要, 可以见 how-to-create-a-sitemap

根据上述结构, 笔者开发了 create-react-doc 的站点地图生成包 crd-generator-sitemap, 其逻辑就是将预渲染的路由路径拼接成上述格式。

使用方只需在站点根目录的 config.yml 添加如下参数便可以在自动化发版过程中自动生成 sitemap

seo:
google: true

将生成的站点地图往 Google Search Console 中提交试试吧,

最后验证下 Google 搜索站点优化前后效果。

优化前: 只搜索到一条数据。

优化后: 搜索到站点地图中声明的位置数据。

至此使用 SSG 优化 SPA 站点实现 SEO 的完整流程完整实现了一遍。后续便剩下参照 搜索引擎优化 (SEO) 新手指南 做一些 SEO 细节方面的优化以及支持更多搜索引擎了。

小结

本文从 SPA 站点实现 SEO 作为切入点, 先后介绍了 SEO 的基本原理, SEO 在 SPA 站点中的 4 种实践案例, 并结合 create-react-doc SPA 框架进行完整的 SEO 实践。

如果本文对您有所帮助, 欢迎 star反馈

相关链接

原文出处

SEO 在 SPA 站点中的实践的更多相关文章

  1. 使用vue开发微信公众号下SPA站点的填坑之旅

    原文发表于本人博客,点击进入使用vue开发微信公众号下SPA站点的填坑之旅 本文为我创业过程中,开发项目的填坑之旅.作为一个技术宅男,我的项目是做一个微信公众号,前后端全部自己搞定,不浪费国家一分钱^ ...

  2. React 与 Redux 在生产环境中的实践总结

    React 与 Redux 在生产环境中的实践总结 前段时间使用 React 与 Redux 重构了我们360netlab 的 开放数据平台.现将其中一些技术实践经验总结如下: Universal 渲 ...

  3. 《SEO在网页制作中的应用》视频笔记

    学习了慕课网<SEO在网页制作中的应用>视频,今天将里面的知识整理一下. 一.SEO介绍 1.  搜索引擎工作原理 搜索引擎现在主流有百度.谷歌.360,他们都有庞大的搜索引擎数据库,每个 ...

  4. Cookie和Session在Node.JS中的实践(二)

    Cookie和Session在Node.JS中的实践(二) cookie篇在作者的上一篇文章Cookie和Session在Node.JS中的实践(一)已经是写得算是比较详细了,有兴趣可以翻看,这篇是s ...

  5. Mysql事务探索及其在Django中的实践(二)

    继上一篇<Mysql事务探索及其在Django中的实践(一)>交代完问题的背景和Mysql事务基础后,这一篇主要想介绍一下事务在Django中的使用以及实际应用给我们带来的效率提升. 首先 ...

  6. 如何在ASP.NET Web站点中统一页面布局[Creating a Consistent Layout in ASP.NET Web Pages(Razor) Sites]

    如何在ASP.NET Web站点中统一页面布局[Creating a Consistent Layout in ASP.NET Web Pages(Razor) Sites] 一.布局页面介绍[Abo ...

  7. 在express站点中使用ejs模板引擎

    在express站点中使用ejs模板引擎 文/玄魂 目录 在express站点中使用ejs模板引擎 前言 1.1         安装 1.2修改app.js 1.3创建测试页面 前言 使用 vs创建 ...

  8. 05-雷海林-mysql备份原理与在TDSQL中的实践

    05-雷海林-mysql备份原理与在TDSQL中的实践 下载地址: http://files.cnblogs.com/files/MYSQLZOUQI/05-%E9%9B%B7%E6%B5%B7%E6 ...

  9. ServiceStack.RabbitMQ在站点中使用时导致静态页面无法正常解析

    当站点中集成ServiceStack.RabbitMQ时快速处理异步请求时,官方建议初始化如下: public class AppHost : AppHostHttpListenerBase { pu ...

随机推荐

  1. 洛谷P1628合并序列【模板】(Trie+dfs)

    很久之前写的题了,当时不知道怎么dfs所以卡了一段时间,^_^ 题解:由于题目给了一大堆字符串,所以首先考虑应该可以建树,之后找到T所在的位置,对T所在的位置dfs就行了 代码: 1 #include ...

  2. java中的装箱及拆箱

    java中存在8中基本的数据类型,每一种数据类型都有包装类型. 包装类型:每一个基本的数据类型都会------对应一个包装类型. boolean------------------>Boolea ...

  3. 线程池原理讲解——ThreadPoolExecutor

    [这是前几天的存货,留着没发表,今天又复习一遍,润化了部分内容,继续干] 说线程池前,先简单回顾一下线程的状态吧: 1.线程状态转换 线程的五种状态,及其转换关系: 2.线程创建方式 三种:两个接口一 ...

  4. Spring(三) Spring IOC

    Spring 核心之 IOC 容器 再谈 IOC 与 DI IOC(Inversion of Control)控制反转:所谓控制反转,就是把原先我们代码里面需要实现的对象创 建.依赖的代码,反转给容器 ...

  5. web 安全 & web 攻防: XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)

    web 安全 & web 攻防: XSS(跨站脚本攻击)和 CSRF(跨站请求伪造) XSS(跨站脚本攻击)和CSRF(跨站请求伪造) Cross-site Scripting (XSS) h ...

  6. 2016 JS 笔试题汇总:

    1 1 1 CS&S(中软国际): 1 JavaScript 循环表达式: 2  JavaScript表达式boolean返回值: 3 网页中的事件/HTML 事件属性/JavaScript ...

  7. js logical operation all in one

    js logical operation all in one 逻辑运算 Logical AND (&&) Logical AND assignment (&&=) L ...

  8. Python Lambda & Functional Programming

    Python Lambda & Functional Programming 函数式编程 匿名函数 纯函数 高阶函数 # higher-order functions def apply_tw ...

  9. You Don't Know Chrome Features

    You Don't Know Chrome Features URL auto convert to QR Code click the tab URL address click QRCode ic ...

  10. webpack async load modules & dynamic code splitting

    webpack async load modules & dynamic code splitting webpack 按需/异步加载/Code Splitting webpack loade ...