Ian Chou's Blog

回歸 Web 1.0 的去中心化精神:我的「聯邦式」部落格架構 (The Blog Federation)

在軟體架構的世界裡,我們常說「合久必分,分久必合」。在經歷了多年使用單一 CMS 或大型單體框架(Monolith)來管理所有內容後,我決定在 2025 年做一個大膽的實驗:將部落格「聯邦化」

這個架構轉變的核心,不在於追求最新的技術,而在於解決最古老的痛點:耦合(Coupling)與維護成本

為什麼選擇「聯邦式」架構?

過去我們習慣把所有文章——無論是 2015 年的舊文還是 2025 年的新作——都塞進同一個專案、同一個資料庫。這導致了幾個問題:

  1. 技術債累積:想升級框架版本時,必須拖著十年的舊內容一起跑。
  2. 技術鎖定:如果我想用最新的 Astro 寫新文章,但舊站是用 Next.js 寫的,遷移成本極高。

我的解決方案是:基於子網域的去中心化架構

每個子站點都是獨立的「小宇宙」,可以使用當下最適合的技術棧。但隨之而來的問題是:如何讓使用者在一個入口網站(Main Site)看到所有內容?

這就是回歸 Web 1.0 精神的時刻:使用最簡單的標準協議 —— RSS

架構核心:主站作為「聚合器 (Aggregator)」

在這個架構中,主站(Landing Page)的角色不再是「資料庫管理者」,而是一個「訂閱者」。它不擁有內容,它只是負責聚合(Aggregate)內容。

其實作邏輯非常優雅且低成本,完全不需要複雜的 API 對接或跨站資料庫連線,僅依賴以下三個步驟:

1. 協議層:每個子站必須吐出 rss.xml

這是整個聯邦唯一的「硬性規定」。無論子站是用 Astro、Next.js 還是 Eleventy,只要它能生成一個標準的 RSS Feed,它就能加入這個聯邦。

這實現了完美的解耦:主站根本不在乎子站是用什麼寫的,它只認 XML。

2. 應用層:主站的構建 (Build Time Aggregation)

為了保持極致的效能與 SEO,我選擇 Astro 作為主要入口網站的框架。Astro 強大的 Data Fetching 能力,讓我可以在「建置時間 (Build Time)」完成所有 RSS 的抓取與混合。

這意味著,當使用者訪問主站時,他們看到的是一份已經排序好的、純靜態的 HTML,不需要在瀏覽器端等待 JavaScript 去抓取 XML,速度飛快。

核心程式碼邏輯如下(使用 rss-parser):

import Parser from "rss-parser";

// 定義聯邦子站點
const SUBDOMAINS = [
	{
		label: "Next.js 2025 (A)",
		url: "https://n25a.citrine.top/rss.xml",
		tag: "n25a",
	},
	{
		label: "Astro 2024 (C)",
		url: "https://a24c.citrine.top/rss.xml",
		tag: "a24c",
	},
];

const parser = new Parser();

// 並發抓取所有站點的 RSS
const results = await Promise.allSettled(
	SUBDOMAINS.map(async (site) => {
		try {
			const feed = await parser.parseURL(site.url);
			return feed.items.map((item) => ({
				...item,
				sourceLabel: site.label, // 標記來源
			}));
		} catch (error) {
			return []; // 容錯設計:某個子站掛了不影響主站存活
		}
	})
);

// 合併並按時間倒序排列
let allPosts = [];
results.forEach((result) => {
	if (result.status === "fulfilled") allPosts.push(...result.value);
});
allPosts.sort((a, b) => new Date(b.pubDate) - new Date(a.pubDate));

3. 自動化層:Deploy Hooks 閉環

因為主站是靜態生成的,如果子站發了新文章,主站怎麼知道要更新?

這時我們利用 Cloudflare Pages (或 Vercel) 的 Deploy Hooks

  1. 在主站設定一個 Hook URL(例如:api.cloudflare.com/.../trigger)。
  2. 在子站的部署流程(CI/CD)完成後,自動發送一個 curl 請求去觸發這個 URL。

這樣就形成了一個自動化的閉環:
子站發布 -> 觸發 Hook -> 主站重新建置 (Rebuild) -> 抓取最新 RSS -> 首頁更新。

搜尋問題的解答

既然內容分散在不同子網域,搜尋怎麼辦?如果要自己做跨站索引,會大幅增加維護成本。

既然我們走的是 Web 1.0 風格,最好的解法就是 Google Programmable Search Engine (CSE)
設定搜尋範圍為 *.citrine.top,讓 Google 的爬蟲幫我們做全文檢索。這雖然犧牲了一點樣式客製化的自由度,但換來的是零維護成本與強大的搜尋能力。

結語

這個架構的最大優勢在於**「進可攻,退可守」**。

用最簡單的技術(HTTP + HTML + RSS)解決最複雜的問題,這就是我心中理想的現代化架構。