SEO 在 SPA 站点中的实践
背景
观察基于 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>
)
}
- 在开发环境、生成环境的基础上新增
预渲染环境
, 同时对路由进行环境匹配。其主要解决了资源文件
与主域名下的子路径
的对应关系。过程比较曲折, 感兴趣的同学可以见 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 实践。
相关链接
- create-react-doc
- why-is-my-website-not-showing-up-on-google/
- A Technical Guide to SEO With Gatsby.js
- 优化向:单页应用多路由预渲染指南
- 除了 SSR,就没有别的办法了吗?
- 基于 SSR/SSG 的前端 SEO 优化
SEO 在 SPA 站点中的实践的更多相关文章
- 使用vue开发微信公众号下SPA站点的填坑之旅
原文发表于本人博客,点击进入使用vue开发微信公众号下SPA站点的填坑之旅 本文为我创业过程中,开发项目的填坑之旅.作为一个技术宅男,我的项目是做一个微信公众号,前后端全部自己搞定,不浪费国家一分钱^ ...
- React 与 Redux 在生产环境中的实践总结
React 与 Redux 在生产环境中的实践总结 前段时间使用 React 与 Redux 重构了我们360netlab 的 开放数据平台.现将其中一些技术实践经验总结如下: Universal 渲 ...
- 《SEO在网页制作中的应用》视频笔记
学习了慕课网<SEO在网页制作中的应用>视频,今天将里面的知识整理一下. 一.SEO介绍 1. 搜索引擎工作原理 搜索引擎现在主流有百度.谷歌.360,他们都有庞大的搜索引擎数据库,每个 ...
- Cookie和Session在Node.JS中的实践(二)
Cookie和Session在Node.JS中的实践(二) cookie篇在作者的上一篇文章Cookie和Session在Node.JS中的实践(一)已经是写得算是比较详细了,有兴趣可以翻看,这篇是s ...
- Mysql事务探索及其在Django中的实践(二)
继上一篇<Mysql事务探索及其在Django中的实践(一)>交代完问题的背景和Mysql事务基础后,这一篇主要想介绍一下事务在Django中的使用以及实际应用给我们带来的效率提升. 首先 ...
- 如何在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 ...
- 在express站点中使用ejs模板引擎
在express站点中使用ejs模板引擎 文/玄魂 目录 在express站点中使用ejs模板引擎 前言 1.1 安装 1.2修改app.js 1.3创建测试页面 前言 使用 vs创建 ...
- 05-雷海林-mysql备份原理与在TDSQL中的实践
05-雷海林-mysql备份原理与在TDSQL中的实践 下载地址: http://files.cnblogs.com/files/MYSQLZOUQI/05-%E9%9B%B7%E6%B5%B7%E6 ...
- ServiceStack.RabbitMQ在站点中使用时导致静态页面无法正常解析
当站点中集成ServiceStack.RabbitMQ时快速处理异步请求时,官方建议初始化如下: public class AppHost : AppHostHttpListenerBase { pu ...
随机推荐
- 洛谷P1628合并序列【模板】(Trie+dfs)
很久之前写的题了,当时不知道怎么dfs所以卡了一段时间,^_^ 题解:由于题目给了一大堆字符串,所以首先考虑应该可以建树,之后找到T所在的位置,对T所在的位置dfs就行了 代码: 1 #include ...
- java中的装箱及拆箱
java中存在8中基本的数据类型,每一种数据类型都有包装类型. 包装类型:每一个基本的数据类型都会------对应一个包装类型. boolean------------------>Boolea ...
- 线程池原理讲解——ThreadPoolExecutor
[这是前几天的存货,留着没发表,今天又复习一遍,润化了部分内容,继续干] 说线程池前,先简单回顾一下线程的状态吧: 1.线程状态转换 线程的五种状态,及其转换关系: 2.线程创建方式 三种:两个接口一 ...
- Spring(三) Spring IOC
Spring 核心之 IOC 容器 再谈 IOC 与 DI IOC(Inversion of Control)控制反转:所谓控制反转,就是把原先我们代码里面需要实现的对象创 建.依赖的代码,反转给容器 ...
- web 安全 & web 攻防: XSS(跨站脚本攻击)和 CSRF(跨站请求伪造)
web 安全 & web 攻防: XSS(跨站脚本攻击)和 CSRF(跨站请求伪造) XSS(跨站脚本攻击)和CSRF(跨站请求伪造) Cross-site Scripting (XSS) h ...
- 2016 JS 笔试题汇总:
1 1 1 CS&S(中软国际): 1 JavaScript 循环表达式: 2 JavaScript表达式boolean返回值: 3 网页中的事件/HTML 事件属性/JavaScript ...
- js logical operation all in one
js logical operation all in one 逻辑运算 Logical AND (&&) Logical AND assignment (&&=) L ...
- Python Lambda & Functional Programming
Python Lambda & Functional Programming 函数式编程 匿名函数 纯函数 高阶函数 # higher-order functions def apply_tw ...
- 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 ...
- webpack async load modules & dynamic code splitting
webpack async load modules & dynamic code splitting webpack 按需/异步加载/Code Splitting webpack loade ...