Skip to content

純 CSR 的 SEO 問題:爬蟲為什麼看到空白的 div

純 CSR 的 SEO 問題:爬蟲看到空白容器

純用戶端渲染(CSR)的頁面,伺服器回傳的 HTML 長這樣:

html
<!DOCTYPE html>
<html lang="zh-Hant-TW">
  <head>
    <title>My App</title>
    <script type="module" src="/assets/index.js"></script>
  </head>
  <body>
    <div id="app"></div>
  </body>
</html>

一個空的 <div> 內容要等 JavaScript 下載、執行、取資料、渲染之後才出現。

這一頁在 索引流程 的第三關(被渲染)就可能卡住。

Google 的兩階段渲染

它會渲染,但分成兩階段

階段做什麼時間
1. 抓取下載 HTML,解析裡面的連結與標籤立即
2. 渲染排進佇列,用瀏覽器引擎執行 JavaScript通常幾秒,但沒有保證

第一階段看到的就是那個空的 <div>。內容要等第二階段才進得了索引。

舊資料常說第二階段要等好幾天,那是 2018 年的狀況,官方後來已經澄清不再適用。但「沒有保證」這件事沒有變,排隊的頁面一多就會等。

第二階段有三個風險

風險後果
佇列延遲輪到之前新內容都不會被收錄
程式碼出錯就放棄任何未捕捉的錯誤都可能讓整頁渲染失敗
資源被封鎖robots.txt 擋掉 JS 或 API 就渲染不出來

第二個風險特別隱形,在您的瀏覽器上正常,在爬蟲的環境(不同的引擎版本、沒有 cookie、可能沒有某些 API)可能就掛了。

社群爬蟲完全不執行 JavaScript

這是比搜尋引擎更嚴重的問題

Google 至少會排隊渲染,但 Facebook、X、LINE、Slack、Discord 的預覽爬蟲 完全不執行 JavaScript

它們只讀伺服器回傳的原始 HTML。所以:

  • 靠程式碼注入的 Open Graph 標籤 → 無效
  • 靠程式碼注入的 Twitter Card無效
  • 分享出去的連結 → 預覽卡片一片空白

而且這個問題 沒有「等一下就會好」的可能,它們永遠不會執行您的程式碼。

六個連帶問題

內容看不到只是最明顯的一個。純 CSR 還會帶來這些:

問題說明
軟性 404狀態碼永遠是 200,不存在的頁面被判定為 軟性 404
檔頭標籤無效社群爬蟲讀不到程式碼注入的 meta 標籤
結構化資料可能漏掉JSON-LD 動態注入的風險
爬蟲走不到連結路由用 <button> 實作時,爬蟲追不到
LCP 變差內容要等程式碼執行完,LCP 自然晚
分頁與無限滾動失效爬蟲不捲動 ,只看到第一批

軟性 404 最容易忽略

純 CSR 的專案幾乎必然有這個問題:

js
// 找不到路由時顯示 404 元件,但 HTTP 狀態碼仍是 200
{
  path: '/:pathMatch(.*)*',
  component: NotFoundPage,
}

狀態碼在伺服器就決定了,前端路由改不了它。搜尋引擎看到「頁面說找不到、但回傳 200」的矛盾,就標記為軟性 404。

怎麼驗證

第一步:檢視網頁原始碼

不要用開發者工具的元素面板

工具顯示什麼
開發者工具的 Elements執行完程式碼之後 的 DOM,永遠看得到內容
檢視網頁原始碼(Ctrl + U伺服器實際回傳 的 HTML

用 Elements 面板永遠驗不出這個問題。

在原始碼裡搜尋這幾樣,找不到就是有問題:

搜尋確認什麼
文章標題的一段文字內容在不在 HTML 裡
og:image社群標籤在不在
application/ld+json結構化資料在不在
<a href導覽連結是不是真的 <a>

第二步:用命令列確認

bash
# 看伺服器實際回傳的內容
curl -s https://example.com/some-page | head -50

# 確認社群標籤在不在
curl -s https://example.com/some-page | grep -o 'og:[a-z]*'

# 模擬社群爬蟲(它們不執行 JS,所以結果就是這樣)
curl -s -A "facebookexternalhit/1.1" https://example.com/some-page | grep 'og:title'

這是最貼近社群爬蟲的測試

curl 不執行 JavaScript,它看到的就是社群爬蟲看到的。抓不到 og:title 就代表分享出去的卡片會是空的。

第三步:網址檢查工具

Google Search Console 的網址檢查會顯示 搜尋引擎渲染後 的 HTML 與截圖:

看什麼判斷
渲染後的 HTML內容有沒有出現
截圖版面對不對
頁面資源有沒有資源載入失敗
JavaScript 主控台訊息有沒有程式碼錯誤

這一步能抓到「本機正常、爬蟲環境出錯」

主控台訊息那一區特別有用,它會顯示爬蟲執行您的程式碼時發生的錯誤。那些錯誤在您的瀏覽器上可能不會出現。

解決方向

按成本從低到高:

方案成本適合
預渲染頁面數不多、內容不常變
靜態產生(SSG)內容型網站
伺服器端渲染(SSR)內容需要即時
增量靜態再生(ISR)中高電商、大量頁面

各模式的取捨見 渲染模式怎麼選

最小的改動:只對需要的頁面做預渲染

不必整站改造

先盤點:哪些頁面真的需要被搜尋引擎收錄?

需要不需要
首頁登入後的操作介面
產品或內容頁後台管理
部落格文章使用者的個人設定
關於、聯絡頁購物車與結帳流程

通常只有前面幾頁需要。針對那少數路由做預渲染,其餘維持原狀。

預渲染在 建置時 把指定路由跑成靜態 HTML:

js
// vite.config.js(概念示意)
export default {
  plugins: [
    prerender({
      routes: ['/', '/about', '/products', '/blog'],
    }),
  ],
};

預渲染的兩個限制

  1. 路由必須事先列舉:動態產生的網址(/products/:id)無法涵蓋,除非在建置時就知道所有 id。
  2. 要確認產出真的有內容:有些設定會漏掉,產出的還是空容器加程式碼。檢視原始碼確認

過渡期的補救

還沒改成 SSR 或 SSG 之前,這幾件事能減輕問題:

補救解決什麼
在 HTML 裡放上基本的 meta 標籤社群卡片至少有標題與描述
導覽連結改用 <a href>爬蟲走得到
伺服器對不存在的路徑回傳真正的 404避免軟性 404
<div id="app"> 裡放骨架內容第一階段至少看到一點文字
html
<!-- 在容器裡放基本內容,程式碼執行後會被取代 -->
<div id="app">
  <h1>網站名稱</h1>
  <p>網站的一段簡介,讓第一階段抓取至少有內容可讀。</p>
</div>

這只是補救,不是解決

容器裡的骨架內容與真正的頁面內容不同,能提供的訊號有限。而且每一頁都放同一段的話,等於 全站標題重複 的問題。

真正的解法還是把 HTML 在伺服器或建置時產生出來。

怎麼確認自己的站沒有這個問題

靜態產生的網站在建置時就把每一頁跑成完整的 HTML,所以天生沒有這個問題。建置完直接檢查產出的檔案:

bash
npm run build
# 產出 dist/seo/rendering-csr-problem.html 等靜態檔案

檢視原始碼時,這些東西都應該在:

項目在原始碼裡
文章內容
<title>description
Open Graph 標籤
ld+json 結構化資料
導覽與側邊欄的 <a href>

這類架構同時也在瀏覽器端接手路由,所以頁面切換仍然是即時的,靜態產生加上用戶端接手,兼得兩者的優點

驗證方式很簡單:

bash
# 建置後直接檢查產出的 HTML
grep -c 'og:title' dist/seo/rendering-csr-problem.html
grep -o '<title>[^<]*' dist/seo/rendering-csr-problem.html

檢查清單

項目標準
檢視原始碼看得到主要內容必備
原始碼裡有 <title>description必備
原始碼裡有 Open Graph 標籤必備
原始碼裡有結構化資料(若有標記)必備
導覽連結是真的 <a href>必備
不存在的路徑回傳真正的 404必備
curl 測過社群爬蟲會看到什麼建議
用網址檢查工具看過渲染結果與主控台訊息建議
需要被收錄的頁面已預渲染或 SSR必備

常見問題

Google 會執行 JavaScript,那到底有什麼問題?

會,但那是排在佇列裡的第二階段,輪到之前內容都不算數,而且程式碼出錯就直接放棄。更關鍵的是社群平台的爬蟲完全不執行 JavaScript,分享連結時只會抓到空容器,預覽卡片一片空白。

怎麼確認爬蟲看到什麼?

用瀏覽器的檢視網頁原始碼功能,不要用開發者工具的元素面板,後者顯示的是執行完程式碼的結果,永遠看得到內容。原始碼是伺服器實際回傳的內容,也是社群爬蟲看到的東西。

只有內容看不到的問題嗎?

不只。狀態碼永遠是 200 所以不存在的頁面變成 軟性 404 、檔頭標籤靠程式碼注入所以社群卡片空白、結構化資料同理、路由連結若用按鈕實作爬蟲還走不到,這些都是連帶問題。

最小的改動是什麼?

先盤點哪些頁面真的需要被收錄,通常只有首頁與內容頁,登入後的操作介面不需要。針對那少數幾頁做預渲染,其餘維持原狀,成本遠低於整站改成伺服器端渲染。

用了預渲染還需要注意什麼?

路由必須事先列舉,動態產生的網址無法涵蓋。另外要確認產出的靜態檔案裡真的有內容與檔頭標籤,而不是只有一個空容器加上程式碼,有些設定會漏掉這一步。

延伸閱讀

參考資料:Google 搜尋中心:了解 JavaScript SEO 基本知識