Edge Functionマスターガイド2025 - エッジコンピューティングの完全攻略
Cloudflare Workers、Vercel Edge Functions、Deno Deployの最新機能を徹底比較。パフォーマンス最適化、コスト削減、実装パターン、本番運用のベストプラクティスまで、エッジコンピューティングを極める完全ガイドです。
エッジコンピューティングは、ユーザーに最も近い場所でコードを実行する新しいパラダイムです。Cloudflare Workersを使って、0msコールドスタートのグローバルアプリケーションを構築する方法を解説します。
「サーバーレス」の次は「エッジ」です。ユーザーに最も近い場所でコードを実行することで、レイテンシーを劇的に削減し、真のグローバルアプリケーションを実現できます。本記事では、Cloudflare Workers を中心に、エッジコンピューティングの基礎から実践的な開発手法まで詳しく解説します。
エッジコンピューティングは、データ処理をユーザーに物理的に近い場所で行う分散コンピューティングパラダイムです。
チャートを読み込み中...
特徴 | 従来のクラウド | エッジコンピューティング | 改善率 |
---|---|---|---|
レイテンシー | 50-200ms | 5-20ms | 90%削減 |
可用性 | 99.9% | 99.99% | 10倍向上 |
スケーラビリティ | リージョン単位 | グローバル自動 | 無限 |
コールドスタート | 100-500ms | 0ms | 完全排除 |
地理的カバレッジ | 数リージョン | 275+拠点 | 50倍 |
数あるエッジプラットフォームの中で、Cloudflare Workers が注目される理由があります。
プラットフォーム | 拠点数 | 言語 | 料金 | 特徴 |
---|---|---|---|---|
Cloudflare Workers | 275+ | JS/Rust/C | $5~/月 | 0msコールドスタート |
AWS Lambda@Edge | 200+ | JS/Python | $0.50/100万 | CloudFront統合 |
Vercel Edge | 20+ | JS/TS | $20~/月 | Next.js統合 |
Fastly Compute | 60+ | Rust/JS | 要問合せ | 高性能CDN |
私たちは、インターネットそのものをプログラム可能にしています。 開発者は地理的な制約から解放され、真にグローバルなアプリケーションを構築できます。
Cloudflare Workers の開発を始めるのは驚くほど簡単です。
# Wranglerのインストール(CLIツール)
npm install -g wrangler
# または
bun add -g wrangler
# バージョン確認
wrangler --version
# 3.x.x(2025年6月時点)
# Cloudflareアカウントにログイン
wrangler login
# 新規プロジェクト作成
wrangler init my-edge-app
# テンプレート選択
? Would you like to use a template?
❯ Hello World Worker
Router Worker
Scheduled Worker
ChatGPT Plugin
# TypeScript設定
? Would you like to use TypeScript? Yes
my-edge-app/
├── src/
│ └── index.ts # メインのWorkerコード
├── wrangler.toml # 設定ファイル
├── package.json
├── tsconfig.json
└── node_modules/
最初の Worker を作成してみましょう。
// src/index.ts
export interface Env {
// 環境変数やバインディングの型定義
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/') {
return new Response('Hello from the Edge!', {
headers: {
'content-type': 'text/plain',
},
});
}
if (url.pathname === '/api/time') {
const now = new Date().toISOString();
return Response.json({
time: now,
region: request.cf?.region || 'unknown',
});
}
return new Response('Not Found', { status: 404 });
},
};
# ローカル開発サーバー起動
wrangler dev
# http://localhost:8787 でアクセス可能
// より実践的なAPI実装
import { Router } from 'itty-router';
const router = Router();
// ユーザー情報API
router.get('/api/users/:id', async (request, env) => {
const { id } = request.params;
// KVストレージから取得(後述)
const user = await env.USERS_KV.get(`user:${id}`, 'json');
if (!user) {
return new Response('User not found', { status: 404 });
}
return Response.json(user);
});
// POST リクエスト処理
router.post('/api/users', async (request, env) => {
const body = await request.json();
const id = crypto.randomUUID();
await env.USERS_KV.put(
`user:${id}`,
JSON.stringify({
id,
...body,
createdAt: new Date().toISOString(),
})
);
return Response.json({ id }, { status: 201 });
});
// デフォルトハンドラー
export default {
fetch: router.handle,
};
Workers KV は、グローバルに分散された低レイテンシーのキーバリューストレージです。
# wrangler.toml
name = "my-edge-app"
main = "src/index.ts"
compatibility_date = "2025-06-20"
[[kv_namespaces]]
binding = "USERS_KV"
id = "your-kv-namespace-id"
export interface Env {
USERS_KV: KVNamespace;
}
// データの保存
await env.USERS_KV.put('key', 'value');
await env.USERS_KV.put('user:123', JSON.stringify(userData), {
expirationTtl: 60 * 60 * 24, // 24時間で期限切れ
});
// データの取得
const value = await env.USERS_KV.get('key');
const user = await env.USERS_KV.get('user:123', 'json');
// リスト取得
const list = await env.USERS_KV.list({ prefix: 'user:' });
// 削除
await env.USERS_KV.delete('key');
// エッジキャッシュの実装
async function getCachedData(key: string, env: Env): Promise<any> {
// まずKVから取得を試みる
const cached = await env.USERS_KV.get(key, 'json');
if (cached && cached.expiry > Date.now()) {
return cached.data;
}
// キャッシュミスの場合、オリジンから取得
const fresh = await fetchFromOrigin(key);
// KVに保存(5分間キャッシュ)
await env.USERS_KV.put(
key,
JSON.stringify({
data: fresh,
expiry: Date.now() + 5 * 60 * 1000,
})
);
return fresh;
}
Durable Objects は、エッジで強い一貫性を持つ状態管理を可能にします。
// src/counter.ts
export class Counter implements DurableObject {
private state: DurableObjectState;
private value: number = 0;
constructor(state: DurableObjectState) {
this.state = state;
// 永続化された状態を復元
this.state.blockConcurrencyWhile(async () => {
const stored = await this.state.storage.get<number>('value');
this.value = stored || 0;
});
}
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
switch (url.pathname) {
case '/increment':
this.value++;
await this.state.storage.put('value', this.value);
return Response.json({ value: this.value });
case '/decrement':
this.value--;
await this.state.storage.put('value', this.value);
return Response.json({ value: this.value });
case '/value':
return Response.json({ value: this.value });
default:
return new Response('Not Found', { status: 404 });
}
}
}
// メインWorkerから呼び出し
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const id = env.COUNTER.idFromName('global-counter');
const counter = env.COUNTER.get(id);
// Durable Objectにリクエストを転送
return counter.fetch(request);
},
};
export class ChatRoom implements DurableObject {
private connections: Set<WebSocket> = new Set();
private messages: Array<Message> = [];
async fetch(request: Request): Promise<Response> {
if (request.headers.get('Upgrade') === 'websocket') {
const pair = new WebSocketPair();
await this.handleWebSocket(pair[1]);
return new Response(null, {
status: 101,
webSocket: pair[0],
});
}
// 通常のHTTPリクエスト
return Response.json({ messages: this.messages });
}
async handleWebSocket(ws: WebSocket) {
ws.accept();
this.connections.add(ws);
// 既存メッセージを送信
ws.send(
JSON.stringify({
type: 'history',
messages: this.messages,
})
);
ws.addEventListener('message', async (event) => {
const message = JSON.parse(event.data as string);
// メッセージを保存
this.messages.push(message);
await this.state.storage.put('messages', this.messages);
// 全接続にブロードキャスト
const broadcast = JSON.stringify({
type: 'message',
data: message,
});
for (const conn of this.connections) {
try {
conn.send(broadcast);
} catch {
this.connections.delete(conn);
}
}
});
ws.addEventListener('close', () => {
this.connections.delete(ws);
});
}
}
R2 は、S3 互換のオブジェクトストレージで、エグレス料金が無料です。
# wrangler.toml
[[r2_buckets]]
binding = "FILES_BUCKET"
bucket_name = "my-files"
// ファイルアップロード
router.post('/upload', async (request, env) => {
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
return new Response('No file uploaded', { status: 400 });
}
const key = `uploads/${crypto.randomUUID()}-${file.name}`;
// R2にアップロード
await env.FILES_BUCKET.put(key, file.stream(), {
httpMetadata: {
contentType: file.type,
},
customMetadata: {
uploadedBy: 'user123',
uploadedAt: new Date().toISOString(),
},
});
return Response.json({
key,
size: file.size,
type: file.type,
});
});
// ファイル取得
router.get('/files/:key', async (request, env) => {
const { key } = request.params;
const object = await env.FILES_BUCKET.get(key);
if (!object) {
return new Response('File not found', { status: 404 });
}
const headers = new Headers();
object.writeHttpMetadata(headers);
headers.set('etag', object.httpEtag);
return new Response(object.body, { headers });
});
// 画像のリサイズとキャッシュ
router.get('/images/:key', async (request, env) => {
const { key } = request.params;
const url = new URL(request.url);
const width = parseInt(url.searchParams.get('w') || '0');
const height = parseInt(url.searchParams.get('h') || '0');
const format = url.searchParams.get('format') || 'webp';
// キャッシュキー
const cacheKey = `${key}-${width}x${height}-${format}`;
// キャッシュチェック
const cached = await env.FILES_BUCKET.get(cacheKey);
if (cached) {
return new Response(cached.body, {
headers: {
'content-type': `image/${format}`,
'cache-control': 'public, max-age=31536000',
},
});
}
// オリジナル画像取得
const original = await env.FILES_BUCKET.get(key);
if (!original) {
return new Response('Image not found', { status: 404 });
}
// Cloudflare Image Resizing API
const resized = await fetch(
`https://example.com/cdn-cgi/image/width=${width},height=${height},format=${format}/${key}`,
{
cf: {
image: {
width,
height,
format,
},
},
}
);
// リサイズ画像をキャッシュ
await env.FILES_BUCKET.put(cacheKey, resized.body);
return new Response(resized.body, {
headers: {
'content-type': `image/${format}`,
'cache-control': 'public, max-age=31536000',
},
});
});
実際のプロダクションで使えるアプリケーション例を紹介します。
// APIゲートウェイとレート制限
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const clientIP = request.headers.get('CF-Connecting-IP') || '';
// レート制限チェック
const rateLimitKey = `rate:${clientIP}`;
const current = await env.RATE_LIMIT_KV.get(rateLimitKey);
if (current && parseInt(current) > 100) {
return new Response('Rate limit exceeded', {
status: 429,
headers: {
'Retry-After': '60',
},
});
}
// レート制限カウンター更新
await env.RATE_LIMIT_KV.put(rateLimitKey, String(parseInt(current || '0') + 1), {
expirationTtl: 60,
});
// 認証チェック
const authHeader = request.headers.get('Authorization');
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return new Response('Unauthorized', { status: 401 });
}
const token = authHeader.substring(7);
const user = await validateToken(token, env);
if (!user) {
return new Response('Invalid token', { status: 403 });
}
// バックエンドAPIへプロキシ
const backendUrl = new URL(request.url);
backendUrl.hostname = 'api.backend.com';
const backendRequest = new Request(backendUrl, request);
backendRequest.headers.set('X-User-ID', user.id);
backendRequest.headers.set('X-Edge-Region', request.cf?.region || 'unknown');
return fetch(backendRequest);
},
};
// A/Bテストの実装
router.get('/', async (request, env) => {
const cookie = parseCookie(request.headers.get('Cookie') || '');
let variant = cookie.variant;
if (!variant) {
// ランダムに振り分け
variant = Math.random() < 0.5 ? 'A' : 'B';
}
// メトリクス記録
await env.ANALYTICS_DO.get(env.ANALYTICS_DO.idFromName('ab-test')).fetch(
new Request('https://internal/record', {
method: 'POST',
body: JSON.stringify({
variant,
timestamp: Date.now(),
country: request.cf?.country,
}),
})
);
// バリアントに応じたコンテンツ返却
const html = variant === 'A' ? await generateVariantA() : await generateVariantB();
return new Response(html, {
headers: {
'content-type': 'text/html',
'set-cookie': `variant=${variant}; Path=/; Max-Age=2592000`,
},
});
});
最寄りのエッジで即座に処理
JWTトークンをエッジで検証
高速なキャッシュレイヤー
必要な処理をエッジで完結
5-20msで応答
エッジでのパフォーマンスを最大化するテクニックを紹介します。
// スマートキャッシュの実装
const cache = caches.default;
router.get('/api/data/:id', async (request, env) => {
const cacheKey = new Request(request.url, request);
const cached = await cache.match(cacheKey);
if (cached) {
// キャッシュヒット
return cached;
}
// データ取得
const data = await fetchData(request.params.id, env);
const response = Response.json(data);
// キャッシュヘッダー設定
response.headers.set('Cache-Control', 'public, max-age=300, s-maxage=3600');
response.headers.set('CDN-Cache-Control', 'max-age=3600');
// エッジキャッシュに保存
ctx.waitUntil(cache.put(cacheKey, response.clone()));
return response;
});
// 複数のAPIを並列で呼び出し
router.get('/dashboard', async (request, env) => {
const userId = getUserId(request);
// 並列でデータ取得
const [user, posts, analytics] = await Promise.all([
env.USERS_KV.get(`user:${userId}`, 'json'),
fetchUserPosts(userId),
fetchAnalytics(userId),
]);
return Response.json({
user,
posts,
analytics,
});
});
// 大量データのストリーミング
router.get('/export/users', async (request, env) => {
const { readable, writable } = new TransformStream();
const writer = writable.getWriter();
// 非同期でデータをストリーミング
ctx.waitUntil(
(async () => {
await writer.write('[');
let first = true;
const list = await env.USERS_KV.list();
for (const key of list.keys) {
const user = await env.USERS_KV.get(key.name, 'json');
if (!first) await writer.write(',');
await writer.write(JSON.stringify(user));
first = false;
}
await writer.write(']');
await writer.close();
})()
);
return new Response(readable, {
headers: {
'content-type': 'application/json',
'content-disposition': 'attachment; filename="users.json"',
},
});
});
従来のサーバーレスと比較した改善率
エッジ環境でのセキュリティは特に重要です。
// CSPヘッダーの設定
function addSecurityHeaders(response: Response): Response {
response.headers.set(
'Content-Security-Policy',
"default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
);
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-XSS-Protection', '1; mode=block');
return response;
}
// CORS設定
function handleCORS(request: Request, response: Response): Response {
const origin = request.headers.get('Origin');
if (origin && isAllowedOrigin(origin)) {
response.headers.set('Access-Control-Allow-Origin', origin);
response.headers.set('Access-Control-Allow-Credentials', 'true');
}
if (request.method === 'OPTIONS') {
response.headers.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
response.headers.set('Access-Control-Allow-Headers', 'Content-Type, Authorization');
response.headers.set('Access-Control-Max-Age', '86400');
}
return response;
}
// グローバルエラーハンドラー
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
try {
return await router.handle(request, env, ctx);
} catch (error) {
// エラーログ記録
ctx.waitUntil(logError(error, request, env));
// 本番環境では詳細を隠す
if (env.ENVIRONMENT === 'production') {
return new Response('Internal Server Error', {
status: 500,
headers: {
'X-Error-Id': crypto.randomUUID(),
},
});
}
// 開発環境では詳細表示
return new Response(error.stack || error.message, {
status: 500,
headers: {
'content-type': 'text/plain',
},
});
}
},
};
エッジコンピューティングは、web 開発の新しいパラダイムです。
✅ グローバルアプリケーション: 世界中のユーザーに低レイテンシーで提供 ✅ APIゲートウェイ: 認証、レート制限、ルーティング ✅ 静的サイト生成: エッジでの動的レンダリング ✅ リアルタイム処理: WebSocket、Server-Sent Events ✅ 画像・動画処理: リサイズ、フォーマット変換
Cloudflare Workers を導入してから、api のレスポンス時間が 80%短縮され、 インフラコストも 50%削減できました。 エッジコンピューティングは、もはや選択肢ではなく必須です。