言語を切り替える
テーマを切り替える

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


便利なテクニック

  1. 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)
  1. 環境変数

Main Runtime(Supabase 管理)と User Runtime(あなたのコード)で権限が異なります。

  • SUPABASE_URLSUPABASE_ANON_KEYSUPABASE_SERVICE_ROLE_KEY は自動注入
  • カスタムは supabase secrets set MY_VAR=value
  1. 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 がグローバルエッジで動き始めます。

同シリーズの関連記事:

Cloudflare Workers 側も気になる方は Cloudflare Workers 実践ガイド をどうぞ。用途に合わせて、または併用して選べます。

初めての Supabase Edge Function をデプロイ

ゼロから Edge Function を作成・テストし、グローバルエッジネットワークへデプロイする

⏱️ 目安時間: 10 分

  1. 1

    ステップ1: Supabase CLI をインストール

    Supabase CLI をグローバルにインストール:

    ```bash
    npm install -g supabase
    ```

    インストール後、`supabase --version` で確認。
  2. 2

    ステップ2: プロジェクトを初期化

    プロジェクトディレクトリで初期化:

    ```bash
    supabase init
    ```

    `supabase` ディレクトリと `config.toml` が作成されます。
  3. 3

    ステップ3: Edge Function を作成

    CLI で新規関数を作成:

    ```bash
    supabase functions new hello-world
    ```

    `supabase/functions/hello-world/index.ts` が生成され、デフォルトで JSON を返します。
  4. 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

    ステップ5: グローバルエッジネットワークへデプロイ

    ログインとプロジェクトリンク後にデプロイ:

    ```bash
    supabase login
    supabase link --project-ref <your-project-id>
    supabase functions deploy hello-world
    ```

    Dashboard で関数 URL とログを確認できます。
  6. 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 は約 120ms、AWS Lambda(Node.js)は 300〜500ms。Edge Functions はグローバルエッジでユーザーに近い場所で動き、Lambda は固定リージョンです。Edge Functions の CPU 上限は 400 秒、Lambda は最大 15 分まで実行できますが、エッジ用途には向きません。
Edge Functions で npm パッケージは使える?
そのままでは使えません。Deno ランタイムのため Node.js の npm エコシステムは非対応です。1)deno.land や jsr.io から Deno 互換パッケージを import、2)axios の代わりに fetch、3)ky など Deno 向け代替を探す、の 3 点が基本です。
Edge Functions に実行時間制限はある?
あります。V8 Isolate の CPU 時間上限は 400 秒(ソフト + ハード)。超過するとプロセスは強制終了されます。JWT 検証、リクエスト転送、軽い計算向けの設計で、重いバッチ処理はセンター側や専用 Worker に任せましょう。
Edge Function から Postgres に接続するには?
接続プール枯渇を避けるため Supabase の Pooler を使います:

• 環境変数 `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 を保護するには?
2 通りあります:

• `config.toml` に `[functions.hello-world] verify_jwt = true`
• デプロイ時に `--verify-jwt`

ローカルは `--no-verify-jwt` でよいですが、本番では必ず有効化。未設定だと誰でも呼び出せます。

4分で読めます · 公開日: 2026年5月3日 · 更新日: 2026年6月8日

シリーズの読書導線 第 10 / 10 記事

Supabase 実践ガイド

検索からこのページに来た場合は、前後の記事もあわせて読むと同じテーマの理解がかなり早く深まります。

シリーズ全体を見る

関連記事

コメント

GitHubアカウントでログインしてコメントできます