SEO 在 SPA 站點中的實踐

背景

觀察基於 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反饋

相關鏈接

原文出處

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章