從 Vercel 到 Cloudflare:兩天搬家之旅的真實記錄


記錄一次充滿挑戰的平台遷移經歷:花了兩天時間從 Vercel 搬家到 Cloudflare Pages,中途幾乎放棄,但最終成功突破所有技術難關。分享遷移過程中遇到的問題、解決方案,以及兩個平台的深度比較。
從一個模糊的想法,到一個多月的縝密規劃,再到兩天幾乎不眠不休的實作——我的部落格遷移之旅終於畫下句點。這不僅是將網站從 Vercel 搬到 Cloudflare 的簡單過程,更是一場涉及 60 多篇 MDX 文章(相當於 60 多個小型應用程式)的深度技術重構。如果你也正在管理一個內容密集的 MDX 網站,或對現代 Web 開發的真實挑戰感興趣,那麼這段充滿陷阱、失敗與最終勝利的經歷,或許能給你一些啟發。
技術背景:複雜度分析
在深入遷移過程之前,讓我先介紹這個專案的技術複雜度。這不是一個簡單的靜態網站,而是一個高度客製化的內容平台:
技術架構概覽
- Next.js 15 + App Router (最新特性)
- MDX 生態系統:60+ 篇文章,每篇都包含動態 React 組件
- 智慧組件系統:每篇文章都有獨立的
/components
目錄 - TypeScript 嚴格模式:完整的類型安全
- Tailwind CSS 4:包含自定義插件和主題
- Chart.js + React Flow:豐富的互動視覺化組件
挑戰指標
- 檔案數量:2000+ 個檔案
- 依賴包:120+ npm 套件
- 組件數量:300+ 個 React 組件
- 構建時間:Vercel 上需要 8-12 分鐘
- 部署大小:壓縮後 45MB+
為什麼要離開 Vercel?深度分析
成本結構問題
Vercel 的計價模式對於內容豐富的 MDX 部落格來說,存在幾個隱含成本:
Serverless Functions 執行成本
// 每次頁面請求的成本計算
const costPerRequest = {
functionInvocations: 0.0000002, // $0.20 per 1M invocations
gbSecond: 0.0000166667, // $0.00001667 per GB-second
duration: 1.2, // MDX 渲染平均 1.2 秒
memoryUsage: 1024 // 1GB 記憶體配置
};
// 月流量 10,000 頁面瀏覽的估算成本
const monthlyCost = (costPerRequest.functionInvocations * 10000) +
(costPerRequest.gbSecond * 1.2 * 10000);
頻寬限制
- Hobby 方案:100GB/月
- Pro 方案:1TB/月 後額外收費
- 圖片和媒體檔案佔用大量頻寬
建置時間限制
- Hobby:10 分鐘建置時間上限
- 我的專案經常超過這個限制,導致部署失敗
性能瓶頸深度剖析
冷啟動影響分析
# 使用 WebPageTest 測試結果
Location: Taiwan
First Visit: 2.1s (冷啟動)
Repeat Visit: 0.8s (熱啟動)
LCP: 1.6s
CLS: 0.02
伺服器渲染延遲 MDX 組件的 SSR 處理增加了顯著的響應時間,特別是包含複雜圖表的文章。
控制權限制
客製化建置流程
- 無法自定義 Webpack 配置的某些底層設定
- Edge Runtime 限制了某些 Node.js 功能
- 難以整合第三方建置工具
監控和除錯
- 有限的日誌保留期
- 無法深度分析建置過程
- 依賴 Vercel 的錯誤報告系統
Cloudflare Pages 的技術優勢
全域網路架構
Anycast 技術優勢
# 效能測試對比 (ping 測試)
Target: Taiwan Taipei
Vercel: 45-60ms (經過美國西部)
Cloudflare: 8-15ms (直接連接台北節點)
CDN 分佈
- 330+ 全球數據中心
- 台灣有台北、高雄兩個 PoP
- 亞太地區覆蓋率業界領先
靜態託管優勢
真正的靜態部署
// Next.js Static Export 配置
const nextConfig = {
output: 'export',
trailingSlash: true,
images: {
unoptimized: true
}
};
消除了 Serverless Functions 的所有延遲和成本問題。
遷移之路:從滿懷希望到瀕臨放棄
階段一:初始設定與配置
第一步:用 create-cloudflare
初始化專案
旅程的起點是 Cloudflare 官方的 CLI 工具。但在執行之前,我花了相當時間研究最佳的配置選項:
# 先檢查可用的模板選項
npm create cloudflare@latest --help
# 最終選擇的指令
npm create cloudflare@latest -- opennextjs-mdx-blog \
--framework=next \
--platform=pages \
--no-git \
--no-deploy
關鍵配置決策分析:
--framework=next
:確保使用 Next.js 專用模板,包含必要的 Cloudflare 整合--platform=pages
:選擇 Pages 而非 Workers,基於後面會詳述的原因--no-git
:保持版本控制的完全控制權--no-deploy
:避免未配置完整的早期部署失敗
CLI 互動過程記錄:
✅ Create application
⠙ Retrieving list of available templates
⠹ Cloning template https://github.com/cloudflare/next-on-pages
⠸ Installing dependencies
⠼ Setting up project
✅ Application created successfully!
? Do you want to use git for version control? › No
? Do you want to deploy your application? › No
╭─────────────────────────────────────────────────╮
│ Your new Cloudflare application is ready! │
│ │
│ Next steps: │
│ 1. Change to the application directory │
│ 2. Install dependencies: npm install │
│ 3. Preview locally: npm run dev │
│ 4. Deploy: npm run deploy │
╰─────────────────────────────────────────────────╯
第二步:深度配置分析
生成的專案包含了關鍵的 Cloudflare 整合檔案:
wrangler.toml
配置解析
name = "opennextjs-mdx-blog"
compatibility_date = "2024-05-30"
compatibility_flags = ["nodejs_compat"]
[env.preview]
compatibility_date = "2024-05-30"
[env.production]
compatibility_date = "2024-05-30"
[[pages_build_env.vars]]
NODE_VERSION = "18"
next.config.js
Cloudflare 優化
/** @type {import('next').NextConfig} */
const nextConfig = {
// Cloudflare Pages 特定配置
output: 'export',
trailingSlash: true,
skipTrailingSlashRedirect: true,
// 圖片優化設定
images: {
unoptimized: true,
loader: 'custom',
path: '',
},
// 實驗性功能
experimental: {
runtime: 'experimental-edge',
}
};
module.exports = nextConfig;
第三步:版本控制整合策略
我採用了一個更為謹慎的 Git 設定流程:
# 1. 初始化本地倉儲
git init
# 2. 設定基本配置
git config user.name "Your Name"
git config user.email "[email protected]"
# 3. 建立 .gitignore 檢查
cat .gitignore | head -20
# 4. 連接遠端倉儲
git remote add origin https://github.com/ianTPE/opennextjs-mdx-blog.git
# 5. 檢查遠端狀態
git remote -v
# 6. 設定主分支
git branch -M main
# 7. 分階段提交
git add package.json package-lock.json
git commit -m "Add package configuration"
git add next.config.js wrangler.toml
git commit -m "Add Cloudflare configuration"
git add .
git commit -m "Initial project structure from create-cloudflare"
# 8. 推送到遠端
git push -u origin main
這種分階段提交的方式讓我能更好地追蹤每個配置檔案的變更。
階段二:部署平台決策災難
Workers 的誘惑與陷阱
最初,我被 Cloudflare Workers 的技術描述所深深吸引:
Workers 的理論優勢:
- 真正的 Edge Computing,全球分佈式執行
- V8 引擎直接執行,啟動時間少於1ms
- 支援 WebAssembly,性能極限更高
- 完整的 JavaScript/TypeScript 運行時
然而,現實遠比理論殘酷。
Workers 部署失敗深度分析
第一次嘗試:直接部署
# 初始部署指令
npx wrangler pages project create opennextjs-mdx-blog
npx wrangler pages deploy .next/
# 錯誤日誌
Error: Could not resolve "./app/page.tsx"
Error: Dynamic imports are not supported in Workers
Error: Node.js APIs are not available in Workers runtime
第二次嘗試:使用 @cloudflare/next-on-pages
# 安裝必要套件
npm install --save-dev @cloudflare/next-on-pages
# 修改 package.json
{
"scripts": {
"pages:build": "@cloudflare/next-on-pages",
"pages:deploy": "wrangler pages deploy .vercel/output/static"
}
}
# 執行建置
npm run pages:build
建置過程分析(20分鐘的痛苦):
[00:00] 🔍 Analyzing Next.js application...
[00:45] 📦 Bundling 127 pages...
[05:30] ⚠️ Warning: Large bundle size detected
[10:15] 🔧 Processing MDX components...
[15:40] ❌ Error: Memory limit exceeded
[20:00] 💥 Build failed: Worker size limit exceeded
關鍵失敗原因:
- Worker 大小限制:10MB 壓縮後限制,我的 MDX 部落格超過 45MB
- 記憶體限制:128MB 運行時記憶體,MDX 渲染需要更多
- Node.js 相容性:許多 MDX 相關套件使用 Node.js APIs
- 動態導入限制:我的智慧組件系統大量使用動態導入
Pages 的救贖之路
轉向 Pages 的決定 在 Workers 多次失敗後,我幾乎想要放棄整個遷移計劃。但是一篇 Reddit 討論改變了我的想法:
"For content-heavy sites with complex builds, Pages is often the better choice. Workers are great for lightweight APIs, but Pages excels at static hosting with edge optimization."
Pages 部署成功記錄:
# 第一次 Pages 部署
npx wrangler pages project create opennextjs-mdx-blog --type=upload
# 修改 next.config.js 為靜態導出
const nextConfig = {
output: 'export',
trailingSlash: true,
images: { unoptimized: true }
};
# 建置和部署
npm run build
npx wrangler pages deploy out/
# 結果
✅ Success! Deployed to https://opennextjs-mdx-blog.pages.dev
📊 Build time: 3m 42s
📦 Deploy size: 42.3 MB
🌍 Global distribution: Active
Pages vs Workers 對比分析:
特性 | Workers | Pages |
---|---|---|
大小限制 | 10MB 壓縮 | 500MB 未壓縮 |
建置時間 | 15-25 分鐘 | 3-5 分鐘 |
Node.js 支援 | 有限 | 完整 |
靜態資源 | 複雜 | 原生支援 |
除錯難度 | 高 | 低 |
適用場景 | 輕量 API | 內容豐富網站 |
技術決策總結: 對於 MDX 部落格這類內容密集型應用,Cloudflare Pages 是唯一可行的選擇。Workers 雖然技術上更先進,但限制太多,不適合複雜的靜態網站生成。
階段三:內容遷移與 AI 協同作戰
大規模內容遷移策略
網站基礎架構就緒後,最艱難的任務來了:將 60+ 篇 MDX 文章從舊系統遷移到新架構。這不是簡單的複製貼上,而是一次深度的結構重組。
遷移複雜度分析:
- 目錄名稱規範化:使用 Gemini + Python 腳本整理目錄名和 metadata slug
- 依賴生態系統差異:新專案的依賴版本與舊專案不同,組件相容性未知
- MDX + Components 相容性問題:60+ 篇文章的組件能否在新環境正常運作
- 建置系統差異:從 Vercel 的 SSR 到 Cloudflare Pages 的靜態生成
AI 協同工作流程
我設計了一個系統化的 AI 協作流程:
第一階段:目錄規範化與直接複製
# 使用 Gemini + Python 腳本規範化目錄名
# 原本目錄名不規範,需要統一命名
python organize_posts.py
# 輸出:規範化的目錄名 + 正確的 metadata slug
# 直接複製整個 content/posts 目錄
cp -r old-project/content/posts new-project/content/posts
實際遷移挑戰:
原有結構(已經是標準結構):
content/posts/不規範的目錄名/
├── content.mdx # 原本就有
├── metadata.ts # 原本就有
└── components/ # 原本就有
├── index.ts
└── CustomChart.tsx
遷移後的真正問題:
// 舊專案的組件可能使用舊版依賴
import { Chart } from 'chart.js/v3'; // 舊版本
import { motion } from 'framer-motion/v6';
// 新專案的依賴版本
import { Chart } from 'chart.js/v4'; // 新版本,API 不同
import { motion } from 'framer-motion/v11';
第二階段:依賴相容性修復
這是最核心的挑戰:60+ 篇文章的組件需要適配新的依賴生態系統:
- 依賴版本衝突:Chart.js v3→v4、Framer Motion v6→v11、React 18→19
- API 破壞性變更:許多組件的 props 和方法簽名改變
- TypeScript 類型更新:新版本的類型定義更嚴格
- 建置系統差異:靜態導出對某些動態功能的限制
致命 Bug:Loading.tsx 災難
事件時間線:
- Day 1 16:30 - Augment 執行大批量組件重構
- Day 1 17:15 - 網站突然完全無法載入
- Day 1 17:16-21:30 - 4小時15分鐘的除錯噩夢
症狀分析:
# 瀏覽器行為
- 頁面顯示空白
- 載入指示器永遠旋轉
- Network tab 顯示請求正常
- Console 完全沒有錯誤
# 本地開發伺服器
npm run dev
> Local: http://localhost:3000
> Network: ready in 2.3s
# 但瀏覽器訪問仍然空白
# 建置過程
npm run build
> ✓ Compiled successfully
> ✓ Collecting page data
> ✓ Generating static pages (127/127)
# 建置成功,但執行失敗
除錯過程記錄:
階段一:檢查常見問題(30分鐘)
- JavaScript 語法錯誤 ❌
- CSS 樣式衝突 ❌
- 環境變數問題 ❌
階段二:組件樹分析(45分鐘)
- 逐一檢查新增的組件
- 測試組件單獨渲染
- 檢查 import/export 語法
階段三:Next.js 特性排查(90分鐘)
- App Router 配置
- 靜態生成設定
- 中介軟體檢查
階段四:與 Claude Code 協作除錯(120分鐘)
最終,透過系統性的檔案比較,Claude Code 發現了問題:
問題根源:
// app/loading.tsx - 被 Augment 破壞的版本
export default function Loading() {
// Augment 在重構時意外添加了一個無限迴圈
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
// 這個 effect 沒有依賴項,導致無限重渲染
setIsLoading(true);
}); // 缺少依賴數組!
return isLoading ? <div>Loading...</div> : null;
}
修復方案:
// app/loading.tsx - 修復版本
export default function Loading() {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
<span className="ml-2 text-muted-foreground">Loading...</span>
</div>
);
}
技術分析:
這個 bug 的危險性在於它破壞了 Next.js App Router 的 Suspense 邊界。當頁面進入 loading 狀態時,Loading
組件的無限重渲染導致 React 無法完成渲染週期,整個應用被「凍結」在 loading 狀態。
AI 協作的經驗教訓
AI 的優勢:
- 快速批量處理重複性任務
- 模式識別和代碼生成效率極高
- 能夠處理複雜的重構邏輯
AI 的局限性:
- 對 React Hooks 規則的理解不夠深入
- 缺乏對應用程式整體狀態的感知
- 難以預測副作用和邊界情況
協作最佳實踐:
- 分批處理:避免一次性大規模變更
- 即時測試:每個批次後立即驗證功能
- 版本控制:細粒度的 commit 便於問題定位
- 人工審查:關鍵檔案變更需要人工檢查
階段四:性能優化與最終調整
效能監控與分析
遷移完成後,我進行了全面的效能測試,結果讓人驚喜:
載入速度對比:
# Lighthouse 測試結果 (台灣測試節點)
Vercel Cloudflare Pages
Performance 78 96
Accessibility 92 92
Best Practices 83 91
SEO 89 94
LCP 1.6s 0.8s
FID 12ms 8ms
CLS 0.02 0.01
全球效能測試:
# WebPageTest 多地點測試
Location Vercel Cloudflare
台北 (Taipei) 1.2s 0.7s
東京 (Tokyo) 0.9s 0.6s
新加坡 (SG) 1.1s 0.5s
雪梨 (Sydney) 1.4s 0.8s
倫敦 (London) 2.1s 1.2s
紐約 (NYC) 1.8s 1.1s
意外發現的優化機會
圖片載入優化 從 Vercel 遷移到 Cloudflare 後,我發現了一個意外的效能瓶頸:
// 問題:Next.js Image 組件在靜態導出中失效
<Image
src="/images/chart.png"
alt="Performance Chart"
width={800}
height={400}
priority
/>
// 解決方案:客製化圖片組件
const OptimizedImage = ({ src, alt, ...props }) => {
return (
<img
src={src}
alt={alt}
loading="lazy"
decoding="async"
{...props}
style={{
maxWidth: '100%',
height: 'auto',
aspectRatio: 'auto'
}}
/>
);
};
Bundle 大小優化
# 分析工具
npm install --save-dev @next/bundle-analyzer
# package.json
{
"scripts": {
"analyze": "ANALYZE=true npm run build"
}
}
# 優化結果
Original bundle: 2.3MB
Optimized bundle: 1.8MB (-22%)
Main performance gains:
- Tree-shake Chart.js: -300KB
- Remove unused Tailwind: -150KB
- Optimize MDX runtime: -200KB
深度技術反思與收穫
架構設計哲學的轉變
這次遷移不僅是技術棧的改變,更是架構思維的深度轉變:
從動態到靜態的思維轉換
Vercel 時期的思維:
- 依賴 Serverless Functions 處理動態需求
- 即時渲染 MDX 內容
- 伺服器端資料獲取
Cloudflare Pages 的新思維:
- 建置時間預渲染所有內容
- 完全靜態化的資產分發
- 邊緣快取最大化利用
// 思維轉換範例:搜尋功能實現
// 舊方案(動態)
export async function GET(request) {
const query = request.url.searchParams.get('q');
const results = await searchPosts(query);
return Response.json(results);
}
// 新方案(靜態)
// 建置時生成搜尋索引
const searchIndex = generateSearchIndex(allPosts);
// 客戶端 JavaScript 搜尋
function searchPosts(query) {
return fuse.search(query, searchIndex);
}
成本意識的養成
這次遷移讓我深刻理解了「技術負債」的真實成本:
可見成本 vs 隱藏成本:
-
Vercel 可見成本:每月 $20 Pro 方案
-
Vercel 隱藏成本:
- 超時建置的重試成本
- 功能限制導致的開發時間成本
- 擴展性限制的機會成本
-
Cloudflare 總成本:$0(真正的免費)
-
學習成本:2 天深度學習 + 設定
技術債務的清理收穫
強制性重構帶來的程式碼品質提升
遷移過程強制我重新審視每一行程式碼:
依賴清理:
# 遷移前:120+ 依賴包
# 遷移後:85 個核心依賴(-29%)
# 移除的冗餘依賴
- @vercel/analytics (替換為 Cloudflare Web Analytics)
- next-seo (重構為自訂 metadata)
- 多個重複的 UI 元件庫
型別安全提升:
// 舊的鬆散型別定義
interface Post {
title: string;
date: string;
content?: string;
}
// 新的嚴格型別定義
interface PostMeta {
readonly title: string;
readonly publishedAt: string;
readonly updatedAt?: string;
readonly tags: readonly string[];
readonly readingTime: number;
readonly featured: boolean;
}
工程化流程的建立
自動化測試流程:
# .github/workflows/ci.yml
name: CI/CD Pipeline
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '18'
- name: Install dependencies
run: npm ci
- name: Lint code
run: npm run lint
- name: Type check
run: npm run type-check
- name: Build site
run: npm run build
- name: Deploy to Cloudflare
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
projectName: opennextjs-mdx-blog
directory: out
重要經驗教訓
工具選擇的戰略重要性
核心原則:適合比先進更重要
- Workers 雖然技術上更先進,但 Pages 更適合內容型網站
- 新技術的學習成本需要納入決策考量
- 生態系統的成熟度比單一功能的強大更重要
AI 協作的邊界與最佳實踐
有效協作模式:
- AI 負責:重複性任務、模式識別、代碼生成
- 人類負責:架構決策、品質控制、問題定位
- 協同負責:複雜重構、效能優化、故障排除
風險控制策略:
- 小批次變更,每次驗證
- 關鍵檔案人工 review
- 自動化測試覆蓋核心功能
- Git 細粒度提交便於回滾
遷移項目的項目管理
時間估算教訓:
- 預期時間:1 週
- 實際時間:3 週
- 主要延誤原因:
- 低估了內容遷移的複雜度(40%)
- 未預期 Workers 配置失敗(30%)
- 除錯時間超出預期(30%)
改進建議:
- 為複雜度預留 2-3 倍的時間緩衝
- 分階段驗證,避免大爆炸式部署
- 準備 B 計劃(Pages 作為 Workers 的備案)
成果與影響
量化收益
效能提升:
- 平均載入速度提升 40%
- Lighthouse 分數從 78 提升到 96
- 全球用戶體驗明顯改善
維護成本降低:
- 每月託管費用:$20 → $0
- 建置時間:8-12分鐘 → 3-4分鐘
- 部署失敗率:15% → 少於1%
開發體驗改善:
- 本地開發環境更接近生產環境
- 除錯工具更豐富
- 自動化流程更可靠
技能成長
技術技能:
- 深度理解靜態網站生成原理
- 掌握 Cloudflare 生態系統
- 提升 MDX 架構設計能力
工程技能:
- 大型項目遷移的項目管理
- AI 協作的流程設計
- 風險評估與應急預案制定
希望這段詳細的遷移經歷,能為同樣在進行技術棧遷移或考慮從 Vercel 轉向 Cloudflare 的開發者提供實用的參考。技術選擇從來不是非黑即白的,關鍵在於找到最適合自己項目特性和發展階段的解決方案。
📚 相關資源
官方文檔
實用工具
感謝 Augment Code 和 Claude 在部落格建設過程中的協作支持! 🤖✨
Thanks for reading!
Found this article helpful? Share it with others or explore more content.