背景
觀察基於 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 優化