跳到主要內容

Vibe Coding 十四個常見地雷

沒有前端經驗、靠 AI 一路生出來的專案,壞掉的地方高度重複。這是清單,每一項都附上怎麼看出來、怎麼修。

約 6 分鐘 · vibe-coding-pitfalls.md

這些不是理論上的風險,是實際會在上線後被用戶發現的東西。依「多久會炸」排序。

一、當天就會被發現

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

詳見 layout-and-overlap

5. 版面跳動(layout shift)

圖片載入後把文字往下推、字型換掉後標題變高、載入完 spinner 消失整頁位移。

  • 每張 <img> 都要有 widthheight(或 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 看得見嗎?

顯示設定

這裡改的每一項,會即時套用到站上所有預覽。

風格

密度

圓角

動態

系統層級的「減少動態效果」永遠優先於這裡的設定。

語言