Hugo 靜態網站社群預覽完整踩坑紀錄:og:image、Cloudflare、Facebook 爬蟲 403

把文章分享到社群平台,結果跳出「No title」、「No description」,或是預覽圖莫名出現頭像——這篇文章完整記錄這些問題的根本原因,以及一個一個解掉的過程。

背景

在完成 Hugo 靜態網站 SEO + AEO 最佳化之後,實際把文章貼到 Buffer 準備同步發佈到 Facebook、LinkedIn 和 X,結果馬上踩到一系列坑。


問題一:Buffer 顯示「No title、No description」

症狀

在 Buffer 貼入文章 URL,預覽顯示:

No title
blog.example.com
No description

Buffer 錯誤訊息:

We were unable to generate a preview of this link. This may happen if the website blocks Buffer’s access to images and other content.

排查過程

第一反應是 og:titleog:description 沒有輸出。用 curl 模擬 Facebook 爬蟲:

curl -s -A "facebookexternalhit/1.1" https://blog.example.com/posts/your-post/

HTML 內容完全正確,og 標籤都在。問題不在程式碼。

接著測試 robots.txt

curl -s https://blog.example.com/robots.txt

回傳的不是自己寫的內容,而是這個:

# As a condition of accessing this website, you agree to abide by the following
# content signals:
# search: building a search index...
# ai-input: inputting content into one or more AI models...
# ai-train: training or fine-tuning AI models.

根本原因

Cloudflare 的 Managed robots.txt 功能開著,它會攔截對 /robots.txt 的請求,用 Cloudflare 自己的「Content Signals Framework」格式回應,完全取代你放在 static/robots.txt 的檔案。

Buffer 的爬蟲讀到這個格式不認識的 robots.txt,可能誤判為被封鎖,導致放棄抓取 metadata。

解法

Cloudflare Dashboard → Scrape Shield → Managed robots.txt → 關閉

關閉後 robots.txt 恢復正常。


問題二:Facebook Sharing Debugger 回傳 403

症狀

到 Facebook Sharing Debugger (developers.facebook.com/tools/debug/) 貼入 URL 重新抓取,結果:

回應碼:403
回應碼原因:This response code could be due to a robots.txt block.
Please allowlist facebookexternalhit on your sites robots.txt config.

排查過程

明明 robots.txt 已經是 User-agent: * Allow: /,照理說 Facebook 爬蟲不應該被擋。

用 curl 模擬確認:

curl -s -o /dev/null -w "%{http_code}" \
  -A "facebookexternalhit/1.1" \
  https://blog.example.com/posts/your-post/
# 回傳:200

本機模擬是 200,但 Facebook 真實爬蟲拿到 403。

根本原因

Cloudflare 的 Bot Fight Mode 把 Facebook 真實爬蟲的 IP 當成惡意 bot,在網路層直接封鎖,請求根本到不了 origin server,更不會去看 robots.txt。

這就是為什麼:

  • 本機用相同 User-Agent curl 是 200(本機 IP 沒被 Cloudflare 標記)
  • Facebook 真實爬蟲是 403(Facebook 的 IP 被 Bot Fight Mode 封鎖)

解法(選一)

方案 A:直接關閉 Bot Fight Mode(適合靜態網站,攻擊面小)

Cloudflare Dashboard → Security → Bots → Bot Fight Mode → 關閉

靜態 HTML 網站基本上沒什麼可攻擊的(無資料庫、無後端程式、無登入),Bot Fight Mode 帶來的保護效益有限,關掉換取社群爬蟲正常運作是合理的取捨。

方案 B:建立 WAF Custom Rule 精準放行(保留 Bot Fight Mode)

Cloudflare Dashboard → Security → WAF → Custom Rules → Create rule

在「Edit expression」模式貼入以下表達式,Action 選 Skip → Skip all remaining custom rules

(http.user_agent contains "facebookexternalhit") or
(http.user_agent contains "Twitterbot") or
(http.user_agent contains "LinkedInBot") or
(http.user_agent contains "Bufferbot") or
(http.user_agent contains "PerplexityBot") or
(http.user_agent contains "GPTBot") or
(http.user_agent contains "ClaudeBot")

問題三:沒有設定 og:image,頭像卻自動出現在預覽圖

症狀

文章 front matter 沒有設定 image,程式碼也沒有輸出 og:image meta tag,但 Facebook、Buffer 的預覽圖卻顯示網站頭像。

確認 meta tag 確實不存在:

curl -s https://blog.example.com/posts/your-post/ \
  | grep 'property="og:image"\|property=og:image'
# 沒有輸出 → meta tag 確實不存在

根本原因

Facebook 有 fallback 機制:當頁面沒有 og:image meta tag 時,Facebook 爬蟲會自動掃描頁面裡所有的 <img> 標籤,抓第一張圖作為預覽圖。

確認頁面裡的 img 標籤:

curl -s https://blog.example.com/posts/your-post/ \
  | grep -o 'img[^>]*src=[^[:space:]>]*'
# 輸出:img src=/images/avatar.jpg

Sidebar 的作者頭像用 <img> 標籤載入,Facebook 掃到了就拿去用。

解法

把 sidebar 頭像從 <img> 改成 CSS background-image。Facebook 爬蟲只掃 <img> 標籤,不解析 CSS,所以改用 CSS 載入之後,Facebook 就掃不到這張圖。

Layout 改動(layouts/partials/header.html):

<!-- 改前 -->
<img src="/images/avatar.jpg" alt="作者名稱" class="sidebar-avatar" />

<!-- 改後 -->
<span class="sidebar-avatar" role="img" aria-label="作者名稱"></span>

CSS 改動(assets/css/main.css):

/* 改前 */
.sidebar-avatar {
  width: 100px;
  height: 100px;
  border-radius: 50%;
  object-fit: cover;
}

/* 改後 */
.sidebar-avatar {
  width: 100px;
  height: 100px;
  border-radius: 50%;
  display: inline-block;
  background-image: url('/images/avatar.jpg');
  background-size: cover;
  background-position: center;
}

視覺上完全一樣,但 Facebook 爬蟲看不到頭像了。

改完後驗證:

curl -s https://blog.example.com/posts/your-post/ \
  | grep -o 'img[^>]*src=[^[:space:]>]*'
# 沒有輸出 → 頁面裡已無任何 <img> 標籤

問題四:og:description 有設但預覽沒有文字

症狀

og:description 在 HTML 裡確認存在,但 Buffer 或 Facebook 預覽只有大標題,沒有描述文字。

根本原因

兩個可能:

1. 描述 fallback 到站點描述而非文章摘要

Hugo 模板常見寫法:

<!-- 錯誤:沒有 description front matter 時 fallback 到站點描述 -->
<meta property="og:description"
  content="{{ with .Params.description }}{{ . }}{{ else }}{{ site.Params.description }}{{ end }}">

每篇文章分享出去都顯示一樣的站點 tagline,社群平台判定為重複內容可能降低展示優先度。

2. 快取問題

Facebook 快取了舊版 metadata,即使 HTML 已經更新,Facebook 仍顯示舊內容。

解法

修正 description fallback 邏輯,改成用文章摘要(<!--more--> 之前的內容):

{{ $desc := site.Params.description }}
{{ if .IsPage }}
  {{ if .Params.description }}
    {{ $desc = .Params.description }}
  {{ else }}
    {{ $desc = .Summary | plainify | truncate 160 }}
  {{ end }}
{{ end }}

<meta name="description" content="{{ $desc }}">
<meta property="og:description" content="{{ $desc }}">
<meta name="twitter:description" content="{{ $desc }}">

優先順序:front matter description文章摘要前 160 字站點描述(首頁用)

清除快取

Cloudflare Dashboard → Caching → Cache Purge → Custom Purge → 貼入 URL

清完後到 Facebook Sharing Debugger 再點一次「再次抓取」。


整體流程回顧

分享連結到 Buffer
No title / No description
    ↓ 排查
Cloudflare Managed robots.txt 攔截 → 關閉
Facebook Sharing Debugger 重抓 → 403
    ↓ 排查
Cloudflare Bot Fight Mode 封鎖 Facebook 爬蟲 IP → 關閉
頭像出現在預覽圖(無 og:image 時 Facebook 掃 <img>)
    ↓ 排查
sidebar <img> 被掃到 → 改成 CSS background-image
✅ 預覽正常:標題 + 文章摘要,無多餘圖片

小結

這次碰到的四個問題,表面上看起來像是「og 設定沒設好」,實際上大部分根因都在 Cloudflare 的預設行為

問題根因一行解法
No title / No descriptionCloudflare Managed robots.txt關閉 Managed robots.txt
Facebook 爬蟲 403Cloudflare Bot Fight Mode關閉或 WAF 放行
頭像出現在預覽Facebook 掃 <img> fallback改 CSS background-image
Description 顯示站點描述Hugo fallback 邏輯錯誤Summary 優先於 site.Params.description

對靜態網站來說,Cloudflare 帶來的保護主要是 CDN 加速和 L3/L4 DDoS 防護,應用層的 Bot Fight Mode 和 Managed robots.txt 反而會干擾正常的社群爬蟲和 AI 引擎,根據需求調整比全部開啟要好。