用 Astro 建一個技術部落格:從零到部署 Cloudflare Pages
🎯 前言
這個 blog 本身就是最好的教材。
與其介紹一個假想的案例,不如直接說明「這個你正在閱讀的網站」是怎麼建起來的。
這篇文章記錄用 Astro 建立這個技術 blog 的完整過程,包含我選擇 Astro 的理由、Content Collections 的實際使用心得、繁體中文的設定細節,以及部署到 Cloudflare Pages 的步驟。
🤔 為什麼選 Astro?
建技術 blog 的選項很多:Hugo、Jekyll、Next.js、Nuxt.js、Ghost…。我最後選了 Astro,主要有幾個理由。
1. 以內容為中心的設計
Astro 的核心設計理念是「內容優先」。它的 Content Collections 功能讓你用 Markdown 寫文章,然後 Astro 幫你處理型別安全、front matter 驗證、路由生成。
這對技術 blog 來說非常合適:
- 文章就是
.md或.mdx檔案,存在 git 裡 - 不需要資料庫,也不需要 CMS 後台
- 版本控制自然整合
2. 預設 Zero JS
Astro 預設不載入任何 JavaScript 到瀏覽器。一個靜態的技術 blog 不需要前端框架,生成的 HTML 直接就是最終產物。頁面載入速度非常快,也對 SEO 友善。
3. MDX 支援
MDX 讓你在 Markdown 中使用 React 元件。雖然這個 blog 目前沒有大量使用 MDX 的複雜功能,但保留了這個可能性。
4. Cloudflare Pages 相容性好
Astro 官方支援部署到 Cloudflare Pages,設定簡單,不需要複雜的 CI/CD 設定。
🏗️ 專案架構
這個 blog 的檔案結構:
src/
├── assets/ ← 靜態資源(圖片等)
│ └── blog-placeholder-1.jpg
├── components/ ← 可重用的 Astro 元件
│ ├── BaseHead.astro
│ ├── Header.astro
│ ├── Footer.astro
│ └── FormattedDate.astro
├── content/
│ ├── blog/ ← 所有文章的 Markdown 檔案
│ │ ├── first-post.md
│ │ └── ...
│ └── config.ts ← Content Collections 的 schema 定義
├── layouts/ ← 頁面佈局
│ ├── BlogPost.astro
│ └── ...
├── pages/
│ ├── index.astro ← 首頁
│ ├── blog/
│ │ ├── index.astro ← 文章列表
│ │ └── [...slug].astro ← 文章詳情頁
│ └── rss.xml.js ← RSS feed
└── consts.ts ← 全域常數(站名等)
📝 Content Collections 的使用心得
Content Collections 是 Astro 最好用的功能之一。
Schema 定義
在 src/content/config.ts 中定義文章的 front matter schema:
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: ({ image }) => z.object({
title: z.string(),
description: z.string(),
pubDate: z.coerce.date(),
updatedDate: z.coerce.date().optional(),
heroImage: image().optional(),
}),
});
export const collections = { blog };
有了這個 schema,astro check 會驗證所有文章的 front matter。
如果你忘記寫 description 欄位,TypeScript 會直接報錯,不用等到 build 才發現。
文章列表頁的寫法
在 src/pages/blog/index.astro 中,取得所有文章並排序:
---
import { getCollection } from 'astro:content';
const posts = (await getCollection('blog'))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
---
<ul>
{posts.map(post => (
<li>
<a href={`/blog/${post.id}/`}>{post.data.title}</a>
<time datetime={post.data.pubDate.toISOString()}>
{post.data.pubDate.toLocaleDateString('zh-TW')}
</time>
</li>
))}
</ul>
文章詳情頁的 getStaticPaths
---
import { getCollection } from 'astro:content';
import { render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.id },
props: post,
}));
}
const post = Astro.props;
const { Content } = await render(post);
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>
關鍵:post.id 就是檔案名稱(去掉副檔名),直接用它當 URL slug。
my-article.md → /blog/my-article/
🌏 繁體中文 i18n 設定
這個 blog 的文章全部用繁體中文(zh-TW)撰寫。Astro 有 i18n 支援,設定方式:
astro.config.mjs 設定
import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://your-blog.pages.dev',
i18n: {
defaultLocale: 'zh-TW',
locales: ['zh-TW'],
},
// ...
});
HTML lang 屬性
在 BaseHead.astro 中確保 <html lang="zh-TW">:
---
// BaseHead.astro
---
<html lang="zh-TW">
<head>
<meta charset="UTF-8" />
<!-- ... -->
</head>
日期格式化
顯示日期時使用 zh-TW locale:
---
// FormattedDate.astro
const { date } = Astro.props;
const formatted = date.toLocaleDateString('zh-TW', {
year: 'numeric',
month: 'long',
day: 'numeric',
});
---
<time datetime={date.toISOString()}>{formatted}</time>
這樣日期會顯示為「2026年2月11日」的格式。
RSS Feed 的語言設定
在 src/pages/rss.xml.js 中設定語言:
import rss from '@astrojs/rss';
import { SITE_TITLE, SITE_DESCRIPTION } from '../consts';
export async function GET(context) {
return rss({
title: SITE_TITLE,
description: SITE_DESCRIPTION,
site: context.site,
// ...
customData: '<language>zh-TW</language>',
});
}
🌐 部署到 Cloudflare Pages
方式一:透過 Git 自動部署(推薦)
這是最簡單的方式:
- 把你的 Astro 專案推送到 GitHub
- 登入 Cloudflare Dashboard
- 進入 Workers & Pages → Create → Pages → Connect to Git
- 選擇你的 GitHub repo
- 設定 Build 設定:
- Framework preset:Astro
- Build command:
npm run build - Build output directory:
dist
- 點擊 Save and Deploy
之後每次你 git push,Cloudflare Pages 會自動 build 並部署。
方式二:手動部署(用 Wrangler CLI)
# 安裝 Wrangler
npm install -g wrangler
# 登入 Cloudflare
wrangler login
# Build
npm run build
# 部署
wrangler pages deploy dist --project-name=your-blog-name
自訂網域
部署成功後,Cloudflare 會給你一個 your-project.pages.dev 的網域。
如果你有自己的網域,可以在 Cloudflare Pages 的設定中綁定。
注意 site URL 設定
部署到 Cloudflare Pages 後,記得更新 astro.config.mjs 中的 site 設定:
export default defineConfig({
site: 'https://your-actual-domain.com', // 改成你的實際網域
// ...
});
這個設定會影響 RSS feed 和 sitemap 中的 URL 生成。
🔍 Sitemap 設定
Astro 有官方的 sitemap 套件,安裝和設定很簡單:
npx astro add sitemap
這個指令會自動:
- 安裝
@astrojs/sitemap - 更新
astro.config.mjs加入 sitemap integration
更新後的 astro.config.mjs:
import { defineConfig } from 'astro/config';
import sitemap from '@astrojs/sitemap';
import mdx from '@astrojs/mdx';
export default defineConfig({
site: 'https://your-domain.com',
integrations: [mdx(), sitemap()],
// ...
});
Build 完成後,dist/sitemap-index.xml 和 dist/sitemap-0.xml 會自動生成。
💡 使用心得:MDX 的限制
雖然 Astro 支援 MDX,但我在實際使用中發現幾個限制:
1. MDX 的 import 語法
在 .mdx 檔案中,你可以 import 元件:
import MyComponent from '../../components/MyComponent.astro';
<MyComponent />
但相對路徑容易搞錯,尤其是目錄結構改變時。
2. 純 Markdown 往往更簡單
對於大多數技術文章,純 Markdown 加上 HTML table 和 code block 就夠了,不需要 MDX。
只有需要互動元件(例如可以展開/收合的細節)或複雜排版時,才值得用 MDX。
🎉 結語
用 Astro 建技術 blog 的整體體驗非常好。
最喜歡的部分:
- Content Collections 的型別安全,減少 front matter 的人為錯誤
- Markdown 寫文章的流暢感
- 部署到 Cloudflare Pages 零設定,git push 就更新
需要注意的地方:
siteURL 一定要設正確,影響 RSS 和 sitemap- MDX 雖然強大,但不必要的情況下純 Markdown 更簡單
- 繁體中文的日期格式要手動指定 locale,預設是英文
如果你也想建一個技術 blog,Astro + Cloudflare Pages 這個組合是一個很好的起點。 免費額度對個人 blog 來說完全夠用,部署又簡單。
這個 blog 本身的原始碼放在 GitHub 上,歡迎參考。
📎 相關文章: