Supabase Edge Functions 実践:Deno ランタイムとグローバルエッジデプロイ
監視ダッシュボードの赤いライン——API レスポンスが 2.3 秒まで跳ね上がっていた。
ユーザーは日本、サーバーは米国オハイオ。往復だけで 120ms、そこに DB クエリとビジネスロジックが乗れば、遅くて当然だ。
Slack で同僚から「エッジ関数、試してみる?」。
正直、懐疑的だった。実行場所を変えるだけで、そんなに差が出る? 測ってみて驚いた。コールドスタート 120ms、レスポンス 47ms。同じロジックをオハイオから東京のエッジへ移しただけで、約 5 倍速かった。
Supabase Edge Functions は Deno 上でグローバルエッジにコードを載せる仕組みです。本記事では、なぜ速いのか、Deno と Node.js の違い、ゼロからの構築手順、Cloudflare Workers との選び方を整理します。
Edge Functions とは何か、なぜ速いのか
要するに、コードを「中心サーバー」から「エッジノード」へ移すだけです。
従来の Lambda は固定リージョン(米東、欧州など)で動きます。東京のユーザーからのリクエストは太平洋を渡って処理され、また戻る。物理距離がレイテンシの下限になる——光速にも限界がある。
Edge Functions は発想が違います。コードは ESZip というコンパクト形式にまとめられ、数十のエッジへ自動配布。東京から来たリクエストは東京のノードで完結し、大洋横断分が丸ごと消えます。
ESZip とは
Deno チームが作ったパッケージ形式です。通常の JS バンドルと違い、依存グラフごと 1 ファイルに封じ込みます。起動時にネットからモジュールを取りに行く必要がなく、「取得 + 解析 + 実行」が「解凍 + 実行」に短縮されます。
Isolate 実行モデル
Supabase Edge Functions は V8 Isolate 上で動き、コンテナや VM ではありません。超軽量な Worker コンテナと考えれば十分。1 プロセスに数十 Isolate、1 リクエスト 1 Isolate。コンテナより軽く、起動も速い。
公式ドキュメントによると CPU 時間上限は 400 秒(ソフト + ハード)。多く聞こえますが、エッジ関数は JWT 検証や転送など軽い仕事向き。重い計算はセンター側に回すのが定石です。
最小例を見てみましょう。
// 最小の Edge Function 例
import "jsr:@supabase/functions-js/edge-runtime.d.ts"
Deno.serve(async (req) => {
const { name } = await req.json()
const data = { message: `Hello ${name}!` }
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" }
})
})
ユーザーに最も近いノードで Deno.serve が受け取り、処理して返す——それだけです。
Deno ランタイム:Node.js の「安全な進化版」
Deno を初めて聞いたとき、「Node.js があるのに、なぜ?」と思った方も多いはず。
答えはシンプル。Node.js は設計が早すぎて、後から問題が露わになった、という側面があります。
セキュリティの違い
Node.js はデフォルトで「全部信頼」。ファイル読み、ネット接続、シェル実行——悪意ある依存 1 つで何でもできます。
Deno は逆。デフォルトはサンドボックス内。ファイルもネットもシェルも不可。必要な権限だけ明示します。
# ネットワーク権限
deno run --allow-net server.ts
# ファイル読み書き
deno run --allow-read --allow-write file_ops.ts
# 全権限(慎重に)
deno run -A everything.ts
グローバル数十ノードで動くエッジでは、この設計が効きます。
コールドスタートの差
同じコードを Deno Deploy と AWS Lambda で測った結果です。
| ランタイム | コールドスタート |
|---|---|
| Deno Deploy | ~120ms |
| AWS Lambda (Node.js) | 300-500ms |
約 3 倍の差。CommonJS の require、node_modules 探索、ESZip 前の依存解決——Deno はこのあたりの起動コストをかなり削っています。
モジュールシステム
Node.js は CommonJS + npm + package.json + node_modules。大きなプロジェクトでは node_modules だけで数百 MB も珍しくありません。
Deno は ESM をそのまま使い、URL で import します。
// URL から直接
import { serve } from "https://deno.land/std@0.168.0/http/server.ts"
// JSR(Deno のパッケージレジストリ)
import { cors } from "jsr:@hono/hono/cors"
初回だけダウンロードしてキャッシュ、以降はローカルから読み込み。
TypeScript をそのまま
Node.js なら ts-node や bundler、tsconfig が必要。Deno は .ts をそのまま実行できます。
deno run hello.ts
コンパイルも型チェックもランタイム内。体験がかなりすっきりします。
ベンダーロックインは?
Supabase Edge Functions を使うと縛られるのでは、という不安もわかります。
Deno も Supabase Edge Runtime も OSS です(GitHub にソースあり)。自前サーバーで Edge Runtime を動かすことも可能。グローバルエッジの運用コストは別問題ですが、「コードごと逃げられない」わけではありません。
実践:10 分で最初の Edge Function
理論だけでは実感が湧きません。公式手順どおり、インストールからデプロイまで 10 分以内で終わりました。
ステップ 1:Supabase CLI
npm install -g supabase
supabase --version
# 出力例:1.200.0
ステップ 2:プロジェクト初期化
supabase init
supabase/ と config.toml ができます。
ステップ 3:Edge Function 作成
supabase functions new hello-world
supabase/functions/hello-world/index.ts が生成されます。
import "jsr:@supabase/functions-js/edge-runtime.d.ts"
Deno.serve(async (req) => {
const data = {
message: "Hello from Edge Function!"
}
return new Response(JSON.stringify(data), {
headers: { "Content-Type": "application/json" }
})
})
name を受け取って挨拶を返す例:
import "jsr:@supabase/functions-js/edge-runtime.d.ts"
Deno.serve(async (req) => {
// POST のみ受け付け
if (req.method !== "POST") {
return new Response("Method not allowed", { status: 405 })
}
try {
const body = await req.json()
const name = body.name || "Stranger"
return new Response(JSON.stringify({
message: `Hey ${name}, welcome to the edge!`,
timestamp: new Date().toISOString()
}), {
headers: { "Content-Type": "application/json" }
})
} catch (err) {
return new Response(JSON.stringify({ error: "Invalid JSON" }), {
status: 400,
headers: { "Content-Type": "application/json" }
})
}
})
ステップ 4:ローカルテスト
supabase functions serve --no-verify-jwt
ポート 54321 で起動。curl で確認:
curl -X POST http://localhost:54321/functions/v1/hello-world \
-H "Content-Type: application/json" \
-d '{"name":"Easton"}'
# 返却例:
# {"message":"Hey Easton, welcome to the edge!","timestamp":"2026-05-03T14:30:00.000Z"}
--no-verify-jwt はローカル専用。本番では JWT 検証をオンに。
ステップ 5:グローバルデプロイ
supabase login
supabase link --project-ref <your-project-id>
supabase functions deploy hello-world
デプロイ後、関数はグローバルエッジへ配布されます。Dashboard で URL とログを確認。
ステップ 6:本番呼び出し
https://<project-id>.supabase.co/functions/v1/hello-world
curl -X POST https://<project-id>.supabase.co/functions/v1/hello-world \
-H "Authorization: Bearer <anon-key>" \
-H "Content-Type: application/json" \
-d '{"name":"World"}'
Authorization に anon key またはユーザー JWT が必要です。
デモ以外の用途も多いです。
Webhook 処理
Stripe の支払い成功 Webhook を Edge Function で受け、DB に書き込む例:
// supabase/functions/stripe-webhook/index.ts
import "jsr:@supabase/functions-js/edge-runtime.d.ts"
import { createClient } from "jsr:@supabase/supabase-js@2"
Deno.serve(async (req) => {
// Stripe 署名検証(本番では hmac 必須)
const event = await req.json()
const supabase = createClient(
Deno.env.get("SUPABASE_URL")!,
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!
)
if (event.type === "payment_intent.succeeded") {
const payment = event.data.object
await supabase.from("payments").insert({
id: payment.id,
amount: payment.amount,
customer_id: payment.customer,
created_at: new Date().toISOString()
})
}
return new Response(JSON.stringify({ received: true }), {
headers: { "Content-Type": "application/json" }
})
})
API プロキシ
API Key をフロントに出したくないとき、Edge Function を中継に使えます。
// supabase/functions/openai-proxy/index.ts
Deno.serve(async (req) => {
const body = await req.json()
const response = await fetch("https://api.openai.com/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${Deno.env.get("OPENAI_API_KEY")}`,
"Content-Type": "application/json"
},
body: JSON.stringify(body)
})
return new Response(response.body, {
headers: { "Content-Type": "application/json" }
})
})
フロントは Edge Function だけ叩き、Key はエッジ側に閉じ込められます。
選び方:Supabase vs Cloudflare Workers
「どちらが上か?」と聞かれがちですが、直接の競合ではありません。用途で分かれます。
パフォーマンス
コールドスタートは Cloudflare Workers が ~30ms、Supabase Edge Functions が ~120ms 程度。
Cloudflare の runtime は自社製でエッジ特化。Supabase は Deno ベースで、TypeScript コンパイルの 1 レイヤ分だけ重くなります。
ただし多くの Web アプリでは 120ms と 30ms の差は体感しにくい。ネットワーク揺らぎの方が大きい。高頻度取引やリアルタイムオークションなら Workers 向きです。
統合性
Supabase の強みはここ。同一プロジェクトの Postgres、Auth、Storage にそのまま触れ、環境変数も自動注入。
Workers は DB 接続を自前で組む必要があります。D1 や外部 DB も可能ですが、設定工数は増えます。
ベンダーロックイン
Workers の runtime はクローズドソース。Cloudflare 専用の書き方になりがちです。
Edge Functions は Deno ベース。同じコードを Deno Deploy や自ホスト Edge Runtime へ移す選択肢があります。Supabase のエッジ網そのものは移せませんが、ランタイム層では自由度が高いです。
エコシステム
Workers は 2017 年から、Hono や Remix など実績が厚い。
Edge Functions は 2022 年登場でまだ成長期。ただし Deno 標準なので、Deno 向けライブラリはそのまま使えます。
判断の目安
| 状況 | おすすめ |
|---|---|
| Supabase(Auth / DB / Storage)利用中 | Supabase Edge Functions |
| DB 不要の純エッジ処理 | Cloudflare Workers |
| ロックインが不安 | Supabase Edge Functions(Deno OSS) |
| コールドスタート ~50ms 未満を狙う | Cloudflare Workers |
| エッジから Postgres 直結 | Supabase Edge Functions |
私は Workers でリバースプロキシとキャッシュ、Edge Functions で Auth と DB 処理、と役割分担しています。
落とし穴とベストプラクティス
落とし穴 1:Node.js パッケージを使った
import axios from "axios" と書いてデプロイ——即モジュール未検出。
Deno なので npm はそのまま不可。fetch で足りることが多いです。
// axios は使わない
// import axios from "axios" // エラーになる
// fetch を使う
const response = await fetch("https://api.example.com/data", {
method: "GET",
headers: { "Authorization": "Bearer xxx" }
})
const data = await response.json()
axios 相当なら Deno 向けの ky などを検討。
落とし穴 2:長時間実行で強制終了
数千件をループするバッチを Edge に載せ、30 秒で切断——CPU 時間上限(400 秒)に抵触した典型例。
検証・転送・軽い計算はエッジ、重いバッチはセンターか専用 Worker へ。
落とし穴 3:DB 接続の持ち方
エッジ各ノードが長接続を張ると、Postgres の接続プールがすぐ溢れます。
Supabase Pooler(SUPABASE_DB_URL)を使い、リクエストごとに借りて release()。
import { Pool } from "https://deno.land/x/postgres@v0.17.0/mod.ts"
const pool = new Pool(Deno.env.get("SUPABASE_DB_URL")!, 10)
Deno.serve(async (req) => {
const client = await pool.connect()
try {
const result = await client.queryArray("SELECT * FROM users LIMIT 10")
return new Response(JSON.stringify(result.rows))
} finally {
client.release() // 忘れずに
}
})
落とし穴 4:JWT 検証を忘れた
ローカルは --no-verify-jwt、本番で有効化し忘れ——誰でも呼べる状態に。
[functions.hello-world]
verify_jwt = true
または supabase functions deploy hello-world --verify-jwt。
便利なテクニック
- Hono でルーティング
13KB 級の軽量フレームワーク。Express より Edge 向きです。
import { Hono } from "jsr:@hono/hono"
import { cors } from "jsr:@hono/hono/cors"
const app = new Hono()
app.use("*", cors())
app.get("/health", (c) => c.json({ status: "ok" }))
app.post("/echo", async (c) => {
const body = await c.req.json()
return c.json({ echo: body })
})
Deno.serve(app.fetch)
- 環境変数
Main Runtime(Supabase 管理)と User Runtime(あなたのコード)で権限が異なります。
SUPABASE_URL、SUPABASE_ANON_KEY、SUPABASE_SERVICE_ROLE_KEYは自動注入- カスタムは
supabase secrets set MY_VAR=value
- Import Map
依存 URL の解決をまとめ、リクエストごとのオーバーヘッドを減らせます。
// deno.json または import_map.json
{
"imports": {
"hono": "jsr:@hono/hono",
"supabase-js": "jsr:@supabase/supabase-js@2"
}
}
import { Hono } from "hono"
import { createClient } from "supabase-js"
まとめ
Supabase Edge Functions は、コードをユーザーに最も近いノードへ載せ、安全な Deno 上で 120ms 級のコールドスタートを実現する仕組みです。
すでに Supabase(Auth / DB / Storage)を使っているなら、自然な次の一手。接続や認証の配線が少なく済みます。Deno と Edge Runtime は OSS なので、ランタイム層では移行の余地も残ります。
次は Dashboard の Quickstart を 10 分走らせてみてください。最初の Edge Function がグローバルエッジで動き始めます。
同シリーズの関連記事:
- Supabase 入門ガイド:PostgreSQL + Auth + Storage でオールインワン バックエンド——全体像の把握
- Supabase Auth 実践ガイド:メール認証、OAuth、セッション管理——認証の実装
- Supabase Auth 高度な設定:OAuth、SSO、権限制御の完全ガイド——進階設定
Cloudflare Workers 側も気になる方は Cloudflare Workers 実践ガイド をどうぞ。用途に合わせて、または併用して選べます。
初めての Supabase Edge Function をデプロイ
ゼロから Edge Function を作成・テストし、グローバルエッジネットワークへデプロイする
⏱️ 目安時間: 10 分
- 1
ステップ1: Supabase CLI をインストール
Supabase CLI をグローバルにインストール:
```bash
npm install -g supabase
```
インストール後、`supabase --version` で確認。 - 2
ステップ2: プロジェクトを初期化
プロジェクトディレクトリで初期化:
```bash
supabase init
```
`supabase` ディレクトリと `config.toml` が作成されます。 - 3
ステップ3: Edge Function を作成
CLI で新規関数を作成:
```bash
supabase functions new hello-world
```
`supabase/functions/hello-world/index.ts` が生成され、デフォルトで JSON を返します。 - 4
ステップ4: ローカルテスト
ローカル開発サーバーを起動:
```bash
supabase functions serve --no-verify-jwt
```
デフォルトポートは 54321。curl または Postman でテスト:
```bash
curl -X POST http://localhost:54321/functions/v1/hello-world \
-H "Content-Type: application/json" \
-d '{"name":"Easton"}'
``` - 5
ステップ5: グローバルエッジネットワークへデプロイ
ログインとプロジェクトリンク後にデプロイ:
```bash
supabase login
supabase link --project-ref <your-project-id>
supabase functions deploy hello-world
```
Dashboard で関数 URL とログを確認できます。 - 6
ステップ6: 呼び出しテスト
関数 URL と認証トークンで呼び出し:
```bash
curl -X POST https://<project-id>.supabase.co/functions/v1/hello-world \
-H "Authorization: Bearer <anon-key>" \
-H "Content-Type: application/json" \
-d '{"name":"World"}'
```
本番では JWT 検証を有効化:`supabase functions deploy hello-world --verify-jwt`
FAQ
Supabase Edge Functions と AWS Lambda の違いは?
Edge Functions で npm パッケージは使える?
Edge Functions に実行時間制限はある?
Edge Function から Postgres に接続するには?
• 環境変数 `SUPABASE_DB_URL`(Pooler 向き)
• `postgres` パッケージでプール作成
• リクエスト後に `client.release()`
エッジノード数が多いため、長時間接続は避けてください。
Supabase Edge Functions と Cloudflare Workers、どう選ぶ?
• Supabase フルスタック利用中 → Edge Functions(深い統合)
• バックエンド不要の純エッジ → Cloudflare Workers(コールドスタート ~30ms)
• ベンダーロックインが不安 → Edge Functions(Deno は OSS、自ホスト可)
• エッジから Postgres 直結 → Edge Functions
併用も有効:Workers でプロキシ・キャッシュ、Edge Functions で Auth と DB 処理。
JWT 検証で Edge Function を保護するには?
• `config.toml` に `[functions.hello-world] verify_jwt = true`
• デプロイ時に `--verify-jwt`
ローカルは `--no-verify-jwt` でよいですが、本番では必ず有効化。未設定だと誰でも呼び出せます。
4分で読めます · 公開日: 2026年5月3日 · 更新日: 2026年6月8日
関連記事
Supabase 入門:PostgreSQL + Auth + Storage のオールインワンバックエンド
Supabase 入門:PostgreSQL + Auth + Storage のオールインワンバックエンド
Supabase データベース設計:テーブル構造・リレーション・Row Level Security 完全ガイド
Supabase データベース設計:テーブル構造・リレーション・Row Level Security 完全ガイド
Supabase Auth 実践:メール認証・OAuth・セッション管理
コメント
GitHubアカウントでログインしてコメントできます