エッジファンクション徹底比較 - Vercel vs Cloudflare vs Deno Deploy
はじめに
エッジコンピューティングは、2025年のWeb開発における最重要トレンドの1つです。ユーザーに最も近い場所でコードを実行することで、レイテンシを劇的に削減できます。
この記事では、主要なエッジファンクションプラットフォーム3つを徹底比較します。
比較対象
| プラットフォーム | ランタイム | 主な用途 |
|---|---|---|
| Vercel Edge Functions | V8 Isolate | Next.js統合、フルスタックアプリ |
| Cloudflare Workers | V8 Isolate | API、グローバル配信、高性能 |
| Deno Deploy | Deno Runtime | TypeScript重視、フル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 Workers | 8ms | 12ms | 0% |
| Vercel Edge | 12ms | 18ms | 0% |
| Deno Deploy | 15ms | 25ms | 0% |
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 Edge | Cloudflare Workers | Deno Deploy |
|---|---|---|---|
| パフォーマンス | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| DX(開発体験) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 価格(無料枠) | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| Node.js互換性 | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| ドキュメント | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
最終推奨
- Next.js + 簡単デプロイ → Vercel Edge Functions
- 最高速度 + グローバル配信 → Cloudflare Workers
- TypeScript + フルNode.js互換 → Deno Deploy
いずれのプラットフォームも優れており、プロジェクトの要件に合わせて選択すれば、エッジコンピューティングの恩恵を最大限に活用できます。