Vibe Coding 十四個常見地雷
沒有前端經驗、靠 AI 一路生出來的專案,壞掉的地方高度重複。這是清單,每一項都附上怎麼看出來、怎麼修。
這些不是理論上的風險,是實際會在上線後被用戶發現的東西。依「多久會炸」排序。
一、當天就會被發現
1. 金鑰被打包進前端
症狀:VITE_、PUBLIC_、NEXT_PUBLIC_ 開頭的環境變數裡放了 API key。
為什麼會發生:AI 想讓 fetch 在瀏覽器裡跑起來,最短路徑就是把 key 放到前端變數。
怎麼看出來:build 完之後在 dist/ 或 .svelte-kit/output/client/ 裡搜尋 key 的前八碼。搜得到就是外洩。
怎麼修:任何金鑰只能在伺服器端使用。前端呼叫你自己的 API route,由 route 帶著 key 去呼叫第三方。詳見 security-basics。
2. 錯誤訊息把技術細節噴給用戶
症狀:畫面上出現 TypeError: Cannot read properties of undefined 或一整段 SQL。
怎麼修:伺服器記完整錯誤,前端只顯示一句人話加一組追蹤碼。詳見 error-handling。
3. 手機版破版
症狀:手機上可以左右滑動,內容被切掉。
怎麼看出來:DevTools 開 375px 寬,看有沒有橫向捲軸。或在 console 跑:
[...document.querySelectorAll('*')].filter(el => el.scrollWidth > document.documentElement.clientWidth)
常見元凶:寫死寬度的表格、width: 100vw(沒算捲軸寬度)、超長不斷行的英數字串、負 margin。
怎麼修:body { overflow-x: clip } 只是止血。真正的修法是讓寬的東西在自己的容器裡捲:.table-scroll { overflow-x: auto }。
二、一週內會被發現
4. 元素互相蓋住(overlap)
sticky header 蓋住錨點標題、modal 被 header 蓋住、下拉選單被下一個區塊切掉。
三個修法,照順序試:
html { scroll-padding-top: 80px }解決錨點被 header 蓋住- 用
isolation: isolate建立 stacking context,不要一路加z-index: 9999 - 被切掉通常是父層有
overflow: hidden,把浮層 portal 到 body
5. 版面跳動(layout shift)
圖片載入後把文字往下推、字型換掉後標題變高、載入完 spinner 消失整頁位移。
- 每張
<img>都要有width和height(或aspect-ratio) - 字型用
font-display: swap並指定接近的 fallback - 骨架屏的尺寸要跟真實內容一樣,不要用一顆會轉的圈圈
6. 每次切頁都整頁重刷
症狀:點導覽列,整個頁面白一下,header 重新閃一次。
通常是用了 <a href> 觸發完整 document 載入,或在每個頁面各自 fetch 相同的資料。詳見 navigation-cache。
7. 輸入框每打一個字就失焦
症狀:中文輸入法打到一半被打斷、每輸入一個字要重新點一次。
原因幾乎都是「每次 render 都重新建立元件」或「用 key 綁到會變的值」。詳見 forms-focus-input。
8. 上一頁回不去 / 篩選狀態消失
篩選、分頁、tab 全放在元件 state 裡,重新整理就沒了,也沒辦法把連結傳給同事。
修法:這類狀態放 URL query string。
三、上線一個月後才痛
9. 顏色寫死,換不了主題
只要有一個地方寫了 #fff,深色模式那裡就是白的。規則:元件只能讀 var(--ds-*)。 詳見 token-contract。
10. 布林值爆炸
const [isLoading, setIsLoading] = useState(false);
const [isError, setIsError] = useState(false);
const [data, setData] = useState(null);
三個布林 = 八種組合,其中五種不該存在(載入中同時失敗?)。改成一個 union:
type State =
| { status: 'idle' }
| { status: 'loading' }
| { status: 'success'; data: Item[] }
| { status: 'error'; message: string };
11. 沒有空狀態、載入狀態、錯誤狀態
只做了「有資料而且成功」那一種。一個清單至少要設計四個畫面:載入中、空的、錯誤、有資料。而且「空的」還分三種:還沒建立、搜尋無結果、沒有權限 —— 講法完全不同。
12. 註解寫的是「這行在做什麼」
// 設定 loading 為 true
setLoading(true);
程式碼已經說了。註解該寫的是為什麼。詳見 comments-and-cleanup。
13. 沒有人知道哪一段是「暫時的」
AI 很會生 mock 資料與 // TODO: 接真的 API。三個月後沒人記得哪些是假的。
做法:假資料一律集中在 src/lib/mock/,並在 build 時檢查 production bundle 有沒有引用到它。
14. 一個檔案 2000 行
AI 傾向把東西加在既有檔案末端。定期問它:「這個檔案有沒有超過一個責任?」超過就拆。詳見 architecture-layers。
一份可以直接貼的自檢 prompt
請針對我剛才的改動做一次自我檢查,逐項回答有或沒有,有問題直接修:
1. 有沒有任何金鑰、token 出現在會打包到前端的檔案?
2. 有沒有把原始錯誤訊息直接顯示給用戶?
3. 360px 寬度會不會出現橫向捲動?
4. 每張圖片有沒有指定寬高?
5. 有沒有寫死的顏色、字體、間距?
6. 有沒有三個以上的布林值在描述同一件事的狀態?
7. 清單類畫面有沒有做載入中、空、錯誤三種狀態?
8. 所有互動元素用鍵盤走得完嗎?focus 看得見嗎?