Supabase Edge Functions Secrets、環境與部署實戰 | Supabase 完整教學

2026/10/21
Supabase Edge Functions Secrets、環境與部署實戰 | Supabase 完整教學

上一篇《Edge Functions 入門》我們寫出並在本機跑通了第一個以 Deno 為底層的無伺服器函式,但它還沒真正上線,金鑰也還躺在本機的 .env 裡。這一篇處理兩件把函式「推上正式環境」必修的事:Secrets 與環境變數——用 supabase secrets set 安全管理正式環境金鑰、用 Deno.env.get() 在函式裡讀取,並認識平台內建自動注入的 SUPABASE_URL、SUPABASE_SERVICE_ROLE_KEY;以及部署——用 supabase functions deploy 把函式送上全球邊緣網路、--no-verify-jwt 做公開端點、import map/deno.json 鎖定套件版本。學完這篇,你的第一個 Edge Function 就能安全地對外服務。

前言

一句話定義本篇主題:Secrets 與部署,是把 Edge Function 從「本機能跑」推進到「正式環境安全對外服務」的最後一哩路——你用 supabase secrets set 把私密金鑰交給正式環境、用 Deno.env.get() 在函式裡讀取、用 supabase functions deploy 把程式碼推上 Supabase 的全球邊緣節點,並在過程中管好 CORS、JWT 驗證與套件版本。

先接住上一篇的脈絡。《Edge Functions 入門》裡我們的 get-weather 函式把 WEATHER_API_KEY 用 Deno.env.get() 讀出來、藏在伺服器端不外洩——這在本機是靠 supabase/functions/.env 檔案餵進去的。但當你要把函式部署到正式環境時,.env 檔案並不會跟著上去(它也絕不該被 commit 進 git),這時就需要一套「正式環境的機密管理機制」,這正是本篇的第一個主角:Secrets。

第二個主角是部署本身。上一篇函式只跑在本機的 supabase functions serve;要讓世界各地的使用者、或 Stripe 這類外部服務打得到它,你得執行 supabase functions deploy 把它推上 Supabase 的全球邊緣網路,並決定它是「需要登入才能呼叫」還是「公開端點」。

用一個生活類比:如果說上一篇是「在自家廚房試做出一道菜」,這一篇就是「把這道菜端上正式營業的餐廳」。Secrets 就是那些只有店家能碰的保險箱鑰匙(付費 API 金鑰)——本機試做時鑰匙放在自家抽屜(.env),正式營業時得交給餐廳的保險箱系統(supabase secrets)妥善保管,服務生(函式)需要時再去保險箱取。而 deploy 就是開店營業這個動作:把菜單與作法正式登記、對外掛牌,客人才點得到。

本篇你會學到:

  • Secrets 管理:supabase secrets set/list/unset 管理正式環境機密、本機 .env 的自動載入、Deno.env.get() 讀取,以及「改 Secret 不必重新 deploy」的關鍵特性
  • 內建環境變數:平台自動注入的 SUPABASE_URL、SUPABASE_SERVICE_ROLE_KEY、SUPABASE_ANON_KEY 等,以及命名規則限制(不能用 SUPABASE_ 前綴)
  • 在函式內建 client:用 service_role key 建立繞過 RLS 的管理員客戶端、用 anon key + 使用者 JWT 建立遵守 RLS 的客戶端,兩者何時該用哪一個
  • 部署與上線:supabase functions deploy、--no-verify-jwt 做公開端點、CORS 設定、import map/deno.json 鎖定套件版本

本篇聚焦「部署與機密」,入門觀念(Deno.serve、functions new、functions.invoke)請見上一篇《Edge Functions 入門》,這裡不再重複。

核心概念

要把 Secrets 與部署做對,先建立三張心智圖:一張是「Secrets 與環境變數的三個來源」,一張是「內建自動注入的變數有哪些、怎麼用」,一張是「從本機到正式環境的部署流程」。

一、環境變數的三個來源

在 Edge Function 裡,你透過 Deno.env.get('KEY') 讀取環境變數。但這些變數其實來自三個不同的地方,搞清楚來源才不會踩雷:

來源怎麼設定本機 vs 正式典型變數
平台內建注入無需設定,自動存在兩者皆有SUPABASE_URL、SUPABASE_SERVICE_ROLE_KEY、SUPABASE_ANON_KEY
本機 .env 檔放進 supabase/functions/.env 或 --env-file 指定只在本機WEATHER_API_KEY、OPENAI_API_KEY(開發用值)
正式環境 Secretssupabase secrets set KEY=val只在正式環境WEATHER_API_KEY、OPENAI_API_KEY(正式值)

關鍵在於同一個 Deno.env.get('OPENAI_API_KEY'),在本機讀到的是 .env 裡的開發用金鑰,在正式環境讀到的是你用 supabase secrets set 設定的正式金鑰。你的程式碼完全不用改——這就是環境變數抽象的好處:程式碼一份,機密隨環境切換。

一張圖看懂:

                你的函式程式碼(一份)
                Deno.env.get('OPENAI_API_KEY')
                          │
          ┌───────────────┴───────────────┐
          ▼                               ▼
   [本機 supabase functions serve]   [正式 supabase functions deploy]
   讀 supabase/functions/.env         讀 supabase secrets(正式機密庫)
   → sk-test-xxxx(開發用)            → sk-live-xxxx(正式用)

二、內建自動注入的環境變數

Supabase 在函式執行環境裡自動注入一批變數,你不必設定就能直接用。最常用的幾個:

變數名稱說明常見用途
SUPABASE_URL專案的 API 閘道位址建立 Supabase 客戶端的第一個參數
SUPABASE_ANON_KEY匿名(可公開)金鑰建立「遵守 RLS」的使用者範圍客戶端
SUPABASE_SERVICE_ROLE_KEY服務角色金鑰(可繞過 RLS)建立「繞過 RLS」的管理員客戶端
SUPABASE_DB_URL直連 PostgreSQL 的連線字串需要原生 SQL/連線池時

兩個重點:

  • 這些變數在本機(supabase start 後)與正式環境(deploy 後)都自動存在,所以你在函式裡寫 Deno.env.get('SUPABASE_URL') 兩邊都能拿到值,不必手動設定。
  • 命名保留:你自訂的 Secrets 不能以 SUPABASE_ 為前綴(這個前綴保留給平台內建變數)。所以你的金鑰要叫 OPENAI_API_KEY、STRIPE_SECRET_KEY,不能叫 SUPABASE_OPENAI_KEY。

SUPABASE_SERVICE_ROLE_KEY 是本篇最需要謹慎對待的變數:它能繞過所有 RLS,等於資料庫的萬能鑰匙。它自動注入在伺服器端的函式環境裡是安全的(前端拿不到),但你絕不能把它回傳給前端、或寫進任何客戶端可見的地方。

三、從本機到正式環境的部署流程

把函式推上線的完整流程,可以濃縮成這張圖:

[1] 本機開發完成
    supabase functions serve(讀 .env 測試)
        │
        ▼
[2] 連結遠端專案(只需一次)
    supabase login
    supabase link --project-ref <PROJECT_ID>
        │
        ▼
[3] 設定正式環境 Secrets
    supabase secrets set OPENAI_API_KEY=sk-live-xxxx
    supabase secrets list(確認名稱已存在)
        │
        ▼
[4] 部署函式
    supabase functions deploy get-weather
    (公開端點加 --no-verify-jwt)
        │
        ▼
[5] 對外可達
    https://<PROJECT_ID>.supabase.co/functions/v1/get-weather

關鍵術語先建立:

  • supabase link:把本機專案「連結」到 Supabase 上的遠端專案(用 Dashboard 取得的 PROJECT_ID),之後的 deploy、secrets set 才知道要操作哪個專案。通常只需做一次。
  • supabase secrets:管理正式環境機密的指令組(set/list/unset),與本機 .env 完全獨立。
  • supabase functions deploy:把函式程式碼打包、上傳、部署到全球邊緣節點。
  • --no-verify-jwt:部署旗標,關閉平台層的 JWT 驗證,讓函式成為公開端點(Webhook、公開 API 必用)。

實作範例

下面用可直接執行的指令與 TypeScript,走一遍「設定 Secrets → 在函式內建 client → 處理 CORS → 部署上線」的完整流程。假設你已完成上一篇的本機開發,並已安裝 Supabase CLI。以下 API 名稱與行為細節請以官方文件為準。

一、本機環境變數:.env 與 Deno.env.get

先看本機。上一篇提到金鑰要用 Deno.env.get() 讀、不寫死。本機開發時,這些值來自 supabase/functions/.env(supabase functions serve 會自動載入這個檔案):

# supabase/functions/.env
# 這些是「本機開發用」的值,絕不 commit 進 git
WEATHER_API_KEY=dev_weather_key_1234
OPENAI_API_KEY=sk-test-abcdefg
STRIPE_SECRET_KEY=sk_test_xxxxxxxx

第一件事:把 .env 加進 .gitignore,這是安全的底線:

# 確保 .env 系列檔案永遠不會被 commit
echo "supabase/functions/.env" >> .gitignore
echo ".env" >> .gitignore
echo ".env.local" >> .gitignore

在函式裡讀取——不論本機或正式環境,程式碼都一樣:

// supabase/functions/get-weather/index.ts
// 在 handler「外部」讀取:模組載入時執行一次即可
const WEATHER_API_KEY = Deno.env.get("WEATHER_API_KEY")

Deno.serve(async (req: Request) => {
  // 防禦性檢查:缺變數時給出明確錯誤,而不是等到 fetch 才神秘失敗
  if (!WEATHER_API_KEY) {
    console.error("WEATHER_API_KEY 未設定")
    return Response.json({ error: "伺服器設定錯誤" }, { status: 500 })
  }

  const { city } = await req.json()
  const apiRes = await fetch(
    `https://api.example-weather.com/v1/current` +
      `?key=${WEATHER_API_KEY}&q=${encodeURIComponent(city)}`,
  )
  const raw = await apiRes.json()
  return Response.json({ city, tempC: raw.current?.temp_c })
})

啟動本機開發伺服器時,.env 會被自動載入;也可以用 --env-file 指定其他檔案:

# 自動載入 supabase/functions/.env
supabase functions serve get-weather

# 或明確指定環境變數檔(例如切換到另一組本機設定)
supabase functions serve get-weather --env-file ./supabase/functions/.env.local

二、正式環境 Secrets:secrets set / list / unset

本機驗證通過後,換設定正式環境的機密。這裡用的是 supabase secrets,與本機 .env 是兩套獨立系統。先確認已連結遠端專案:

# 登入(開瀏覽器授權,只需一次)
supabase login

# 連結到遠端專案(PROJECT_ID 從 Dashboard → Project Settings 取得)
supabase link --project-ref your-project-id

接著設定 Secrets:

# 設定單一 Secret(KEY=value 格式)
supabase secrets set OPENAI_API_KEY=sk-live-realproductionkey

# 一次設定多個
supabase secrets set STRIPE_SECRET_KEY=sk_live_xxx WEATHER_API_KEY=prod_weather_key

# 從 .env 檔批次匯入(注意:這是正式值的檔案,同樣不該進 git)
supabase secrets set --env-file ./.env.production

# 列出所有已設定的 Secret「名稱」(基於安全,不會顯示值)
supabase secrets list

# 刪除某個 Secret
supabase secrets unset WEATHER_API_KEY

supabase secrets list 的輸出只會列名稱與一段 digest,不會印出實際的值——這是刻意的安全設計,避免金鑰在終端機或 CI 日誌裡外洩。

一個容易被忽略、但很好用的特性:設定或更新 Secret 後,通常不需要重新 deploy 函式,變數就會對之後的請求生效。因為 Deno.env.get() 讀的是「執行當下環境裡的值」,不是「部署當下打包進去的值」。所以你可以事後輪替金鑰而完全不動程式碼。

三、在函式內建立 Supabase 客戶端:service_role vs anon

這是本篇最核心的實作。在 Edge Function 裡,你常需要讀寫自己的 Postgres 資料庫。因為平台自動注入了 SUPABASE_URL 與各種 key,你可以直接用它們建立客戶端。關鍵是選對 key,這決定了「要不要遵守 RLS」。

情境 A:管理員客戶端(用 service_role key,繞過 RLS)

適用於明確需要跨使用者權限的後台作業,例如排程彙總、Webhook 寫入:

// supabase/functions/admin-report/index.ts
import { createClient } from "npm:@supabase/supabase-js@2"

// 用 service_role key 建立管理員客戶端 → 繞過所有 RLS
// 放在 handler 外部,只建立一次
const supabaseAdmin = createClient(
  Deno.env.get("SUPABASE_URL")!, // 內建注入,不必自己設
  Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!, // 內建注入,可繞過 RLS
  {
    auth: {
      autoRefreshToken: false, // 伺服器端不需要自動更新 token
      persistSession: false, // 也不需要保存 session
    },
  },
)

Deno.serve(async (_req: Request) => {
  // 這個查詢會「看到所有使用者的資料」,因為 service_role 繞過 RLS
  const { data, error } = await supabaseAdmin
    .from("orders")
    .select("id, user_id, amount")
    .gte("created_at", new Date(Date.now() - 86400000).toISOString())

  if (error) {
    return Response.json({ error: error.message }, { status: 500 })
  }
  return Response.json({ count: data.length, orders: data })
})

情境 B:使用者範圍客戶端(用 anon key + 使用者 JWT,遵守 RLS)

適用於「代表某位登入使用者操作」,讓 RLS 自動把資料限縮在該使用者權限內:

// supabase/functions/my-documents/index.ts
import { createClient } from "npm:@supabase/supabase-js@2"

Deno.serve(async (req: Request) => {
  // 從請求取出呼叫者的 JWT(supabase.functions.invoke 會自動帶上)
  const authHeader = req.headers.get("Authorization")
  if (!authHeader) {
    return Response.json({ error: "Unauthorized" }, { status: 401 })
  }

  // 用 anon key 建立客戶端,並把呼叫者的 Authorization 標頭轉傳進去
  // → 查詢會以「該登入使用者」身分執行,RLS 完整生效
  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_ANON_KEY")!, // 注意:是 anon key 不是 service_role
    {
      global: { headers: { Authorization: authHeader } },
      auth: { autoRefreshToken: false, persistSession: false },
    },
  )

  // RLS 生效:只會回傳「這位使用者有權限看到」的 documents
  const { data, error } = await supabase.from("documents").select("*")
  if (error) {
    return Response.json({ error: error.message }, { status: 400 })
  }
  return Response.json({ documents: data })
})

一句話記住兩者的分野:要遵守呼叫者權限就用 anon key + 使用者 JWT(RLS 生效);明確要管理員權限跨使用者操作才用 service_role key(繞過 RLS)。 這個選擇攸關資料安全,下一節會再強調它的坑。

四、CORS:部署前務必確認

上一篇談過 CORS 的原理,這裡補上部署前的檢查點。只要函式會被瀏覽器呼叫,就必須處理 OPTIONS preflight 並在每個回應帶上 CORS 標頭,否則部署後從網頁呼叫就會被擋。把 CORS 標頭抽成共用模組:

// supabase/functions/_shared/cors.ts
// 以 _ 開頭的資料夾不會被部署為獨立函式,適合放共用模組
export const corsHeaders = {
  "Access-Control-Allow-Origin": "*", // 正式環境建議改成你的具體網域
  "Access-Control-Allow-Headers":
    "authorization, x-client-info, apikey, content-type",
  "Access-Control-Allow-Methods": "POST, OPTIONS",
}

在函式裡套用(成功、錯誤、preflight 三處都要帶):

// supabase/functions/my-documents/index.ts(加上 CORS)
import { createClient } from "npm:@supabase/supabase-js@2"
import { corsHeaders } from "../_shared/cors.ts"

Deno.serve(async (req: Request) => {
  // 1) 先回應瀏覽器的 preflight
  if (req.method === "OPTIONS") {
    return new Response("ok", { headers: corsHeaders })
  }

  const authHeader = req.headers.get("Authorization")
  if (!authHeader) {
    // 2) 錯誤回應也要帶 CORS 標頭
    return Response.json(
      { error: "Unauthorized" },
      { status: 401, headers: corsHeaders },
    )
  }

  const supabase = createClient(
    Deno.env.get("SUPABASE_URL")!,
    Deno.env.get("SUPABASE_ANON_KEY")!,
    { global: { headers: { Authorization: authHeader } } },
  )

  const { data, error } = await supabase.from("documents").select("*")
  // 3) 成功回應同樣帶上 CORS 標頭
  return Response.json(
    error ? { error: error.message } : { documents: data },
    { status: error ? 400 : 200, headers: corsHeaders },
  )
})

五、部署上線:functions deploy 與 –no-verify-jwt

一切就緒,執行部署。supabase functions deploy 會把函式打包上傳到全球邊緣節點:

# 部署單一函式
supabase functions deploy my-documents

# 部署所有函式(一次上傳 supabase/functions/ 下全部)
supabase functions deploy

# 部署為「公開端點」:關閉平台層 JWT 驗證(Webhook、公開 API 用)
supabase functions deploy stripe-webhook --no-verify-jwt

部署完成後,函式的正式 URL 是:

https://<PROJECT_ID>.supabase.co/functions/v1/<function-name>

關於 --no-verify-jwt 的關鍵理解:Edge Function 預設要求請求帶有效 JWT(平台層在函式執行前就會擋掉沒帶 token 的請求)。這對「只給登入使用者呼叫」的函式是好事;但對 Stripe Webhook 這種「由外部服務主動打進來、根本沒有使用者 JWT」的端點,你就必須用 --no-verify-jwt 關掉這道驗證,改在函式內部自己驗證(例如驗 Stripe 簽章)。除了旗標,也可以在 supabase/config.toml 針對個別函式設定:

# supabase/config.toml
[functions.stripe-webhook]
verify_jwt = false   # 等同 --no-verify-jwt,但寫在設定檔更明確、可版控

[functions.my-documents]
verify_jwt = true    # 預設值,需要登入才能呼叫

六、鎖定套件版本:import map 與 deno.json

最後一個部署品質關卡:版本鎖定。上一篇我們用 npm:@supabase/supabase-js@2 這種寫法匯入套件,@2 是「2.x 最新」的浮動版本——正式環境為了可重現、避免某天套件更新突然壞掉,建議把版本釘死。Deno 現在推薦用 deno.json 的 imports 欄位集中管理:

// supabase/functions/deno.json
{
  "imports": {
    "@supabase/supabase-js": "npm:@supabase/supabase-js@2.49.0",
    "zod": "npm:zod@3.23.8",
    "hono": "jsr:@hono/hono@4.6.0"
  }
}

有了它,函式裡就能用「裸名稱」匯入,版本統一由 deno.json 控制:

// 不再寫死版本號,改用 deno.json 對應的裸名稱
import { createClient } from "@supabase/supabase-js"
import { z } from "zod"

部署時,若你的映射檔不叫 deno.json(例如沿用舊的 import_map.json),可用旗標明確指定:

# 用 deno.json(放在函式資料夾或 supabase/functions/ 下)通常會自動偵測
supabase functions deploy my-documents

# 或明確指定 import map 檔
supabase functions deploy my-documents --import-map ./supabase/functions/import_map.json

版本鎖定的好處是可重現的部署:今天 deploy 和三個月後 deploy 用的是同一套套件版本,不會因為上游套件的破壞性更新而在正式環境突然出包。

常見錯誤與最佳實踐

坑一:把 service_role client 當 anon client 用來處理一般使用者請求。 這是本篇最危險的坑。用 SUPABASE_SERVICE_ROLE_KEY 建立的客戶端繞過所有 RLS,如果你拿它來處理「代表某位使用者」的查詢,卻忘了手動加 .eq('user_id', user.id) 這類過濾,使用者就能讀到別人的資料——你等於親手廢掉 RLS 這道防線。正確做法:處理使用者請求時,用 SUPABASE_ANON_KEY + 呼叫者的 JWT(global.headers.Authorization)建立客戶端,讓 RLS 自動限縮權限;只有在明確需要跨使用者的管理員作業(排程、Webhook、後台)才用 service_role,而且務必想清楚「這真的需要繞過 RLS 嗎」。

坑二:把 .env(或正式金鑰)commit 進 git。 本機的 supabase/functions/.env、批次匯入用的 .env.production 一旦被 commit,金鑰就永久留在 git 歷史裡,即使之後刪除也已外洩。正確做法:第一時間把 .env、.env.local、.env.production、supabase/functions/.env 全加進 .gitignore;正式金鑰只透過 supabase secrets set 進入正式環境,本機用開發用的假金鑰或測試金鑰;若不慎 commit 過,除了刪檔還要立刻輪替該金鑰(改 Secret 不必重新 deploy,成本很低)。

坑三:以為 .env 會跟著 deploy 上正式環境。 本機 .env 只在 supabase functions serve 時載入,不會隨 supabase functions deploy 上去;部署後函式讀不到你以為存在的變數,Deno.env.get() 回傳 undefined,於是在正式環境神秘失敗。正確做法:正式環境的機密一律用 supabase secrets set 設定,部署後用 supabase secrets list 確認名稱都在;在函式裡對關鍵變數做防禦性檢查(缺少時回明確的 500 而非讓後續 fetch 爆掉),並到 Dashboard 函式日誌確認。

坑四:CORS 沒處理,部署後從瀏覽器呼叫全被擋。 本機用 curl 測都正常,一放到網頁就 CORS error——因為沒回應 OPTIONS preflight,或回應沒帶 Access-Control-Allow-* 標頭。「curl 通、瀏覽器不通」是典型症狀。正確做法:只要函式會被瀏覽器直接呼叫,就在 handler 開頭處理 req.method === 'OPTIONS' 回帶 CORS 標頭的回應,並讓成功與錯誤的每個回應都帶上同一組標頭(抽成 _shared/cors.ts);正式環境把 Access-Control-Allow-Origin 從 * 收斂成你的具體網域。

坑五:公開端點忘了 --no-verify-jwt,Webhook 一直被平台擋在門外。 Edge Function 預設要求有效 JWT,Stripe/GitHub 這類 Webhook 由外部服務主動打進來、根本帶不出使用者 token,於是被平台層直接擋掉,你在函式日誌裡連請求都看不到。正確做法:公開端點(Webhook、免登入 API)部署時加 --no-verify-jwt,或在 config.toml 設 verify_jwt = false;關掉平台驗證後,務必在函式內部自己做驗證(如驗 Stripe 簽章、檢查密鑰),別讓端點裸奔。

坑六:套件版本用浮動 tag(@2、@latest),正式環境某天突然壞掉。 npm:@supabase/supabase-js@2 會抓 2.x 最新版,上游某次更新引入破壞性變更時,你的函式會在沒改任何程式碼的情況下出包,且難以重現。正確做法:正式環境用 deno.json 的 imports 把版本釘到精確號(如 @2.49.0),追求可重現的部署;升級時明確改版本號、在本機驗過再 deploy。

最佳實踐總結:

  • 選對 key:使用者請求用 anon + JWT(RLS 生效);管理員作業才用 service_role(繞過 RLS),且絕不外洩
  • .env 一律進 .gitignore:正式機密只走 supabase secrets set,不慎外洩立刻輪替
  • 分清環境:本機讀 .env、正式讀 Secrets,同一份程式碼靠 Deno.env.get() 抽象切換
  • 善用「改 Secret 不必重 deploy」:金鑰輪替成本很低,程式碼一份到底
  • CORS 三處都帶:preflight、成功、錯誤回應都帶 CORS 標頭,正式環境收斂 origin
  • 公開端點顯式 --no-verify-jwt:關平台驗證後在函式內自己驗
  • 版本釘死:deno.json 精確版本號,換取可重現的正式部署
  • 不確定的 API 以官方為準:supabase secrets、functions deploy、旗標與內建變數名稱都可能調整,正式決策前查官方文件

小結

這是 Supabase 系列教學的第 032 篇,緊接 SB-4 運算與 AI 的開篇。上一篇《Edge Functions 入門》我們認識了以 Deno 為底層的 Edge Functions,寫出並在本機跑通第一個函式;這一篇補上把它推上正式環境必修的兩件事——Secrets 管理與部署。

核心觀念濃縮成一句話:同一份函式程式碼靠 Deno.env.get() 讀環境變數,本機值來自 .env、正式值來自 supabase secrets set(改 Secret 不必重新 deploy);函式內建 Supabase 客戶端時,處理使用者請求用 anon key + JWT 讓 RLS 生效、管理員作業才用可繞過 RLS 的 service_role key;用 supabase functions deploy 把函式送上全球邊緣網路,公開端點加 --no-verify-jwt,並用 deno.json 釘死套件版本、記得處理 CORS。

  • 三個變數來源:平台內建注入(SUPABASE_URL/SERVICE_ROLE_KEY)、本機 .env、正式 supabase secrets
  • 建 client 選 key:anon + 使用者 JWT(遵守 RLS)vs service_role(繞過 RLS)
  • 部署三件事:functions deploy 上線、--no-verify-jwt 做公開端點、deno.json 鎖版本
  • 安全底線:.env 進 .gitignore、service_role 絕不外洩、CORS 每個回應都帶

你的第一個 Edge Function 現在能安全地對外服務了。但很多真實需求是「回應使用者之後、還要在背景繼續做事」(寄信、寫日誌),或「不靠使用者觸發、而是定時自動執行」(每日報表、清理過期資料)。下一篇《Background Tasks 與 Cron》,我們就來處理這兩類需求:用 EdgeRuntime.waitUntil 做回應後的背景任務,用 pg_cron + pg_net 讓資料庫定時呼叫你的函式。運算篇章繼續前進,下一篇見。

BenZ Software Developer

熱愛技術的軟體開發者,在這裡分享程式開發經驗與學習筆記。

本週主打

AI 自動化入門包

你每天手動在做的那些煩事,其實 AI 可以自己跑。這份給你 10 個照著做就會的自動化工作流 + 50 個複製即用的提示詞,不用會寫程式。

看看這個產品 →