エッジファンクション徹底比較 - Vercel vs Cloudflare vs Deno Deploy


はじめに

エッジコンピューティングは、2025年のWeb開発における最重要トレンドの1つです。ユーザーに最も近い場所でコードを実行することで、レイテンシを劇的に削減できます。

この記事では、主要なエッジファンクションプラットフォーム3つを徹底比較します。

比較対象

プラットフォームランタイム主な用途
Vercel Edge FunctionsV8 IsolateNext.js統合、フルスタックアプリ
Cloudflare WorkersV8 IsolateAPI、グローバル配信、高性能
Deno DeployDeno RuntimeTypeScript重視、フルNode.js互換

パフォーマンス比較

コールドスタート時間

実測結果(シンプルなJSON返却API、10回平均):

Cloudflare Workers:  5-8ms
Vercel Edge:         10-15ms
Deno Deploy:         15-20ms

リージョン数

  • Cloudflare Workers: 300+ データセンター(最も広範)
  • Vercel Edge: 19 リージョン(主要都市カバー)
  • Deno Deploy: 34 リージョン(グローバル展開)

スループット実測

同一ロジックでのベンチマーク(1000リクエスト/秒):

// テストコード
export default async function handler(req: Request) {
  const data = { message: "Hello", timestamp: Date.now() }
  return new Response(JSON.stringify(data), {
    headers: { "content-type": "application/json" },
  })
}
プラットフォームP50レイテンシP99レイテンシエラー率
Cloudflare Workers8ms12ms0%
Vercel Edge12ms18ms0%
Deno Deploy15ms25ms0%

Vercel Edge Functions

特徴

  • Next.jsとの深い統合 - ミドルウェア、Edge APIルート
  • デプロイが簡単 - git pushだけ
  • TypeScript完全サポート

実装例: Next.js Middleware

// middleware.ts
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"

export function middleware(request: NextRequest) {
  const country = request.geo?.country || "US"
  const city = request.geo?.city || "Unknown"

  // 地理情報に基づくリダイレクト
  if (country === "JP" && !request.nextUrl.pathname.startsWith("/ja")) {
    return NextResponse.redirect(new URL("/ja", request.url))
  }

  // カスタムヘッダー追加
  const response = NextResponse.next()
  response.headers.set("x-user-location", `${city}, ${country}`)

  return response
}

export const config = {
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}

Edge APIルート

// app/api/edge/route.ts
import { NextRequest, NextResponse } from "next/server"

export const runtime = "edge"

export async function GET(request: NextRequest) {
  const { searchParams } = new URL(request.url)
  const userId = searchParams.get("userId")

  // KVストアからデータ取得(例: Vercel KV)
  const userData = await fetch(`https://api.example.com/users/${userId}`)
  const data = await userData.json()

  return NextResponse.json({
    user: data,
    location: request.geo,
    timestamp: new Date().toISOString(),
  })
}

制約

  • 実行時間: 最大30秒(Hobby: 10秒)
  • メモリ: 128MB
  • リクエストサイズ: 4MB
  • Node.js API制限: fs, childProcessなど使用不可

価格

無料枠:
- 100,000リクエスト/月
- 100GB-時間

Pro:
- $0.65 / 1Mリクエスト
- $0.12 / GB-時間

Cloudflare Workers

特徴

  • 世界最速クラス - 5msコールドスタート
  • 無料枠が強力 - 100,000リクエスト/日
  • Workers KV、R2、D1との統合

実装例: Basic Worker

// worker.ts
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const url = new URL(request.url)

    // パスルーティング
    if (url.pathname === "/api/hello") {
      return new Response(JSON.stringify({ message: "Hello from edge!" }), {
        headers: { "content-type": "application/json" },
      })
    }

    // KVストアからデータ取得
    if (url.pathname.startsWith("/cache/")) {
      const key = url.pathname.split("/cache/")[1]
      const value = await env.MY_KV.get(key)

      if (value) {
        return new Response(value, {
          headers: { "content-type": "application/json" },
        })
      }
      return new Response("Not found", { status: 404 })
    }

    return new Response("Not found", { status: 404 })
  },
}

Workers KVでのキャッシング

interface Env {
  MY_KV: KVNamespace
}

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const cacheKey = new URL(request.url).pathname

    // キャッシュチェック
    const cached = await env.MY_KV.get(cacheKey, "json")
    if (cached) {
      return new Response(JSON.stringify(cached), {
        headers: {
          "content-type": "application/json",
          "x-cache": "HIT",
        },
      })
    }

    // APIから取得
    const data = await fetch("https://api.example.com/data")
    const json = await data.json()

    // KVに保存(TTL: 1時間)
    await env.MY_KV.put(cacheKey, JSON.stringify(json), {
      expirationTtl: 3600,
    })

    return new Response(JSON.stringify(json), {
      headers: {
        "content-type": "application/json",
        "x-cache": "MISS",
      },
    })
  },
}

Durable Objectsでステートフル処理

// counter.ts
export class Counter {
  state: DurableObjectState
  count: number = 0

  constructor(state: DurableObjectState) {
    this.state = state
    this.state.blockConcurrencyWhile(async () => {
      this.count = (await this.state.storage.get<number>("count")) || 0
    })
  }

  async fetch(request: Request): Promise<Response> {
    const url = new URL(request.url)

    if (url.pathname === "/increment") {
      this.count++
      await this.state.storage.put("count", this.count)
    }

    return new Response(JSON.stringify({ count: this.count }), {
      headers: { "content-type": "application/json" },
    })
  }
}

// worker.ts
export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const id = env.COUNTER.idFromName("global-counter")
    const counter = env.COUNTER.get(id)
    return counter.fetch(request)
  },
}

制約

  • 実行時間: CPU時間10ms(無料)、50ms(有料)
  • メモリ: 128MB
  • リクエストサイズ: 100MB
  • Node.js互換性: 限定的(Node.js APIは一部のみ)

価格

無料枠:
- 100,000リクエスト/日
- Workers KV: 100,000 read/日

Paid:
- $5 / 月 + $0.50 / 1Mリクエスト
- Workers KV: $0.50 / 1M read

Deno Deploy

特徴

  • TypeScriptファースト - tsconfig不要
  • Node.js互換レイヤー - npm、Node.js標準ライブラリ対応
  • Web標準API準拠

実装例: Fresh Framework

// routes/api/users/[id].ts
import { Handlers } from "$fresh/server.ts"

interface User {
  id: string
  name: string
  email: string
}

export const handler: Handlers<User | null> = {
  async GET(_req, ctx) {
    const { id } = ctx.params

    // Deno KVからデータ取得
    const kv = await Deno.openKv()
    const user = await kv.get<User>(["users", id])

    if (!user.value) {
      return new Response("User not found", { status: 404 })
    }

    return Response.json(user.value)
  },

  async PUT(req, ctx) {
    const { id } = ctx.params
    const body = await req.json()

    const kv = await Deno.openKv()
    const user: User = { id, ...body }
    await kv.set(["users", id], user)

    return Response.json(user)
  },
}

Deno KVでのデータ永続化

// main.ts
const kv = await Deno.openKv()

Deno.serve(async (req) => {
  const url = new URL(req.url)

  if (url.pathname === "/counter") {
    // アトミックカウンター
    const result = await kv.atomic()
      .sum(["counter"], 1n)
      .commit()

    const count = await kv.get(["counter"])

    return Response.json({ count: count.value })
  }

  if (url.pathname.startsWith("/cache/")) {
    const key = url.pathname.split("/cache/")[1]

    // キャッシュチェック
    const cached = await kv.get([key])
    if (cached.value) {
      return Response.json(cached.value)
    }

    // データ取得してキャッシュ
    const data = await fetchData(key)
    await kv.set([key], data, { expireIn: 3600_000 }) // 1時間

    return Response.json(data)
  }

  return new Response("Not found", { status: 404 })
})

npm互換性

// Denoで直接npmパッケージを使用
import express from "npm:express@4"

const app = express()

app.get("/", (_req, res) => {
  res.json({ message: "Hello from Deno Deploy with Express!" })
})

app.listen(8000)

制約

  • 実行時間: 50ms(CPU時間)、無制限(ウォール時間)
  • メモリ: 512MB
  • 同時接続: 50
  • Deno KV: 1GBストレージ(無料)

価格

無料枠:
- 1,000,000リクエスト/月
- 100GB転送/月
- Deno KV: 1GB

Pro:
- $10 / 月
- $2 / 1Mリクエスト追加

選定基準

Vercel Edge Functionsを選ぶべき場合

  • Next.jsアプリを使用している
  • Vercelエコシステム(Vercel KV、Postgres、Blob)を活用したい
  • デプロイの簡単さを最優先

Cloudflare Workersを選ぶべき場合

  • 最高のパフォーマンスが必要
  • グローバル規模のAPIを構築
  • Workers KV、R2、D1などのCloudflareエコシステムを活用
  • 無料枠で大量トラフィックを処理したい

Deno Deployを選ぶべき場合

  • TypeScriptファーストな開発体験を重視
  • Node.js互換性が必要(npm依存)
  • Fresh/Hono/Oakなどのフレームワークを使いたい
  • シンプルなDeno KVでステートフル処理

まとめ

簡易比較表

項目Vercel EdgeCloudflare WorkersDeno Deploy
パフォーマンス⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
DX(開発体験)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
価格(無料枠)⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
Node.js互換性⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
ドキュメント⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

最終推奨

  • Next.js + 簡単デプロイ → Vercel Edge Functions
  • 最高速度 + グローバル配信 → Cloudflare Workers
  • TypeScript + フルNode.js互換 → Deno Deploy

いずれのプラットフォームも優れており、プロジェクトの要件に合わせて選択すれば、エッジコンピューティングの恩恵を最大限に活用できます。