Vibe Coding 從零到部署: 30 個小時寫出一套學習儀表板(下)|專家論點【鄭緯筌Vista】
作者:鄭緯筌(專欄作家,「臺灣電子商務暨創業聯誼會」共同創辦人,前「APP01」網站總監、《風傳媒》產品總監和《數位時代》雜誌主編)
Debug 之旅:當測試成為最好的老師
環境變數的隱形殺手
在本地測試時,一切運作正常。但當用戶回報「新增筆記失敗」時,我陷入了困惑。後端日誌顯示沒有收到任何 POST 請求,前端卻顯示「保存失敗,請稍後再試」。老實說,這種「幽靈錯誤」最難除錯,因為你不知道問題出在哪一個環節?
經過 Claude Code 仔細地檢查,發現前端開啟了兩個 Vite 開發伺服器實例,而且它們使用的環境變數可能不一致。問題的根源在於我在不同的終端視窗中多次執行 npm run dev,而 Vite 的熱重載機制讓我誤以為服務已經更新。
經過與 Claude Code 的討論,解決方法很直接:關閉所有 Vite 進程,重新啟動單一實例。但這個經驗讓我學會了使用 ps aux | grep vite 來檢查執行中的進程,以及在啟動服務前先確認沒有殘留的進程。更重要的是,我在專案中加入了 .env.example 檔案,明確記錄所有需要的環境變數,避免團隊成員(或未來的自己)踩到同樣的坑。
資料庫初始化的暗黑時刻
在準備部署時,我發現一個更嚴重的問題:本地測試用的 SQLite 資料庫檔案是 0 位元組。這意味著資料庫從未真正初始化成功,我一直在測試的是某個未知位置的資料庫檔案。
原來,Flask 的資料庫初始化邏輯預設會在 instance 目錄下創建資料庫檔案,而我的 app.py 中使用了相對路徑 sqlite:///gradpilot.db,這導致 Flask 在預期的位置找不到資料庫。更糟的是,我在 app.py 中實作了一個簡單的初始化機制:只有在第一次請求時才創建資料表。如果這個機制失敗,所有後續的請求都會失敗,但錯誤訊息卻不明確。
修復這個問題,需要手動執行資料庫初始化:進入 Python shell,匯入 app 和 db,然後執行 db.create_all()。從那之後,我學會了在專案的 README.md 中明確記錄初始化步驟,並在部署腳本中加入資料庫初始化指令。這個教訓告訴我,部署前的環境驗證絕對不能省略。
測試驅動的品質保證
當所有功能都實作完成後,我面臨一個問題:如何確保所有功能都能正常運作?手動測試既耗時,又容易遺漏細節。於是,我請 Claude Code 創建了一份詳盡的測試清單 TESTING_CHECKLIST.md,涵蓋 12 個主要功能模組、超過 300 個測試項目。
這份清單不僅僅是一個核取清單,它更像是一份互動式的測試指南。每個測試項目都包含明確的操作步驟和預期結果,讓任何人都能按圖索驥地完成測試。在測試過程中,我發現了前面提到的所有 bug,也驗證了修復的正確性。
在測試的過程中,也讓我重新審視了使用者體驗。例如,我發現番茄鐘的提示文字寫著「保持專注 25 分鐘」,但這個數字應該根據使用者設定動態改變。這種細節很容易在開發過程中被忽略,但對使用者體驗卻有顯著影響。
部署的最後一哩路
在本地端的程式順利執行之後,接下來會遇到另一個問題,就是不知要部署到哪個主機?得花多少錢?
經過 Claude Code 的建議,最後選擇了 Render 作為部署平臺。相比於 Heroku(已取消免費方案)和 AWS(對新手不友善),Render 提供了簡單易用的介面和慷慨的免費額度。更重要的是,它原生支援從 Git 倉庫自動部署,這讓 CI/CD 流程變得極其簡單。
此外,部署配置檔 render.yaml 很重要,它定義了整個應用的架構:PostgreSQL 資料庫、Python 後端服務與靜態前端網站。Render 會自動讀取這個檔案,創建所有必要的資源,並設定正確的環境變數。唯一需要手動處理的是敏感資訊如 SECRET_KEY 和 JWT_SECRET_KEY,Render 提供了自動生成功能,確保每個部署環境都有獨立的密鑰。
第一次部署時,我遇到了 CORS 錯誤。前端部署在 gradpilot-frontend.onrender.com,後端部署在 gradpilot-backend.onrender.com,瀏覽器的同源政策阻止了跨域請求。解決方法是在後端配置 CORS 白名單,允許來自前端域名的請求。這個問題提醒我,本地開發環境和生產環境的差異永遠存在,必須在實際部署後進行完整測試。

效能優化:在免費方案的限制中求生存
值得注意的是,Render 的免費方案有一個顯著的限制:服務閒置 15 分鐘後會進入休眠狀態,下次訪問時需要 30-60 秒的喚醒時間。這對使用者體驗是個挑戰,但也促使我思考如何在前端層面進行優化。
在 Claude Code 的協助之下,我實作了一個簡單但有效的載入狀態管理系統。當使用者首次訪問時,如果後端還在喚醒,前端會顯示友善的載入動畫和提示訊息,而不是讓使用者面對一片空白。這個小小的改變大幅提升了使用者的容忍度。
另一個優化是實作資料快取。用戶的基本資訊在登入後會存放在 localStorage,避免每次頁面刷新都需要重新從後端獲取。這不僅加快了載入速度,也減少了後端的負擔。當然,敏感資訊如 JWT token 需要謹慎處理,我設定了合理的過期時間,並在登出時清除所有快取資料。
從 MVP 到產品:使用者回饋的力量
當 GradPilot 2.0 上線後,我邀請了幾位研究生朋友試用。他們的回饋既有鼓勵也有批評,但都極具價值。有人反映番茄鐘的預設時間太長,有人希望能在筆記中插入圖片,有人建議增加資料匯出功能以防資料遺失。
這些回饋,促使我重新思考產品的定位。GradPilot 不應該是一個功能完整的工具,而應該是一個恰到好處的工具。畢竟,研究生已經有太多複雜的工具需要學習,他們需要的是一個簡單、直覺又能專注於核心需求的平臺。
基於這個認知,我實作了資料匯出功能,支援 JSON、CSV 與 Markdown 等多種格式。這不僅解決了資料備份的需求,也讓使用者能自由地將資料遷移到其他工具。這種不鎖定使用者的設計哲學,反而增加了使用者的信任和忠誠度。
技術債與重構的永恆輪迴
隨著功能的增加,技術債也在累積。有些程式碼寫得匆忙,有些設計不夠優雅,有些測試覆蓋不足。但作為一個新手開發者,我必須在完美和完成之間取得平衡。
Claude Code 教會我採用漸進式重構的策略,也就是每次新增功能時,順帶重構相關的舊程式碼。例如,在實作文獻管理功能時,我發現待辦事項和筆記的標籤處理邏輯幾乎相同,於是我抽取了一個共用的 formatTags 函數。這種遇到問題就修改的策略,讓程式碼品質在不知不覺中持續提升。
當然,有些技術債必須專門騰出時間來處理。例如,前端的狀態管理一度變得非常混亂,有些狀態存在 Context,有些存在組件內部,有些存在 localStorage。我花了一個晚上梳理問題,統一了狀態管理的模式,大幅降低 bug 的發生率。
開源與社群:站在巨人的肩膀上
這個專案能取得初步的成功,很大程度上歸功於開源社群。Flask、React、Vite、TailwindCSS⋯⋯每一個工具都凝聚了無數開發者的心血。當我遇到問題時,往往能在 Stack Overflow、GitHub Issues 或官方文件中找到答案。
作為回饋,我選擇將 GradPilot 2.0 的完整原始碼開源在 GitHub 上,並撰寫了詳盡的文件和部署指南。我希望這個專案能幫助其他有類似需求的研究生,也希望它能成為初學者學習全端開發的參考案例。
開源不僅僅是分享程式碼,更是分享知識和經驗。在撰寫文件的過程中,我重新審視了自己的設計決策,發現了許多可以改進的地方。教學相長,這個過程本身就是一種學習。

展望未來:永不停歇的迭代
GradPilot 2.0 目前已經滿足了我的基本需求,但這只是開始。我已經在規劃 3.0 版本,計劃加入更多進階功能:與文獻資料庫的 API 整合、基於機器學習的文獻推薦或多人協作的研究筆記等。
但在追求新功能之前,我會先確保現有功能的穩定性和使用者體驗。這是我在這次開發中學到的最重要的一課:好的產品不是功能最多的產品,而是最能解決使用者問題的產品。
回顧這 30 個小時的開發旅程,在 Claude Code 的協助之下,我不僅建立了一個實用的工具,更重要的是培養了一種系統化的思考方式。面對複雜的問題,如何拆解成可管理的小任務?遇到 bug 時,如何有條理地排查?在資源有限的情況下,如何做出明智的技術選擇?
我覺得,這些能力遠比寫出漂亮的程式碼更重要。技術會過時,工具會更迭,但解決問題的思維方式是永恆的。GradPilot 2.0 對我來說不只是一個專案,它是一次完整的學習歷程,一次從想法到產品的實踐,一次對自己能力的挑戰和證明。
無論你是一位上班族或研究生,如果你也有想要解決的問題,我鼓勵你動手去做。不要被技術的複雜性嚇倒,不要等到準備好了才開始。正如我的經驗所示,最好的學習往往發生在實作的過程中,最大的成長往往來自於克服困難的時刻。
現在,就開始和 AI 共創你的第一行程式碼吧。你的作品正在呼喚你!
![]()






