本文僅分享技術心得與效能改進經驗。
核心重點在於如何運用 AI 輔助完成重構與技術選型,純屬開發者個人實驗紀錄。
過去版本(v2.56)使用 Google Apps Script + Google 試算表 當作資料來源。
每次讀取 1 萬筆交易資料時,都要耗時 13 秒以上。
當切換記帳本時,還得再次等待,導致整體體驗極差。
// v2.56 Code.gs - 查詢交易記錄
function getTransactions(bookId) {
  const sheet = SpreadsheetApp.openById(SHEET_ID).getSheetByName('transactions');
  const data = sheet.getDataRange().getValues(); // 讀取所有資料
  const transactions = [];
  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;
}
效能瓶頸分析:
getDataRange().getValues() 每次都抓整張試算表| 資料筆數 | 載入時間 | 
|---|---|
| 1,000 筆 | 約 2 秒 | 
| 5,000 筆 | 約 6 秒 | 
| 10,000 筆 | 約 13 秒 | 
| 20,000 筆 | 超過 25 秒 | 
📁 v2.56
├── Code.gs
├── Database.gs
├── Utils.gs
├── Report.gs
└── HTML × 20
沒有資料夾結構,找程式碼要靠關鍵字。
沒有型別檢查,容易出現低級錯誤。
HTML Service 不能使用 React / npm,CSS 與 JS 整合困難,手機版不支援 RWD。
當資料量達 1 萬筆時,系統幾乎無法使用。
每次載入都要 13 秒,快取也救不了。
這時候我意識到:
👉 最終決定重寫系統。
| 方案 | 優點 | 缺點 | 結論 | 
|---|---|---|---|
| 優化 GAS | 不需重寫 | 效能天花板 | ❌ 放棄 | 
| Firebase | 雲端整合佳 | NoSQL 不適合關聯資料 | ❌ 放棄 | 
| MERN Stack | 彈性高 | 成本高、部署麻煩 | ❌ 放棄 | 
| Next.js + Supabase | 現代化、快速上手 | 需學習新技術 | ✅ 採用 | 
// 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 { useEffect, useState } 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 } = await supabase.from('transactions')
      .select('*')
      .order('date', { ascending: false })
    setTransactions(data || [])
    setLoading(false)
  }
  if (loading) return <div>載入中...</div>
  return (
    <div>
      <h1>交易記錄</h1>
      {transactions.map(t => (
        <div key={t.id}>
          {t.date} - {t.description} - ${t.amount}
        </div>
      ))}
    </div>
  )
}
這次重構幾乎全程由 AI 參與協助,加速至少 70% 開發時間。
| 項目 | AI 輔助 | 我的人工作業 | 
|---|---|---|
| 資料表設計 | Schema 初稿 | 審查調整 | 
| CRUD API | 自動生成 | 測試與修正 | 
| 前端頁面 | 元件雛形 | 美化與邏輯補強 | 
| 認證 | Supabase Auth 整合 | 加上邀請制 | 
| 分帳邏輯 | 提示演算法 | 驗證與優化 | 
// app/api/transactions/route.ts
import { createClient } from '@/lib/supabase/server'
import { NextResponse } from 'next/server'
export async function GET() {
  const supabase = await createClient()
  const { data: { user } } = await supabase.auth.getUser()
  if (!user) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  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)
}
| 指標 | v2.56 (GAS) | v3.38 (Next.js + Supabase) | 
|---|---|---|
| 資料載入時間 | 13 秒 | <1 秒 | 
| 程式結構 | 扁平 | 模組化 | 
| 開發效率 | 低 | 高(AI 協助) | 
| 擴充性 | 差 | 高(API + DB) | 
| 部署方式 | 手動 | Vercel 自動部署 |