[{"data":1,"prerenderedAt":1439},["ShallowReactive",2],{"blog:\u002Fblog\u002Fnuxt\u002Fsupabase-local-first":3},{"id":4,"title":5,"author":6,"body":7,"category":1423,"date":1424,"description":1425,"draft":1426,"extension":1427,"image":1428,"meta":1429,"navigation":159,"path":1430,"seo":1431,"series":1432,"seriesOrder":175,"seriesTitle":1433,"stem":1434,"tags":1435,"updatedAt":1428,"__hash__":1438},"blog\u002Fblog\u002Fnuxt\u002Fsupabase-local-first\u002Findex.md","Supabase Local-First 開發流程","charles",{"type":8,"value":9,"toc":1393},"minimark",[10,14,18,34,37,40,45,56,60,112,114,118,121,287,291,409,412,415,478,480,483,570,572,576,580,657,661,664,745,747,750,754,794,812,815,864,866,869,1072,1074,1077,1081,1087,1092,1107,1113,1117,1126,1136,1140,1174,1178,1186,1190,1240,1242,1246,1249,1305,1307,1310,1346,1348,1351,1389],[11,12,13],"h2",{"id":13},"這篇要解決什麼問題",[15,16,17],"p",{},"資料庫 Schema 變更是最容易出錯的環節。這篇文章將說明：",[19,20,21,25,28,31],"ul",{},[22,23,24],"li",{},"為什麼堅持 Local → Test → Push 流程",[22,26,27],{},"Migration 的建立與管理",[22,29,30],{},"為什麼禁止直接在 GUI 改 Schema",[22,32,33],{},"完整的 Supabase CLI 命令流程",[35,36],"hr",{},[11,38,39],{"id":39},"核心原則",[41,42,44],"h3",{"id":43},"local-first-開發流程","Local-First 開發流程",[46,47,52],"pre",{"className":48,"code":50,"language":51},[49],"language-text","┌─────────────────────────────────────────────────────────────┐\n│                      1. 本地開發                             │\n├─────────────────────────────────────────────────────────────┤\n│  supabase migration new add_users_table                     │\n│  → 編輯 SQL 檔案                                            │\n│  → supabase db reset（套用到本地）                          │\n└─────────────────────────────────────────────────────────────┘\n                              ↓\n┌─────────────────────────────────────────────────────────────┐\n│                      2. 安全檢查                             │\n├─────────────────────────────────────────────────────────────┤\n│  supabase db lint --level warning                           │\n│  → 必須零警告                                               │\n└─────────────────────────────────────────────────────────────┘\n                              ↓\n┌─────────────────────────────────────────────────────────────┐\n│                      3. 類型同步                             │\n├─────────────────────────────────────────────────────────────┤\n│  supabase gen types typescript --local                       │\n│  → 更新 app\u002Ftypes\u002Fdatabase.types.ts                          │\n│  → pnpm typecheck                                            │\n└─────────────────────────────────────────────────────────────┘\n                              ↓\n┌─────────────────────────────────────────────────────────────┐\n│                      4. 提交與推送                           │\n├─────────────────────────────────────────────────────────────┤\n│  git commit → git push                                       │\n│  → CI 執行 supabase db push                                  │\n└─────────────────────────────────────────────────────────────┘\n","text",[53,54,50],"code",{"__ignoreMap":55},"",[41,57,59],{"id":58},"為什麼不能直接改遠端","為什麼不能直接改遠端？",[61,62,63,76],"table",{},[64,65,66],"thead",{},[67,68,69,73],"tr",{},[70,71,72],"th",{},"問題",[70,74,75],{},"後果",[77,78,79,88,96,104],"tbody",{},[67,80,81,85],{},[82,83,84],"td",{},"無版本控制",[82,86,87],{},"無法追蹤誰改了什麼",[67,89,90,93],{},[82,91,92],{},"無法復原",[82,94,95],{},"出錯時難以回滾",[67,97,98,101],{},[82,99,100],{},"環境不一致",[82,102,103],{},"本地與遠端 Schema 不同步",[67,105,106,109],{},[82,107,108],{},"測試困難",[82,110,111],{},"直接在正式環境測試",[35,113],{},[11,115,117],{"id":116},"migration-工作流程","Migration 工作流程",[41,119,120],{"id":120},"標準開發流程",[46,122,126],{"className":123,"code":124,"language":125,"meta":55,"style":55},"language-bash shiki shiki-themes material-theme-lighter github-light github-dark","# 1. 建立新 migration\nsupabase migration new add_users_table\n\n# 2. 編輯 migration SQL（保持單一主題）\n# supabase\u002Fmigrations\u002F20240101000000_add_users_table.sql\n\n# 3. 套用到本機測試\nsupabase db reset\n\n# 4. 安全檢查\nsupabase db lint --level warning\n\n# 5. 重新產生 TypeScript types\nsupabase gen types typescript --local | tee app\u002Ftypes\u002Fdatabase.types.ts > \u002Fdev\u002Fnull\n\n# 6. 執行測試驗證\npnpm typecheck\n","bash",[53,127,128,137,154,161,167,173,178,184,195,200,206,223,228,234,267,272,278],{"__ignoreMap":55},[129,130,133],"span",{"class":131,"line":132},"line",1,[129,134,136],{"class":135},"sutJx","# 1. 建立新 migration\n",[129,138,140,144,148,151],{"class":131,"line":139},2,[129,141,143],{"class":142},"sbgvK","supabase",[129,145,147],{"class":146},"s_sjI"," migration",[129,149,150],{"class":146}," new",[129,152,153],{"class":146}," add_users_table\n",[129,155,157],{"class":131,"line":156},3,[129,158,160],{"emptyLinePlaceholder":159},true,"\n",[129,162,164],{"class":131,"line":163},4,[129,165,166],{"class":135},"# 2. 編輯 migration SQL（保持單一主題）\n",[129,168,170],{"class":131,"line":169},5,[129,171,172],{"class":135},"# supabase\u002Fmigrations\u002F20240101000000_add_users_table.sql\n",[129,174,176],{"class":131,"line":175},6,[129,177,160],{"emptyLinePlaceholder":159},[129,179,181],{"class":131,"line":180},7,[129,182,183],{"class":135},"# 3. 套用到本機測試\n",[129,185,187,189,192],{"class":131,"line":186},8,[129,188,143],{"class":142},[129,190,191],{"class":146}," db",[129,193,194],{"class":146}," reset\n",[129,196,198],{"class":131,"line":197},9,[129,199,160],{"emptyLinePlaceholder":159},[129,201,203],{"class":131,"line":202},10,[129,204,205],{"class":135},"# 4. 安全檢查\n",[129,207,209,211,213,216,220],{"class":131,"line":208},11,[129,210,143],{"class":142},[129,212,191],{"class":146},[129,214,215],{"class":146}," lint",[129,217,219],{"class":218},"stzsN"," --level",[129,221,222],{"class":146}," warning\n",[129,224,226],{"class":131,"line":225},12,[129,227,160],{"emptyLinePlaceholder":159},[129,229,231],{"class":131,"line":230},13,[129,232,233],{"class":135},"# 5. 重新產生 TypeScript types\n",[129,235,237,239,242,245,248,251,255,258,261,264],{"class":131,"line":236},14,[129,238,143],{"class":142},[129,240,241],{"class":146}," gen",[129,243,244],{"class":146}," types",[129,246,247],{"class":146}," typescript",[129,249,250],{"class":218}," --local",[129,252,254],{"class":253},"smGrS"," |",[129,256,257],{"class":142}," tee",[129,259,260],{"class":146}," app\u002Ftypes\u002Fdatabase.types.ts",[129,262,263],{"class":253}," >",[129,265,266],{"class":146}," \u002Fdev\u002Fnull\n",[129,268,270],{"class":131,"line":269},15,[129,271,160],{"emptyLinePlaceholder":159},[129,273,275],{"class":131,"line":274},16,[129,276,277],{"class":135},"# 6. 執行測試驗證\n",[129,279,281,284],{"class":131,"line":280},17,[129,282,283],{"class":142},"pnpm",[129,285,286],{"class":146}," typecheck\n",[41,288,290],{"id":289},"migration-檔案範例","Migration 檔案範例",[46,292,296],{"className":293,"code":294,"language":295,"meta":55,"style":55},"language-sql shiki shiki-themes material-theme-lighter github-light github-dark","-- supabase\u002Fmigrations\u002F20240101000000_add_users_table.sql\n\n-- 建立表格\nCREATE TABLE public.users (\n  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n  email text UNIQUE NOT NULL,\n  name text,\n  role text NOT NULL DEFAULT 'staff',\n  created_at timestamptz NOT NULL DEFAULT now(),\n  updated_at timestamptz NOT NULL DEFAULT now()\n);\n\n-- 啟用 RLS\nALTER TABLE public.users ENABLE ROW LEVEL SECURITY;\n\n-- 建立 RLS Policy\nCREATE POLICY \"Users can view own profile\"\n  ON public.users FOR SELECT\n  USING (auth.uid() = id);\n\n-- 建立索引\nCREATE INDEX users_email_idx ON public.users (email);\n","sql",[53,297,298,303,307,312,317,322,327,332,337,342,347,352,356,361,366,370,375,380,386,392,397,403],{"__ignoreMap":55},[129,299,300],{"class":131,"line":132},[129,301,302],{},"-- supabase\u002Fmigrations\u002F20240101000000_add_users_table.sql\n",[129,304,305],{"class":131,"line":139},[129,306,160],{"emptyLinePlaceholder":159},[129,308,309],{"class":131,"line":156},[129,310,311],{},"-- 建立表格\n",[129,313,314],{"class":131,"line":163},[129,315,316],{},"CREATE TABLE public.users (\n",[129,318,319],{"class":131,"line":169},[129,320,321],{},"  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n",[129,323,324],{"class":131,"line":175},[129,325,326],{},"  email text UNIQUE NOT NULL,\n",[129,328,329],{"class":131,"line":180},[129,330,331],{},"  name text,\n",[129,333,334],{"class":131,"line":186},[129,335,336],{},"  role text NOT NULL DEFAULT 'staff',\n",[129,338,339],{"class":131,"line":197},[129,340,341],{},"  created_at timestamptz NOT NULL DEFAULT now(),\n",[129,343,344],{"class":131,"line":202},[129,345,346],{},"  updated_at timestamptz NOT NULL DEFAULT now()\n",[129,348,349],{"class":131,"line":208},[129,350,351],{},");\n",[129,353,354],{"class":131,"line":225},[129,355,160],{"emptyLinePlaceholder":159},[129,357,358],{"class":131,"line":230},[129,359,360],{},"-- 啟用 RLS\n",[129,362,363],{"class":131,"line":236},[129,364,365],{},"ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;\n",[129,367,368],{"class":131,"line":269},[129,369,160],{"emptyLinePlaceholder":159},[129,371,372],{"class":131,"line":274},[129,373,374],{},"-- 建立 RLS Policy\n",[129,376,377],{"class":131,"line":280},[129,378,379],{},"CREATE POLICY \"Users can view own profile\"\n",[129,381,383],{"class":131,"line":382},18,[129,384,385],{},"  ON public.users FOR SELECT\n",[129,387,389],{"class":131,"line":388},19,[129,390,391],{},"  USING (auth.uid() = id);\n",[129,393,395],{"class":131,"line":394},20,[129,396,160],{"emptyLinePlaceholder":159},[129,398,400],{"class":131,"line":399},21,[129,401,402],{},"-- 建立索引\n",[129,404,406],{"class":131,"line":405},22,[129,407,408],{},"CREATE INDEX users_email_idx ON public.users (email);\n",[41,410,411],{"id":411},"單一主題原則",[15,413,414],{},"每個 Migration 只做一件事：",[46,416,418],{"className":123,"code":417,"language":125,"meta":55,"style":55},"# ✅ 好的命名（單一主題）\nsupabase migration new create_users_table\nsupabase migration new add_users_role_column\nsupabase migration new create_users_rls_policies\n\n# ❌ 不好的命名（混合多個主題）\nsupabase migration new update_users_and_add_posts_and_fix_policies\n",[53,419,420,425,436,447,458,462,467],{"__ignoreMap":55},[129,421,422],{"class":131,"line":132},[129,423,424],{"class":135},"# ✅ 好的命名（單一主題）\n",[129,426,427,429,431,433],{"class":131,"line":139},[129,428,143],{"class":142},[129,430,147],{"class":146},[129,432,150],{"class":146},[129,434,435],{"class":146}," create_users_table\n",[129,437,438,440,442,444],{"class":131,"line":156},[129,439,143],{"class":142},[129,441,147],{"class":146},[129,443,150],{"class":146},[129,445,446],{"class":146}," add_users_role_column\n",[129,448,449,451,453,455],{"class":131,"line":163},[129,450,143],{"class":142},[129,452,147],{"class":146},[129,454,150],{"class":146},[129,456,457],{"class":146}," create_users_rls_policies\n",[129,459,460],{"class":131,"line":169},[129,461,160],{"emptyLinePlaceholder":159},[129,463,464],{"class":131,"line":175},[129,465,466],{"class":135},"# ❌ 不好的命名（混合多個主題）\n",[129,468,469,471,473,475],{"class":131,"line":180},[129,470,143],{"class":142},[129,472,147],{"class":146},[129,474,150],{"class":146},[129,476,477],{"class":146}," update_users_and_add_posts_and_fix_policies\n",[35,479],{},[11,481,482],{"id":482},"命名規則",[61,484,485,498],{},[64,486,487],{},[67,488,489,492,495],{},[70,490,491],{},"項目",[70,493,494],{},"規則",[70,496,497],{},"範例",[77,499,500,517,533,545,557],{},[67,501,502,505,508],{},[82,503,504],{},"表名",[82,506,507],{},"snake_case 複數",[82,509,510,513,514],{},[53,511,512],{},"users",", ",[53,515,516],{},"tool_inserts",[67,518,519,522,525],{},[82,520,521],{},"欄位",[82,523,524],{},"snake_case",[82,526,527,513,530],{},[53,528,529],{},"created_at",[53,531,532],{},"user_id",[67,534,535,538,540],{},[82,536,537],{},"函式",[82,539,524],{},[82,541,542],{},[53,543,544],{},"get_user_role",[67,546,547,550,552],{},[82,548,549],{},"Enum",[82,551,524],{},[82,553,554],{},[53,555,556],{},"user_role",[67,558,559,562,565],{},[82,560,561],{},"Migration",[82,563,564],{},"snake_case 描述",[82,566,567],{},[53,568,569],{},"add_users_table",[35,571],{},[11,573,575],{"id":574},"gui-使用準則","GUI 使用準則",[41,577,579],{"id":578},"什麼可以用-gui","什麼可以用 GUI",[61,581,582,595],{},[64,583,584],{},[67,585,586,589,592],{},[70,587,588],{},"功能",[70,590,591],{},"可否使用",[70,593,594],{},"備註",[77,596,597,608,618,633,647],{},[67,598,599,602,605],{},[82,600,601],{},"查看資料",[82,603,604],{},"✅",[82,606,607],{},"無需額外動作",[67,609,610,613,615],{},[82,611,612],{},"查看 RLS Policy",[82,614,604],{},[82,616,617],{},"方便除錯",[67,619,620,623,626],{},[82,621,622],{},"快速 PoC",[82,624,625],{},"⚠️",[82,627,628,629,632],{},"用完必須 ",[53,630,631],{},"db diff"," 產出 migration",[67,634,635,638,641],{},[82,636,637],{},"建立函式",[82,639,640],{},"❌",[82,642,643,644],{},"無法控制 ",[53,645,646],{},"search_path",[67,648,649,652,654],{},[82,650,651],{},"直接匯入 SQL",[82,653,640],{},[82,655,656],{},"可能與 Repo 不同步",[41,658,660],{"id":659},"gui-變更後的處理","GUI 變更後的處理",[15,662,663],{},"如果在 Supabase Studio 做了變更：",[46,665,667],{"className":123,"code":666,"language":125,"meta":55,"style":55},"# 產生 diff 並存為 migration\nsupabase db diff --use-migra -f from_gui\n\n# 檢視產生的 SQL\ncat supabase\u002Fmigrations\u002F*_from_gui.sql\n\n# 繼續標準流程\nsupabase db reset\nsupabase db lint --level warning\n",[53,668,669,674,692,696,701,716,720,725,733],{"__ignoreMap":55},[129,670,671],{"class":131,"line":132},[129,672,673],{"class":135},"# 產生 diff 並存為 migration\n",[129,675,676,678,680,683,686,689],{"class":131,"line":139},[129,677,143],{"class":142},[129,679,191],{"class":146},[129,681,682],{"class":146}," diff",[129,684,685],{"class":218}," --use-migra",[129,687,688],{"class":218}," -f",[129,690,691],{"class":146}," from_gui\n",[129,693,694],{"class":131,"line":156},[129,695,160],{"emptyLinePlaceholder":159},[129,697,698],{"class":131,"line":163},[129,699,700],{"class":135},"# 檢視產生的 SQL\n",[129,702,703,706,709,713],{"class":131,"line":169},[129,704,705],{"class":142},"cat",[129,707,708],{"class":146}," supabase\u002Fmigrations\u002F",[129,710,712],{"class":711},"s_hVV","*",[129,714,715],{"class":146},"_from_gui.sql\n",[129,717,718],{"class":131,"line":175},[129,719,160],{"emptyLinePlaceholder":159},[129,721,722],{"class":131,"line":180},[129,723,724],{"class":135},"# 繼續標準流程\n",[129,726,727,729,731],{"class":131,"line":186},[129,728,143],{"class":142},[129,730,191],{"class":146},[129,732,194],{"class":146},[129,734,735,737,739,741,743],{"class":131,"line":197},[129,736,143],{"class":142},[129,738,191],{"class":146},[129,740,215],{"class":146},[129,742,219],{"class":218},[129,744,222],{"class":146},[35,746],{},[11,748,749],{"id":749},"遠端同步",[41,751,753],{"id":752},"推送-migration","推送 Migration",[46,755,757],{"className":123,"code":756,"language":125,"meta":55,"style":55},"# 手動推送（通常由 CI 執行）\nsupabase db push\n\n# 檢視遠端 migration 狀態\nsupabase migration list --linked\n",[53,758,759,764,773,777,782],{"__ignoreMap":55},[129,760,761],{"class":131,"line":132},[129,762,763],{"class":135},"# 手動推送（通常由 CI 執行）\n",[129,765,766,768,770],{"class":131,"line":139},[129,767,143],{"class":142},[129,769,191],{"class":146},[129,771,772],{"class":146}," push\n",[129,774,775],{"class":131,"line":156},[129,776,160],{"emptyLinePlaceholder":159},[129,778,779],{"class":131,"line":163},[129,780,781],{"class":135},"# 檢視遠端 migration 狀態\n",[129,783,784,786,788,791],{"class":131,"line":169},[129,785,143],{"class":142},[129,787,147],{"class":146},[129,789,790],{"class":146}," list",[129,792,793],{"class":218}," --linked\n",[795,796,799],"callout",{"title":797,"type":798},"Self-hosted 環境","tip",[15,800,801,802,805,806,811],{},"如果使用 Self-hosted Supabase，CI 無法直接執行 ",[53,803,804],{},"supabase db push","。\n請參考 ",[807,808,810],"a",{"href":809},"\u002Fblog\u002Fnuxt\u002Fsupabase-self-hosted","Self-hosted Supabase 部署與遷移"," 了解手動執行 migration 的方式。",[41,813,814],{"id":814},"處理不一致",[46,816,818],{"className":123,"code":817,"language":125,"meta":55,"style":55},"# 標記某個 migration 為已棄用\nsupabase migration repair --status reverted 20240101000000\n\n# 重建遠端（⚠️ 會清除資料）\nsupabase db reset --linked\n",[53,819,820,825,844,848,853],{"__ignoreMap":55},[129,821,822],{"class":131,"line":132},[129,823,824],{"class":135},"# 標記某個 migration 為已棄用\n",[129,826,827,829,831,834,837,840],{"class":131,"line":139},[129,828,143],{"class":142},[129,830,147],{"class":146},[129,832,833],{"class":146}," repair",[129,835,836],{"class":218}," --status",[129,838,839],{"class":146}," reverted",[129,841,843],{"class":842},"srdBf"," 20240101000000\n",[129,845,846],{"class":131,"line":156},[129,847,160],{"emptyLinePlaceholder":159},[129,849,850],{"class":131,"line":163},[129,851,852],{"class":135},"# 重建遠端（⚠️ 會清除資料）\n",[129,854,855,857,859,862],{"class":131,"line":169},[129,856,143],{"class":142},[129,858,191],{"class":146},[129,860,861],{"class":146}," reset",[129,863,793],{"class":218},[35,865],{},[11,867,868],{"id":868},"常用命令速查",[46,870,872],{"className":123,"code":871,"language":125,"meta":55,"style":55},"# 啟動本地 Supabase\nsupabase start\n\n# 停止本地 Supabase\nsupabase stop\n\n# 重置資料庫（套用所有 migration）\nsupabase db reset\n\n# 安全檢查\nsupabase db lint --level warning\n\n# 產生類型\nsupabase gen types typescript --local | tee app\u002Ftypes\u002Fdatabase.types.ts > \u002Fdev\u002Fnull\n\n# 建立新 migration\nsupabase migration new \u003Cdescription>\n\n# 從 GUI 變更產生 diff\nsupabase db diff --use-migra -f \u003Cname>\n\n# 推送到遠端\nsupabase db push\n\n# 檢視遠端狀態\nsupabase migration list --linked\n",[53,873,874,879,886,890,895,902,906,911,919,923,928,940,944,949,971,975,980,1001,1005,1010,1032,1036,1041,1050,1055,1061],{"__ignoreMap":55},[129,875,876],{"class":131,"line":132},[129,877,878],{"class":135},"# 啟動本地 Supabase\n",[129,880,881,883],{"class":131,"line":139},[129,882,143],{"class":142},[129,884,885],{"class":146}," start\n",[129,887,888],{"class":131,"line":156},[129,889,160],{"emptyLinePlaceholder":159},[129,891,892],{"class":131,"line":163},[129,893,894],{"class":135},"# 停止本地 Supabase\n",[129,896,897,899],{"class":131,"line":169},[129,898,143],{"class":142},[129,900,901],{"class":146}," stop\n",[129,903,904],{"class":131,"line":175},[129,905,160],{"emptyLinePlaceholder":159},[129,907,908],{"class":131,"line":180},[129,909,910],{"class":135},"# 重置資料庫（套用所有 migration）\n",[129,912,913,915,917],{"class":131,"line":186},[129,914,143],{"class":142},[129,916,191],{"class":146},[129,918,194],{"class":146},[129,920,921],{"class":131,"line":197},[129,922,160],{"emptyLinePlaceholder":159},[129,924,925],{"class":131,"line":202},[129,926,927],{"class":135},"# 安全檢查\n",[129,929,930,932,934,936,938],{"class":131,"line":208},[129,931,143],{"class":142},[129,933,191],{"class":146},[129,935,215],{"class":146},[129,937,219],{"class":218},[129,939,222],{"class":146},[129,941,942],{"class":131,"line":225},[129,943,160],{"emptyLinePlaceholder":159},[129,945,946],{"class":131,"line":230},[129,947,948],{"class":135},"# 產生類型\n",[129,950,951,953,955,957,959,961,963,965,967,969],{"class":131,"line":236},[129,952,143],{"class":142},[129,954,241],{"class":146},[129,956,244],{"class":146},[129,958,247],{"class":146},[129,960,250],{"class":218},[129,962,254],{"class":253},[129,964,257],{"class":142},[129,966,260],{"class":146},[129,968,263],{"class":253},[129,970,266],{"class":146},[129,972,973],{"class":131,"line":269},[129,974,160],{"emptyLinePlaceholder":159},[129,976,977],{"class":131,"line":274},[129,978,979],{"class":135},"# 建立新 migration\n",[129,981,982,984,986,988,991,994,998],{"class":131,"line":280},[129,983,143],{"class":142},[129,985,147],{"class":146},[129,987,150],{"class":146},[129,989,990],{"class":253}," \u003C",[129,992,993],{"class":146},"descriptio",[129,995,997],{"class":996},"su5hD","n",[129,999,1000],{"class":253},">\n",[129,1002,1003],{"class":131,"line":382},[129,1004,160],{"emptyLinePlaceholder":159},[129,1006,1007],{"class":131,"line":388},[129,1008,1009],{"class":135},"# 從 GUI 變更產生 diff\n",[129,1011,1012,1014,1016,1018,1020,1022,1024,1027,1030],{"class":131,"line":394},[129,1013,143],{"class":142},[129,1015,191],{"class":146},[129,1017,682],{"class":146},[129,1019,685],{"class":218},[129,1021,688],{"class":218},[129,1023,990],{"class":253},[129,1025,1026],{"class":146},"nam",[129,1028,1029],{"class":996},"e",[129,1031,1000],{"class":253},[129,1033,1034],{"class":131,"line":399},[129,1035,160],{"emptyLinePlaceholder":159},[129,1037,1038],{"class":131,"line":405},[129,1039,1040],{"class":135},"# 推送到遠端\n",[129,1042,1044,1046,1048],{"class":131,"line":1043},23,[129,1045,143],{"class":142},[129,1047,191],{"class":146},[129,1049,772],{"class":146},[129,1051,1053],{"class":131,"line":1052},24,[129,1054,160],{"emptyLinePlaceholder":159},[129,1056,1058],{"class":131,"line":1057},25,[129,1059,1060],{"class":135},"# 檢視遠端狀態\n",[129,1062,1064,1066,1068,1070],{"class":131,"line":1063},26,[129,1065,143],{"class":142},[129,1067,147],{"class":146},[129,1069,790],{"class":146},[129,1071,793],{"class":218},[35,1073],{},[11,1075,1076],{"id":1076},"踩坑經驗",[41,1078,1080],{"id":1079},"gui-改-schema-的同步災難","GUI 改 Schema 的同步災難",[15,1082,1083,1086],{},[1084,1085,72],"strong",{},"：在 Supabase Studio 新增欄位，但本地沒有對應的 migration。",[15,1088,1089,1091],{},[1084,1090,75],{},"：",[19,1093,1094,1101,1104],{},[22,1095,1096,1097,1100],{},"本地 ",[53,1098,1099],{},"db reset"," 後欄位消失",[22,1102,1103],{},"其他開發者環境不一致",[22,1105,1106],{},"TypeScript 類型與實際 Schema 不同步",[15,1108,1109,1112],{},[1084,1110,1111],{},"解決","：所有變更都從 Migration 開始，GUI 只用來觀察。",[41,1114,1116],{"id":1115},"sequence-不同步","Sequence 不同步",[15,1118,1119,1121,1122,1125],{},[1084,1120,72],{},"：資料匯入後，新增資料出現 ",[53,1123,1124],{},"duplicate key"," 錯誤。",[15,1127,1128,1131,1132,1135],{},[1084,1129,1130],{},"原因","：使用 ",[53,1133,1134],{},"INSERT ... (id, ...)"," 指定 ID 時，sequence 不會自動更新。",[15,1137,1138,1091],{},[1084,1139,1111],{},[46,1141,1143],{"className":293,"code":1142,"language":295,"meta":55,"style":55},"-- 重設 sequence 為 max(id) + 1\nSELECT setval(\n  'public.users_id_seq',\n  (SELECT COALESCE(MAX(id), 0) + 1 FROM public.users),\n  false\n);\n",[53,1144,1145,1150,1155,1160,1165,1170],{"__ignoreMap":55},[129,1146,1147],{"class":131,"line":132},[129,1148,1149],{},"-- 重設 sequence 為 max(id) + 1\n",[129,1151,1152],{"class":131,"line":139},[129,1153,1154],{},"SELECT setval(\n",[129,1156,1157],{"class":131,"line":156},[129,1158,1159],{},"  'public.users_id_seq',\n",[129,1161,1162],{"class":131,"line":163},[129,1163,1164],{},"  (SELECT COALESCE(MAX(id), 0) + 1 FROM public.users),\n",[129,1166,1167],{"class":131,"line":169},[129,1168,1169],{},"  false\n",[129,1171,1172],{"class":131,"line":175},[129,1173,351],{},[41,1175,1177],{"id":1176},"migration-不一致","Migration 不一致",[15,1179,1180,1091,1182,1185],{},[1084,1181,72],{},[53,1183,1184],{},"schema_migrations"," 表與本地 migration 檔案不一致。",[15,1187,1188,1091],{},[1084,1189,1111],{},[46,1191,1193],{"className":123,"code":1192,"language":125,"meta":55,"style":55},"# 檢視差異\nsupabase migration list --linked\n\n# 標記問題 migration\nsupabase migration repair --status reverted \u003Ctimestamp>\n",[53,1194,1195,1200,1210,1214,1219],{"__ignoreMap":55},[129,1196,1197],{"class":131,"line":132},[129,1198,1199],{"class":135},"# 檢視差異\n",[129,1201,1202,1204,1206,1208],{"class":131,"line":139},[129,1203,143],{"class":142},[129,1205,147],{"class":146},[129,1207,790],{"class":146},[129,1209,793],{"class":218},[129,1211,1212],{"class":131,"line":156},[129,1213,160],{"emptyLinePlaceholder":159},[129,1215,1216],{"class":131,"line":163},[129,1217,1218],{"class":135},"# 標記問題 migration\n",[129,1220,1221,1223,1225,1227,1229,1231,1233,1236,1238],{"class":131,"line":169},[129,1222,143],{"class":142},[129,1224,147],{"class":146},[129,1226,833],{"class":146},[129,1228,836],{"class":218},[129,1230,839],{"class":146},[129,1232,990],{"class":253},[129,1234,1235],{"class":146},"timestam",[129,1237,15],{"class":996},[129,1239,1000],{"class":253},[35,1241],{},[11,1243,1245],{"id":1244},"pre-commit-checklist","Pre-commit Checklist",[15,1247,1248],{},"在提交 Migration 前確認：",[19,1250,1253,1266,1275,1284,1293,1299],{"className":1251},[1252],"contains-task-list",[22,1254,1257,1261,1262,1265],{"className":1255},[1256],"task-list-item",[1258,1259],"input",{"disabled":159,"type":1260},"checkbox"," ",[53,1263,1264],{},"supabase db reset"," 能順利重建",[22,1267,1269,1261,1271,1274],{"className":1268},[1256],[1258,1270],{"disabled":159,"type":1260},[53,1272,1273],{},"supabase db lint --level warning"," 無錯誤",[22,1276,1278,1280,1281],{"className":1277},[1256],[1258,1279],{"disabled":159,"type":1260}," 新增函式皆 ",[53,1282,1283],{},"SET search_path = ''",[22,1285,1287,1289,1290],{"className":1286},[1256],[1258,1288],{"disabled":159,"type":1260}," 新增 View 皆設定 ",[53,1291,1292],{},"security_invoker = true",[22,1294,1296,1298],{"className":1295},[1256],[1258,1297],{"disabled":159,"type":1260}," TypeScript types 已更新",[22,1300,1302,1304],{"className":1301},[1256],[1258,1303],{"disabled":159,"type":1260}," 相關文件已同步更新",[35,1306],{},[11,1308,1309],{"id":1309},"最佳實踐總結",[1311,1312,1313,1319,1325,1334,1340],"ol",{},[22,1314,1315,1318],{},[1084,1316,1317],{},"本地優先","：所有變更先在本地測試",[22,1320,1321,1324],{},[1084,1322,1323],{},"單一主題","：每個 migration 只做一件事",[22,1326,1327,1091,1330,1333],{},[1084,1328,1329],{},"自動化檢查",[53,1331,1332],{},"db lint"," 必須零警告",[22,1335,1336,1339],{},[1084,1337,1338],{},"類型同步","：每次 migration 後重新產生 types",[22,1341,1342,1345],{},[1084,1343,1344],{},"版本控制","：Migration 檔案必須 commit 到 Git",[35,1347],{},[11,1349,1350],{"id":1350},"延伸閱讀",[19,1352,1353,1361,1368,1375,1382],{},[22,1354,1355],{},[807,1356,1360],{"href":1357,"rel":1358},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fcli",[1359],"nofollow","Supabase CLI 文件",[22,1362,1363],{},[807,1364,1367],{"href":1365,"rel":1366},"https:\u002F\u002Fsupabase.com\u002Fdocs\u002Fguides\u002Fcli\u002Flocal-development#database-migrations",[1359],"Supabase Migration 指南",[22,1369,1370,1091,1373],{},[1084,1371,1372],{},"進階",[807,1374,810],{"href":809},[22,1376,1377,1378],{},"上一篇：",[807,1379,1381],{"href":1380},"\u002Fblog\u002Fnuxt\u002Frole-based-access-control","角色權限系統設計",[22,1383,1384,1385],{},"下一篇：",[807,1386,1388],{"href":1387},"\u002Fblog\u002Fnuxt\u002Fsupabase-rls-strategy","RLS 與「讀 Client，寫 Server」策略",[1390,1391,1392],"style",{},"html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .sbgvK, html code.shiki .sbgvK{--shiki-light:#E2931D;--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .stzsN, html code.shiki .stzsN{--shiki-light:#91B859;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s_hVV, html code.shiki .s_hVV{--shiki-light:#90A4AE;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}",{"title":55,"searchDepth":156,"depth":156,"links":1394},[1395,1396,1400,1405,1406,1410,1414,1415,1420,1421,1422],{"id":13,"depth":139,"text":13},{"id":39,"depth":139,"text":39,"children":1397},[1398,1399],{"id":43,"depth":156,"text":44},{"id":58,"depth":156,"text":59},{"id":116,"depth":139,"text":117,"children":1401},[1402,1403,1404],{"id":120,"depth":156,"text":120},{"id":289,"depth":156,"text":290},{"id":411,"depth":156,"text":411},{"id":482,"depth":139,"text":482},{"id":574,"depth":139,"text":575,"children":1407},[1408,1409],{"id":578,"depth":156,"text":579},{"id":659,"depth":156,"text":660},{"id":749,"depth":139,"text":749,"children":1411},[1412,1413],{"id":752,"depth":156,"text":753},{"id":814,"depth":156,"text":814},{"id":868,"depth":139,"text":868},{"id":1076,"depth":139,"text":1076,"children":1416},[1417,1418,1419],{"id":1079,"depth":156,"text":1080},{"id":1115,"depth":156,"text":1116},{"id":1176,"depth":156,"text":1177},{"id":1244,"depth":139,"text":1245},{"id":1309,"depth":139,"text":1309},{"id":1350,"depth":139,"text":1350},"Nuxt","2026-01-22","建立安全可靠的 Supabase Migration 工作流程，從本地開發到遠端同步的完整指南。",false,"md",null,{},"\u002Fblog\u002Fnuxt\u002Fsupabase-local-first",{"title":5,"description":1425},"nuxt-fullstack","Nuxt 4 全棧實戰筆記","blog\u002Fnuxt\u002Fsupabase-local-first\u002Findex",[1423,1436,1437],"Supabase","PostgreSQL","5iA-diFmoGk2xQejTaz8uGhlndGjK_ci3BEQ2VYNrPw",1780512499430]