TL;DR(太長不看版)
🐌 舊版問題:Google Apps Script + 試算表,1 萬筆資料載入要 13 秒
🚀 新版改進:Next.js + Supabase,載入時間 <1 秒,提升 13 倍
🤖 開發方式:幾乎全部靠 AI 輔助開發📊 技術棧:Next.js 14 + React 19 + Supabase (PostgreSQL) + Tailwind CSS
⚡ 核心收穫:記帳系統需要快速回應,試算表不適合當資料庫
專案連結:
- v2.56 (Google Apps Script)
- v3.38 程式碼範例見文章內容
一、痛點:每次等 13 秒的煎熬
想像一下這個場景:
你剛吃完午餐,拿出手機準備記帳。打開記帳網站,螢幕顯示「載入中...」,然後你等了 5 秒、10 秒、13 秒,頁面終於出現了。
你記完這筆交易,想切換到另一個記帳本(例如從「個人開銷」切換到「旅行分帳」),然後又是 13 秒的等待。
這是我在 v2.56(Google Apps Script 版本)遇到的真實問題。
為什麼會這麼慢?
v2.56 使用 Google Apps Script + Google 試算表 作為後端:
執行流程:
使用者請求
→ GAS 讀取試算表(1 萬筆資料)
→ 在試算表中線性搜尋、篩選
→ 回傳結果給前端
→ 總耗時:13 秒
試算表不是資料庫。它沒有索引、沒有查詢優化、沒有快取機制。當資料量超過 5,000 筆時,效能就會急劇下降。
v2.56 的實際程式碼(GAS)
📝 程式碼範例:v2.56 查詢交易記錄的方式
function getTransactions(bookId) {
const sheet = SpreadsheetApp.openById(SHEET_ID)
.getSheetByName('transactions');
const data = sheet.getDataRange().getValues(); // 讀取所有資料
const transactions = [];
// 從第 2 行開始(第 1 行是標題)
for (let i = 1; i < data.length; i++) {
const row = data[i];
// 手動篩選符合條件的資料
if (row[1] === bookId) {
transactions.push({
id: row[0],
bookId: row[1],
amount: row[2],
category: row[3],
description: row[4],
date: row[5],
userId: row[6]
});
}
}
// 手動排序(按日期)
transactions.sort((a, b) => new Date(b.date) - new Date(a.date));
return transactions; // 耗時:13 秒(1 萬筆資料)
}
問題分析:
- ❌
getDataRange().getValues()每次都讀取整張試算表(包括不需要的資料) - ❌ 使用 for 迴圈線性搜尋(O(n) 時間複雜度)
- ❌ 沒有索引,每次都要掃描所有資料
- ❌ 試算表的 I/O 本身就很慢
效能測試數據
當資料量增加時,載入時間呈指數成長:
- 1,000 筆資料:約 2 秒
- 5,000 筆資料:約 6 秒
- 10,000 筆資料:約 13 秒
- 20,000 筆資料:估計超過 25 秒(完全無法使用)
對於一個記帳系統來說,這是致命的。記帳應該是「快速記錄」,而不是「等待載入」。
二、v2.56 的其他問題
除了效能,Google Apps Script 還有其他限制:
1. 沒有資料夾結構
GAS 的程式碼是扁平化的,所有檔案都在同一層。當專案變大時,找程式碼超級困難。
v2.56 的檔案結構:
📁 v2.56 (GAS)
├── Code.gs (主程式)
├── Database.gs (資料庫操作)
├── Utils.gs (工具函數)
├── Report.gs (報表)
├── Group.gs (群組功能)
├── Settlement.gs (結算邏輯)
└── HTML檔案 × 20 (各種頁面)
沒有資料夾分類,只能靠檔名區分。
2. 沒有 TypeScript
GAS 只支援 JavaScript,沒有型別檢查。這導致很多低級錯誤,而且 IDE 無法提供自動完成。
例如:
// 無法知道 transaction 有哪些欄位
function addTransaction(transaction) {
// transaction.amount? transaction.ammount?
// 打錯字只能在執行時才會發現
}
3. HTML Service 限制
GAS 的前端使用 HTML Service,功能很陽春:
- ❌ 無法使用現代前端框架(React、Vue)
- ❌ 無法使用 npm 套件
- ❌ 樣式很難調整(CSS 要寫在
<style>標籤裡) - ❌ 手機版體驗差(無響應式設計)
4. 開發體驗差
- 只能在瀏覽器裡寫程式碼(不能用 VS Code)
- 沒有 Git 版本控制(要手動複製貼上)
- 無法多人協作(只能一個人編輯)
- 部署要手動點擊「發布」
5. 優化無效
我嘗試過各種優化方法:
- ✅ 使用
getValues()批次讀取(有用,但不夠) - ✅ 快取常用資料(有用,但過期機制難處理)
- ✅ 限制查詢範圍(有用,但還是慢)
最終結論:試算表不適合當資料庫。
三、決定重寫的轉捩點
當資料量達到 1 萬筆 時,系統變得幾乎無法使用。
每次開啟網站或切換記帳本,都要等 13 秒。
這已經不是「效能優化」能解決的問題了,而是架構問題。
我意識到:
- 試算表不是資料庫,無法再優化
- GAS 的限制讓新功能難以實作(例如:即時通知、複雜權限控制)
- 程式碼越來越難維護(扁平結構 + 無 TypeScript)
是時候重寫了。
四、技術選型:為什麼是 Next.js + Supabase?
考慮過的方案
我評估了幾個方案:
繼續優化 GAS:不用重寫,但試過了沒用 → ❌ 放棄
Firebase:Google 生態系,但 NoSQL 不適合關聯資料、價格貴 → ❌ 不選
MERN Stack:完全掌控,但要自己架資料庫、寫認證 → ❌ 太麻煩
Next.js + Supabase:現代化、免費、PostgreSQL、需要學習 → ✅ 最終選擇
為什麼選 Next.js?
Next.js 是 React 的全端框架,解決了純 React 的很多問題:
- 全端開發:前端 + 後端 API 在同一個專案
- App Router:檔案系統路由,超級直觀
- Server Components:可以在伺服器端渲染,減少 JavaScript Bundle
- 效能優化:自動代碼分割、圖片優化
- 部署簡單:Vercel 一鍵部署(免費)
為什麼選 Supabase?
Supabase 是開源的 Firebase 替代方案,但使用 PostgreSQL 而非 NoSQL:
1. PostgreSQL 適合記帳系統
記帳資料有很多關聯(交易 ↔ 分類 ↔ 群組 ↔ 成員)。SQL 的 JOIN 查詢天生適合處理關聯資料,而 Firebase 的 NoSQL 需要反正規化,查詢複雜。
2. 自動生成 API
建立資料表後,Supabase 自動生成 RESTful API。不用自己寫 app.get(), app.post()。
3. Row Level Security (RLS)
在資料庫層級控制權限。例如:使用者只能看到自己的記帳資料。比在 API 層級控制更安全。
4. 免費版夠用
- 500MB 資料庫(記帳系統綽綽有餘)
- 5GB 檔案儲存
- 50,000 月活躍使用者
5. 開源
可以看到所有原始碼,也可以自己架設(不會被廠商綁架)。
技術棧總覽
前端:Next.js 14 + React 19 + TypeScript + Tailwind CSS 後端:Supabase (PostgreSQL + Auth + Storage) 部署:Vercel (免費) 開發:VS Code + Git + AI 輔助
五、Supabase 設定與程式碼範例
初始化 Supabase Client(伺服器端)
📝 程式碼範例:lib/supabase/server.ts
import { createServerClient } from '@supabase/ssr'
import { cookies } from 'next/headers'
export async function createClient() {
const cookieStore = await cookies()
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
},
},
}
)
}
前端元件使用範例
📝 程式碼範例:app/transactions/page.tsx - 交易列表頁面
'use client'
import { useState, useEffect } from 'react'
import { createClient } from '@/lib/supabase/client'
export default function TransactionsPage() {
const [transactions, setTransactions] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
loadTransactions()
}, [])
async function loadTransactions() {
const supabase = createClient()
// 查詢資料(自動帶入使用者權限)
const { data, error } = await supabase
.from('transactions')
.select('*')
.order('date', { ascending: false })
if (!error) {
setTransactions(data)
}
setLoading(false) // 載入完成:<1 秒
}
if (loading) return <div>載入中...</div>
return (
<div>
<h1>交易記錄</h1>
{transactions.map(t => (
<div key={t.id}>
{t.date} - {t.description} - ${t.amount}
</div>
))}
</div>
)
}
對比 GAS 版本的前端
v2.56 使用 google.script.run,需要等待 13 秒:
// v2.56 前端(HTML Service)
function loadTransactions() {
showLoading(); // 開始載入
google.script.run
.withSuccessHandler(function(transactions) {
// 13 秒後才會執行到這裡
hideLoading();
renderTransactions(transactions);
})
.withFailureHandler(function(error) {
alert('載入失敗:' + error);
})
.getTransactions(currentBookId);
}
v3.38 使用標準的 async/await,程式碼更直觀、更好維護,而且不到 1 秒就完成。
六、AI 輔助開發實戰(重點章節)
這次重寫,幾乎全部靠 AI 完成。這不是誇張,而是 2025 年開發的真實寫照。
AI 輔助的範圍
AI 的貢獻與我的貢獻:
- 資料庫設計:🤖 AI 生成 Schema → ✅ 我審查、調整
- API 邏輯:🤖 AI 寫完整 CRUD → ✅ 我測試、修 Bug
- 前端元件:🤖 AI 寫基礎元件 → ✅ 我調整樣式、邏輯
- 認證系統:🤖 AI 寫 Supabase Auth 整合 → ✅ 我加入邀請制邏輯
- 分帳演算法:✅ 我自己寫(核心邏輯) → 🤖 AI 協助優化
- UI/UX:🤖 AI 建議 Tailwind 樣式 → ✅ 我設計整體風格
實際案例:用 AI 生成 CRUD API
我給 AI 的 Prompt:
我需要一個 Next.js 14 App Router 的 API,處理 transactions 資料表的 CRUD。
資料表欄位:
- id (UUID)
- user_id (UUID, 外鍵到 users)
- amount (DECIMAL)
- category (TEXT)
- description (TEXT)
- date (DATE)
- created_at (TIMESTAMP)
要求:
1. 使用 Supabase Client
2. 檢查使用者權限(只能操作自己的資料)
3. 使用 TypeScript
4. 錯誤處理
AI 生成的程式碼(稍作調整後可直接使用):
📝 程式碼範例:app/api/transactions/route.ts
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const supabase = await createClient()
// 檢查使用者是否登入
const { data: { user }, error: authError } = await supabase.auth.getUser()
if (authError || !user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// 查詢使用者的交易記錄(RLS 會自動過濾)
const { data, error } = await supabase
.from('transactions')
.select('*')
.eq('user_id', user.id)
.order('date', { ascending: false })
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
return NextResponse.json(data)
}
export async function POST(request: Request) {
const supabase = await createClient()
const { data: { user }, error: authError } = await supabase.auth.getUser()
if (authError || !user) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
const body = await request.json()
// 新增交易
const { data, error } = await supabase
.from('transactions')
.insert({
...body,
user_id: user.id,
})
.select()
.single()
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
return NextResponse.json(data)
}
評價:這段程式碼基本可以直接使用,只需要微調錯誤訊息和驗證邏輯。
AI 最有幫助的地方
1. 快速生成重複性程式碼
CRUD API、表單元件、資料庫 Schema,節省 80% 的時間。
2. 學習新技術
我之前沒用過 Supabase,AI 提供範例程式碼,讓我邊做邊學。
3. 錯誤排查
複製錯誤訊息給 AI,快速找到解決方案。例如:TypeScript 型別錯誤、Supabase RLS 設定問題。
4. 程式碼優化
AI 建議更好的寫法(例如:用 Promise.all 並行查詢)。
AI 的限制
1. 無法理解業務邏輯
分帳演算法(最少轉帳次數)需要我自己寫。AI 會給基礎實作,但不夠聰明。
2. 可能產生錯誤程式碼
例如:忘記處理 edge case。需要仔細審查和測試。
3. 無法做架構設計
整體專案結構、資料夾分類還是要自己規劃。AI 只能幫忙寫單一元件或功能。
我的開發流程
1. 我設計架構(資料庫 Schema、頁面結構)
2. 用 AI 生成基礎程式碼(CRUD、元件)
3. 我審查、測試、調整
4. 遇到問題問 AI(錯誤排查、優化建議)
5. 核心邏輯自己寫(分帳演算法、邀請制)
結論:AI 是超強的助手,但還是需要人類把關。
七、成果對比
效能提升
v2.56 vs v3.38 效能對比:
- 載入時間:13 秒 → <1 秒(提升 13 倍)
- 切換記帳本:13 秒 → <1 秒(提升 13 倍)
- 新增交易:3 秒 → <0.5 秒(提升 6 倍)
- 查詢統計:8 秒 → <1 秒(提升 8 倍)
資料量測試:
- v2.56: 1 萬筆 = 13 秒
- v3.38: 1 萬筆 = 0.8 秒
- 提升 16 倍
v3.38 的查詢程式碼(Next.js + Supabase)
相同的功能,使用 Next.js + Supabase 實作:
📝 程式碼範例:app/api/transactions/route.ts - v3.38 查詢交易記錄
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
export async function GET(request: Request) {
const { searchParams } = new URL(request.url)
const bookId = searchParams.get('bookId')
const supabase = await createClient()
// PostgreSQL 自動使用索引查詢
const { data, error } = await supabase
.from('transactions')
.select('*')
.eq('book_id', bookId)
.order('date', { ascending: false })
if (error) {
return NextResponse.json({ error: error.message }, { status: 500 })
}
return NextResponse.json(data) // 耗時:<1 秒(1 萬筆資料)
}
改進分析
為什麼 v3.38 快這麼多?
✅ SQL 查詢使用索引:PostgreSQL 的 WHERE book_id = ? 有索引優化,不需要掃描全表
✅ 資料庫只回傳需要的資料:不像 GAS 要讀取整張試算表
✅ 資料庫端排序:ORDER BY date DESC 由資料庫處理,比 JavaScript 快很多
✅ PostgreSQL 效能優化:B-tree 索引、查詢計畫最佳化
程式碼對比總結
v2.56 (GAS):
- 資料讀取:
getDataRange().getValues()讀取全部 - 篩選方式:JavaScript
for迴圈 - 排序方式:JavaScript
.sort() - 程式碼行數:30 行
- 執行時間:13 秒
v3.38 (Next.js + Supabase):
- 資料讀取:SQL 只讀取需要的資料
- 篩選方式:SQL
WHERE子句(有索引) - 排序方式:SQL
ORDER BY(資料庫端) - 程式碼行數:15 行
- 執行時間:<1 秒
八、開發體驗提升
工具和環境對比
編輯器:瀏覽器 → VS Code
版本控制:❌ 無 → ✅ Git
型別檢查:❌ 無 → ✅ TypeScript
程式碼結構:扁平化 → 模組化(資料夾)
套件管理:❌ 無 → ✅ npm
除錯工具:Logger.log() → React DevTools、Chrome DevTools
熱重載:❌ 無 → ✅ 自動重載
功能提升
v3.38 新增的功能(v2.56 做不到):
1. 邀請制註冊系統
只有持有邀請碼的人可以註冊,保護隱私、控制使用者數量。
2. 發票上傳功能
拍照上傳收據,使用 Supabase Storage 處理。
3. 信用卡回饋追蹤
記錄每張卡的回饋金額。
4. 即時更新
Supabase Realtime 支援,多人協作時即時同步。
5. 響應式設計
手機版體驗好很多,使用 Tailwind CSS + shadcn/ui。
6. 進階權限控制
Row Level Security (RLS),在資料庫層級保護資料。
維護性提升
程式碼行數對比:
- 總行數:v2.56 約 8,000 行 → v3.38 約 12,000 行
- 平均檔案大小:v2.56 約 500 行 → v3.38 約 100 行
- 最大檔案:v2.56 約 2,000 行 → v3.38 約 300 行
- 可讀性:v2.56 ⭐⭐ → v3.38 ⭐⭐⭐⭐⭐
v3.38 程式碼更多,但更好維護:
- 每個檔案只負責一件事
- 有資料夾分類
- TypeScript 型別檢查
- 清楚的註解
九、資料遷移:從試算表到 PostgreSQL
遷移策略
由於系統還沒正式上線,我選擇了最簡單的方式:
1. 從 v2.56 匯出 CSV(試算表 → CSV)
2. 在 v3.38 使用匯入功能(CSV → PostgreSQL)
3. 不需要複雜的資料庫遷移腳本
為什麼這樣做?
優點:
- ✅ 簡單、不會出錯
- ✅ 可以順便清理舊資料(刪除測試資料)
- ✅ 用戶重新開始,體驗新系統
缺點:
- ❌ 無法保留使用者帳號(要重新註冊)
- ❌ 如果是正式上線的系統,不能這樣做
適用場景:
- 個人使用或小團隊
- 資料量不大(<1 萬筆)
- 願意重新匯入資料
如果是正式系統該怎麼做?
如果 v2.56 已經有真實使用者,遷移策略會是:
1. 寫資料遷移腳本
// 從試算表讀取資料
const sheet = SpreadsheetApp.openById('SHEET_ID')
const data = sheet.getDataRange().getValues()
// 轉換格式
const transactions = data.map(row => ({
amount: row[0],
category: row[1],
date: row[2],
// ...
}))
// 插入到 Supabase
const supabase = createClient(SUPABASE_URL, SUPABASE_KEY)
await supabase.from('transactions').insert(transactions)
2. 分批遷移(避免一次插入太多資料)
3. 驗證資料完整性(確認筆數、金額總和是否一致)
4. 提供使用者遷移工具(讓使用者自己匯入)
十、給讀者的建議
什麼時候該從 GAS 遷移?
強烈建議遷移的情況:
📊 資料量 > 5,000 筆:試算表會變很慢
🐢 載入時間 > 5 秒:使用者體驗差
📱 需要手機友善:GAS HTML Service 很難做響應式
👥 多人協作:GAS 程式碼難以協作
🔒 需要複雜權限控制:試算表權限機制有限
🚀 需要新功能:例如即時通知、檔案上傳、複雜 UI
可以繼續用 GAS 的情況:
📝 個人小工具:只有你自己用
🔢 資料量 < 1,000 筆:效能還可以接受
💻 只在電腦上用:不在意手機體驗
⚡ 快速原型:不想花時間學新技術
技術選型建議
如果你決定遷移,我推薦的技術棧:
前端:
- ✅ Next.js(React 全端框架)
- ✅ TypeScript(型別安全)
- ✅ Tailwind CSS(快速寫 CSS)
後端:
- ✅ Supabase(PostgreSQL + Auth + Storage)
- 或 Firebase(如果你喜歡 NoSQL)
- 或 自架 PostgreSQL + Prisma(完全掌控)
部署:
- ✅ Vercel(Next.js 最佳搭檔,免費)
- 或 Netlify
- 或 Cloudflare Pages
開發工具:
- ✅ VS Code
- ✅ Git + GitHub
- ✅ AI 輔助(ChatGPT / Claude / GitHub Copilot)
學習資源
Next.js:
- 官方文件(超級清楚)
- Next.js 14 完整教學
Supabase:
TypeScript:
十一、總結
關鍵數據回顧
- 效能提升:13 秒 → <1 秒(13 倍)
- 開發方式:幾乎全靠 AI 輔助
- 資料遷移:使用 CSV 匯入(簡單有效)
- 技術棧:Next.js + Supabase(現代化、免費)
最大的收穫
1. 試算表不是資料庫
小專案(<1,000 筆)可以用,超過 5,000 筆就該換真正的資料庫。
2. AI 改變了開發方式
不用從零開始寫每一行程式碼,專注在架構設計和核心邏輯,開發速度提升 3-5 倍。
3. 現代化技術棧很重要
TypeScript 幫我抓到很多錯誤,Git 讓我不怕改壞程式碼,模組化結構讓維護變簡單。
4. 使用者體驗是關鍵
載入時間從 13 秒 → <1 秒,這是質的飛躍,不只是量的提升。
給想要重寫專案的你
重寫的決策標準:
如果 (效能問題 OR 維護困難 OR 功能受限)
AND (有時間學習新技術)
AND (資料遷移可行)
則 → 值得重寫
否則 → 繼續優化現有系統
重寫的建議:
- 先列出舊系統的所有痛點
- 調查新技術是否真的能解決問題
- 評估學習成本和時間投入
- 制定資料遷移計畫
- 分階段重寫(先做核心功能)
- 善用 AI 輔助開發
下一步
如果你也在用 Google Apps Script,遇到類似的效能問題,歡迎參考我的經驗。
你也可以:
- ⭐ 給舊專案一個 Star:v2.56 GitHub
- 💬 留言討論你的遷移經驗
- 📧 有技術問題歡迎聯絡我:bill86854238@gmail.com
十二、專案連結
- v2.56 (Google Apps Script):GitHub
- v3.38 核心程式碼範例:見文章內容(完整專案包含個人資料,暫不公開)
十三、架構對比圖
v2.56 架構(GAS + 試算表)
使用者
↓
Google Apps Script (HTML Service)
↓
Google 試算表(模擬資料庫)
- Sheet 1: 交易記錄
- Sheet 2: 使用者資料
- Sheet 3: 分類
- ...
↓
線性搜尋、手動過濾
↓
回傳結果(13 秒)
v3.38 架構(Next.js + Supabase)
使用者
↓
Next.js App Router (Vercel)
├─ Server Components(伺服器端)
└─ Client Components(客戶端)
↓
Supabase
├─ PostgreSQL(真正的資料庫)
│ ├─ 索引優化
│ ├─ JOIN 查詢
│ └─ Row Level Security
├─ Auth(認證系統)
└─ Storage(檔案儲存)
↓
回傳結果(<1 秒)
感謝閱讀!如果這篇文章對你有幫助,歡迎分享給朋友。
有任何問題或建議,歡迎在下方留言!
📬 聯絡方式
- 📧 Email: bill86854238@gmail.com
- 💻 GitHub: @bill86854238
- 💬 提供技術諮詢服務(首次 30 分鐘免費)
作者:麟幻 GitHub:@bill86854238

















