【部落格架設】告別 WordPress,用 Astro 重建部落格的完整紀錄
經營 WordPress 部落格這幾年,一直很在意網站速度和每個月的主機費用,直到發現 Astro 這個靜態網站框架,才決定把整個部落格搬家。這篇文章完整記錄從 WordPress 搬家到 Astro 的過程,包含文章轉移、圖片遷移到 CDN、部署到 Cloudflare Pages,以及搬家之後的心得。
為什麼要離開 WordPress
說真的,WordPress 用了這麼多年其實並沒有什麼大問題,外掛豐富、SEO 工具完整、後台操作也直覺,但隨著文章愈積愈多,有幾個點開始讓我很不舒服:
1. 網站速度 — 每次用 PageSpeed Insights 測試分數都不好看,PHP 即時渲染加上一堆外掛,首次載入就花掉不少時間,Core Web Vitals 的分數一直是個心頭痛。
2. 主機費用 — 台灣的主機費用說貴不貴、說便宜也不算便宜,每個月固定支出,流量高的月份還要擔心超量,對個人部落格來說有點浪費。
3. 資安維護 — WordPress 核心、佈景主題、外掛三個面向都要持續更新,一不小心就有安全漏洞,雖然這幾年都沒出事,但心裡總是有一根刺。
Astro 是什麼?
Astro 是一個專注於內容驅動網站的靜態網站框架,跟傳統框架最大的差別是它預設不會把 JavaScript 送到瀏覽器,也就是「零 JS」的概念。
靜態網站的意思是:每篇文章在 build 的時候就已經產生好 HTML 檔案,訪客進來直接拿到已經寫好的 HTML,不需要伺服器動態產生,速度自然比 WordPress 快很多。
搭配 Cloudflare Pages 免費方案部署的話,主機費用直接歸零,這對個人部落格來說真的很吸引人!!
選擇佈景主題
Kent 最後選擇了 Fuwari 這個主題,外觀乾淨、支援深色/淺色模式、內建文章搜尋,對技術部落格來說功能已經很完整了。
主要使用的技術棧:
- Astro 5.x — 靜態網站框架
- Svelte 5.x — 互動元件
- Tailwind CSS — 樣式
- Pagefind — 全文搜尋(build 時自動建立索引)
- Cloudflare Pages — 免費靜態網站託管
- Cloudflare R2 — 圖片 CDN 儲存
文章遷移
WordPress 原本有上百篇文章,一篇一篇複製貼上當然不是辦法,Kent 用 Node.js 寫了一套自動化腳本來處理,整體分成兩個步驟:
Step 1 — 用 WordPress REST API 拉取文章
WordPress 內建 REST API,不需要任何外掛就能直接透過網址取得所有文章資料,路徑長這樣:
https://你的網域/wp-json/wp/v2/posts抓取的思路如下:
-
分頁處理 — WordPress API 每次最多回傳 100 筆,超過 100 篇文章的話需要分頁。回應的 Header 裡有
X-WP-TotalPages欄位,腳本會讀這個數字決定要跑幾頁。 -
Retry 機制 — 網路偶爾會抖動,腳本加了 3 次自動重試,每次失敗等 1 秒再試,避免因為短暫的連線問題中斷整個抓取流程。
-
請求節流 — 每頁之間等 300ms,避免打太快被主機限流或封鎖。
-
一次抓齊 — 除了文章(posts)之外,媒體庫(media)、分類(categories)、標籤(tags)也一起抓下來,之後轉換時需要用到。
node fetch-all.js執行完之後,data/ 目錄下會有 posts.json、media.json、categories.json、tags.json 等檔案,這些就是後續轉換的原始資料。
Step 2 — 把 JSON 轉成 Markdown
拿到 JSON 之後,再用 convert.js 把每篇文章轉成 Astro 可以讀取的 .md 格式。
轉換的思路:
-
HTML → Markdown — 文章內容是 WordPress 存的 HTML,透過 turndown 這套工具轉成 Markdown,程式碼區塊、連結、圖片都能正確處理。
-
封面圖判斷 — 優先使用 WordPress 設定的精選圖片(
featured_media),如果沒有就自動從文章內容抓第一張圖片當封面。媒體 ID 對應到實際網址則靠前一步抓下來的media.json。 -
SEO description — 優先使用 WordPress 文章摘要(
excerpt),若沒有摘要就從文章內容擷取前 160 個字,順便把 HTML 標籤清掉,避免摘要出現原始 HTML 語法。 -
分類和標籤 — WordPress 儲存的是 ID,需要對照
categories.json和tags.json轉換成實際的名稱文字。 -
Frontmatter 自動產生 — 標題、日期、slug、描述、封面圖、分類、標籤全部整合進 YAML frontmatter,轉出來就是 Astro 可以直接使用的格式。
node convert.js轉換完的每篇文章都是一個 .md 檔案,直接放進 src/content/posts/ 就可以讓 Astro 讀取,整個流程跑完大約幾分鐘就能把幾百篇文章全部轉好,完全不需要手動處理。
圖片遷移到 Cloudflare R2
原本 WordPress 的圖片都存在主機的 /wp-content/uploads/ 目錄,搬家之後這個路徑就失效了,所以決定把圖片都搬到 Cloudflare R2。
什麼是 R2?
R2 是 Cloudflare 推出的物件儲存服務,定位跟 AWS S3 差不多,但有一個很重要的差別:流量費用(egress)免費。S3 每次有人讀取檔案都要收出流量費,R2 不收,對圖片這種高存取的靜態資源來說非常划算。免費額度每個月有 10GB 儲存空間,對個人部落格來說完全夠用!!
建立 R2 Bucket
STEP 1
登入 Cloudflare Dashboard,左側選單找到《 R2 物件儲存 》,點擊《 建立儲存貯體 》,輸入 Bucket 名稱(Kent 用的是 blog3c-assets)。
STEP 2
建立完成後,進入 Bucket 設定頁面,找到《 自訂網域 》,綁定自己的網域,例如 img.blog3c.net。綁定之後 Cloudflare 會自動幫你把這個網域對應到 R2,外部存取就直接用這個網域即可。
STEP 3
在《 R2 API 》頁面建立 API Token,權限選《 物件讀取 & 寫入 》,記下 Access Key ID 和 Secret Access Key,之後設定 rclone 時需要用到。
用 rclone 同步圖片
rclone 是一套支援各種雲端儲存的命令列工具,R2 支援 S3 相容 API,所以 rclone 設定時選 S3 類型並填入 Cloudflare 的端點即可:
[r2]type = s3provider = Cloudflareaccess_key_id = 你的Access Key IDsecret_access_key = 你的Secret Access Keyendpoint = https://<帳號ID>.r2.cloudflarestorage.com設定完成後,一行指令把本機的 images/ 目錄同步到 R2:
bash sync-images-to-r2.sh同步完成後,再把所有 Markdown 文章裡的圖片路徑批次替換成 CDN 網址:
https://img.blog3c.net/圖片名稱.png部署到 Cloudflare Pages
整個部落格最終是部署在 Cloudflare Pages 上,不是傳統的虛擬主機或 VPS,而是 Cloudflare 提供的靜態網站託管服務。每次 build 完把 dist/ 目錄推上去,Cloudflare 就會自動幫你分發到全球的 CDN 節點,訪客就近取得內容,速度非常快。免費方案對個人部落格已經完全足夠,不需要另外付主機費。
部署工具使用的是 Wrangler CLI,設定好 wrangler.toml 之後,一行指令就能 build 並部署:
npm run deploy這個指令背後做的事情:
astro build— 產生靜態 HTML/CSS/JSpagefind --site dist— 建立搜尋索引wrangler pages deploy dist --branch master— 部署到 Cloudflare Pages 正式環境- 呼叫 Cloudflare API 自動清除 CDN 快取
Google Analytics、AdSense 與 SEO 設定
網站架好之後,流量追蹤和廣告收益的設定當然也不能少,這部分在 Astro 上的做法跟 WordPress 差很多,WordPress 有外掛一鍵搞定,Astro 需要自己動手寫進 HTML。
Google Analytics
GA4 的追蹤碼直接寫進 Layout.astro(所有頁面共用的版面骨架),為了不影響網站效能,Kent 選擇搭配 Partytown 讓 GA 在 Web Worker 裡執行,不佔用主執行緒:
<script type="text/partytown" src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script><script type="text/partytown"> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-XXXXXXXXXX');</script>跟一般直接貼上 <script async> 的差別是,Partytown 會把這段腳本移交給 Web Worker 處理,讓 GA 不會拖慢頁面渲染速度,對 Core Web Vitals 有幫助。
Google AdSense
AdSense 設定上遇到的第一個問題是廣告版位的規劃。WordPress 上用的是 Ad Inserter 外掛,可以設定多個廣告 Block 插入不同位置。搬到 Astro 之後需要自己決定版位,Kent 最後設定了 5 個版位:
- 文章內廣告 ×2(In-article fluid)— 插在文章內文結尾
- Autorelaxed 廣告 — 文章底部
- Multiplex 廣告 — 文章底部
- 側欄廣告(auto 尺寸)— 桌機版側欄
AdSense 腳本一樣放在 Layout.astro 的 <head> 裡:
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-XXXXXXXXXXXXXXXX" crossorigin="anonymous"></script>另外要記得在網站根目錄放 ads.txt,內容格式如下,沒有這個檔案 AdSense 審核會過不了:
google.com, pub-XXXXXXXXXXXXXXXX, DIRECT, f08c47fec0942fa0SEO Meta Tags
WordPress 的 SEO 靠 Rank Math 或 Yoast 外掛處理,搬到 Astro 之後這些都要自己補上。Kent 在 Layout.astro 裡加了以下這些 meta tag:
<!-- Open Graph(FB、LINE 分享預覽) --><meta property="og:title" content="文章標題" /><meta property="og:description" content="文章摘要" /><meta property="og:image" content="封面圖網址" /><meta property="og:type" content="article" /><meta property="og:locale" content="zh_TW" />
<!-- Twitter Card --><meta name="twitter:card" content="summary_large_image" /><meta name="twitter:image" content="封面圖網址" />
<!-- 文章專用(發布時間、修改時間、作者) --><meta property="article:published_time" content="ISO日期" /><meta property="article:modified_time" content="ISO日期" /><meta property="article:author" content="Kent" />
<!-- Canonical(避免重複內容) --><link rel="canonical" href="文章網址" />文章封面圖的 og:image 直接從文章的 frontmatter image 欄位取得,這樣每篇文章分享出去都會有對應的封面圖預覽,不會全部都顯示同一張。
Google Search Console
Search Console 的驗證方式選《 HTML 標籤驗證 》,把驗證碼加進 <head> 即可:
<meta name="google-site-verification" content="驗證碼" />驗證完成後,把 sitemap 網址提交給 Search Console:
https://blog3c.net/sitemap-index.xmlAstro 有 @astrojs/sitemap 套件,build 的時候會自動產生 sitemap,不需要另外設定。
搬家之後遇到的坑
老實說搬家的過程不是一帆風順,這裡記錄幾個比較印象深刻的問題:
Wrangler 部署後只顯示「Cloudflare Pages not deploying」 — 這個坑卡了蠻久的!!安裝好 Wrangler 之後,第一次執行 wrangler login 進行 Cloudflare 驗證,過程中 Wrangler 會在 Cloudflare Pages 後台幫你建立一個預設的專案和分支。問題是預設分支不一定是 master,如果在驗證或初始化的過程中改動了分支名稱,或是用錯分支部署,Cloudflare Pages 後台的「正式環境」分支跟你實際推的分支對不上,訪客就只會看到「Cloudflare Pages not deploying」的提示,但 build log 看起來完全正常,非常難 debug。
解法是在 npm run deploy 的 wrangler 指令明確加上 --branch master:
wrangler pages deploy dist --branch master這樣就能強制指定部署到正式環境分支,不會再出現頁面空白的問題。
URL 轉址無限迴圈 — 舊網站的文章 URL 是 /blog/slug,新網站是 /blog/post/slug,設定 _redirects 轉址規則的時候用了萬用字元 /blog/*,結果 /blog/post/slug 也被匹配到,一直跳轉造成無限迴圈。解法是改用具名參數 /blog/:slug 只匹配單層路徑。
Tailwind 跨檔案 @apply 失效 — 在 markdown.css 裡用 @apply link 引用了定義在 main.css 的 class,在某些環境下會 build 失敗。解法是把樣式直接寫進 CSS,不要跨檔案 @apply。
WordPress HTML 實體字元殘留 — 文章標題裡有大量 –(en dash)、’(右單引號)等 HTML 實體,轉換腳本沒有處理到,需要用 sed 批次替換。
搬家之後的感受
部落格搬到 Astro 之後,第一個明顯感受是網站真的快很多,靜態 HTML 加上 Cloudflare CDN,全球載入速度都很穩定。
費用方面,Cloudflare Pages 和 R2 的免費額度對個人部落格完全夠用,等於主機費直接省下來了。
搜尋功能用 Pagefind 實作,build 的時候會自動對所有文章建立索引,不需要額外的伺服器,在靜態網站上就能有完整的全文搜尋,這點讓我很滿意。
唯一的缺點是技術門檻相對高,如果不熟悉前端工具鏈,環境設定和 debug 可能會花不少時間,不像 WordPress 安裝完就能馬上用。不過對工程師來說,這些反而是有趣的地方~
結論
如果你也是個人部落客,每個月在付主機費用、又在意網站速度,Astro + Cloudflare Pages 這個組合非常值得考慮。尤其是文章數量多的部落格,靜態化之後的速度提升會非常明顯!!
當然每個人的情況不一樣,WordPress 的生態系和外掛豐富度仍然無可取代,搬家之前最好先評估看看你最在乎的是什麼,再決定要不要動~
延伸閱讀
👉 【雲端主機】vCenter雲端主機介紹 v2ray VPN架設教學
👉 【NAS DIY】舊電腦先別丟!! 安裝 TrueNAS 15分鐘打造高效能網路硬碟
👉 【NAS DIY】5分鐘添加TrueCharts軟體庫,讓TrueNAS有更多內建APP