在網路上看到很多文章說「一定要加 CI」,所以我也跟著在這個 Blog 加了 GitHub Actions。
加完之後我想知道它實際上能做什麼,於是做了一個實驗:故意在 code 裡寫一個 TypeScript 型別錯誤,然後 push 上去,看看 CI 和 Vercel 各自有什麼反應。
結果出乎我意料。
實驗:故意寫一個型別錯誤
在 lib/utils.ts 加了這一行:
const _ciFailureDemo: number = "這是故意的 type error";
// ^^^^^^^^^^^^^^^^^^^^^^
// string 賦值給 number,明顯錯誤然後 push 上去,觀察兩邊的反應。
GitHub Actions CI 的反應
CI 在 TypeScript type check 那個 step 停下來,後面的 build 沒有繼續跑:

有抓到,行為符合預期。
Vercel 的反應
Vercel 的 build 也失敗了,build log 裡顯示:
Failed to compile.
./lib/utils.ts:9:7
Type error: Type 'string' is not assignable to type 'number'.
7 |
8 | // TODO: 這行是為了展示 CI 失敗而故意加的
> 9 | const _ciFailureDemo: number = "這是故意的 type error";
| ^
10 |
Next.js build worker exited with code: 1 and signal: null
Error: Command "npm run build" exited with 1

也有抓到,錯誤訊息一樣清楚,顯示了完整的程式碼行號。
所以 Vercel 到底能做什麼?
這個實驗讓我重新認識 Vercel 的 build pipeline。
Vercel 跑的是完整的 next build,而 next build 本身內含 TypeScript 型別檢查。所以 Vercel 不只是確認「能不能打包」,它也會在型別有錯誤的時候擋下來。
但 Vercel 有兩件事不會做:
- 不跑 ESLint — Next.js 16 正式移除了 build-time linting,
next lint指令也一併被移除,改成直接使用 ESLint CLI。所以npm run build不包含 lint - 不跑自動化測試 — 就算你有寫 unit test,Vercel 也不會幫你執行
那 CI 真正比 Vercel 多做了什麼?
把兩者放在一起比較:
| Vercel build | GitHub Actions CI | |
|---|---|---|
| TypeScript 型別檢查 | ✅(next build 內建) | ✅(tsc --noEmit) |
| ESLint | ❌ | ✅ |
| 自動化測試 | ❌ | ✅(需要自己設定) |
| build log | ✅ 清楚 | ✅ 清楚 |
| 擋住 PR merge | ❌ | ✅(搭配 branch protection) |
對個人專案而言,差距主要在 ESLint 和測試這兩件事。
什麼時候才真正需要 CI?
誠實地說,這幾個場景才是 CI 真正發揮作用的地方:
1. 有自動化測試的專案
如果專案裡有 unit test 或 E2E test,CI 可以在每次 push 時自動跑測試。Vercel 不會幫你跑測試,這是兩者最大的差距。
2. 多人協作、需要 PR workflow
搭配 GitHub 的 branch protection rules,設定「PR 要 CI 全過才能 merge」,這樣任何人的 code 進入 main 之前都必須通過檢查。Vercel 沒辦法做這件事。
3. 想要 ESLint 自動擋關
ESLint 錯誤不會讓 Vercel build 失敗,只有 CI 能在 push 時自動檢查。不過對個人專案而言,本地跑一次 npm run lint 也能達到同樣效果。
我的選擇
我保留了 CI,但調整了對它的期待:它不是 Vercel 的替代品,也不是「必要的品質保障」,而是一個自動化的確認流程,讓每次 push 都留下一個明確的紀錄。
實際的設定很精簡:
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
jobs:
check:
name: Lint & Type Check
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm
- name: Install dependencies
run: npm ci
- name: ESLint
run: npm run lint
- name: TypeScript type check
run: npm run type-check
- name: Build
run: npm run buildpackage.json 需要加一個 type-check script:
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint",
"type-check": "tsc --noEmit"
}全部 step 通過之後,GitHub Actions tab 會是這個樣子:

補充:npm ci vs npm install
workflow 裡用的是 npm ci 而不是 npm install:
npm install | npm ci | |
|---|---|---|
| 用途 | 本地開發 | CI 環境 |
package-lock.json | 可能更新 | 嚴格依照 lock 檔案 |
| 確定性 | 不保證 | 保證每次相同結果 |
CI 需要「每次在相同環境跑出相同結果」,所以用 npm ci。
結論
Vercel 的 build pipeline 比很多人以為的更完整,它並不是只確認「能打包」而已,TypeScript 型別錯誤它一樣會抓到。
CI 真正額外的價值在於:ESLint 自動檢查、自動跑測試,以及搭配 PR workflow 之後能在 code 進 main 之前擋關。
對沒有自動化測試、也沒有 PR workflow 的個人專案來說,Vercel 的 build 已經夠用了。但如果之後加了測試,CI 就會是真正不可少的一環。