別讓 Google 找不到你的文章
SSR Blog 的 SEO 踩坑實錄
用 Astro + PostgreSQL 建 Blog 過程中,每一個 SEO 決策背後的原因——以及那些差點讓文章消失在搜尋結果裡的坑。
我們做了哪些事?#
每一層處理一個 SEO 面向,由上到下各有明確的消費者。
sitemap-posts.xml 從 DB 即時查詢已發布文章,build 時自動 patch 進 sitemap-index.xmlhttp://localhost 和 https://blog.joneshong.com 被判定重複/admin/ 和 /api/。指向兩份 sitemapCore Web Vitals 優化#
Astro 的殺手鐧:預設不送任何 JavaScript。React 元件只在需要互動的地方 hydrate(client:only),其餘全是靜態 HTML。
我們怎麼發現問題的?#
不是預先設計好 SEO 再實作——是每個坑踩下去才發現的。
sitemap-posts.xml 沒人引用#
@astrojs/sitemap 生成 sitemap-index.xml 時只包含靜態頁。動態的 sitemap-posts.xml 被無視了。修法:build 後自動 patch sitemap-index 加入 posts sitemap。
領悟:自動化工具不一定覆蓋所有 case,SSR 動態頁需要手動補漏。
Rich HTML 的 content.length 包含整個 CSS#
content.length / 400 算閱讀時間,但 Rich HTML 格式的 content 有 25000+ 字元的 CSS + HTML tags。Strip tags 後才正確。
領悟:任何跟 content 長度相關的邏輯,都要先想清楚「這個 content 是什麼格式」。
Vite dev server + subpath = 死路#
Vite 的 ES module import 用絕對路徑,nginx sub_filter 改不完。解法:dev preview 不用 Vite dev server,改用 built SSR server。
領悟:Astro islands 的 component-url 屬性也需要被 sub_filter 改寫。
複製給你的 AI Agent#
把下面這段話貼給你的 AI coding agent,讓它幫你評估 SSR Blog 的 SEO 設定。
延伸閱讀#
這些是在過程中實際參考過、借鑑過的資源。
| 文章 | 為什麼重要 |
|---|---|
| Apideck: Your MCP Server Is Eating Your Context Window | 啟發了「隱形成本」的類比 |
| SEO for Astro: How to Make the Fastest Framework Also the Smartest | Astro SEO 實作的完整指南 |
| JSON-LD Blog Post Example | BlogPosting schema 的參考實作 |
| Multilingual SEO and Hreflang Guide | 67% 錯誤率數據的來源 |
| How to Improve Core Web Vitals | SSR 效能優化的具體方法 |
SEO 是工程,不是玄學#
每一個決策都有原因,每一個坑都有教訓。
SSR Blog 的 SEO 不是設完 meta tag 就結束——是一個持續迭代的工程問題。
問題 1: 動態文章不在 sitemap
→ 手動補 sitemap-posts.xml
問題 2: SSR 無快取
→ 靜態頁 prerender + 動態頁 SSR
問題 3: 多語言 SEO 易出錯
→ hreflang 雙向 + x-default
驗證: Google Search Console 手動提交 + 監控
// 歸納
Astro SSR + 自動化 SEO pipeline
不漏收錄、不重複、不延遲