Edge Runtime実践ガイド - 次世代のサーバーレス実行環境


Edge Runtime実践ガイド - 次世代のサーバーレス実行環境

Edge Runtimeは、世界中のエッジロケーションでJavaScript/TypeScriptコードを実行できる次世代の実行環境です。従来のサーバーレス関数と比べて、圧倒的な低レイテンシと高速なコールドスタートを実現します。

本記事では、Edge Runtimeの基本概念から、主要プラットフォームの比較、実践的なユースケースまでを網羅的に解説します。

Edge Runtimeとは何か

基本概念

Edge Runtimeは、CDN(Content Delivery Network)のエッジサーバー上でコードを実行する環境です。

従来のサーバーレス(Lambda)との違い:

特性Edge Runtime従来のサーバーレス
実行場所世界中のエッジ(300+拠点)特定リージョン(1拠点)
コールドスタート0-5ms100-1000ms
レイテンシ10-50ms50-500ms
実行時間制限10-50秒15分
ランタイムV8 Isolateコンテナ/仮想マシン
Node.js API制限ありフルサポート

V8 Isolateとは

Edge Runtimeは、ChromeのV8エンジンの「Isolate」という軽量な実行コンテキストを使用します。

// 各リクエストが独立したIsolateで実行される
// コンテナ起動のオーバーヘッドがないため、コールドスタートが極めて速い

// ✅ 使えるAPI
fetch() // Web標準Fetch API
Response, Request, Headers
URL, URLSearchParams
TextEncoder, TextDecoder
setTimeout, setInterval
crypto.subtle // Web Crypto API

// ❌ 使えないAPI(Node.js固有)
fs, path // ファイルシステム
child_process // プロセス実行
net, http // ネイティブネットワーク

主要プラットフォーム比較

1. Vercel Edge Functions

特徴:

  • Next.jsとの完全統合
  • Edge Middlewareで認証・リダイレクト
  • Vercel KVでエッジストレージ

制限:

  • 実行時間: 30秒(Hobbyプラン)
  • メモリ: 512MB
  • レスポンスサイズ: 4MB

セットアップ:

// Next.js App Router
// app/api/hello/route.ts

export const runtime = 'edge' // Edge Runtimeを指定

export async function GET(request: Request) {
  const url = new URL(request.url)
  const name = url.searchParams.get('name') || 'World'

  return new Response(
    JSON.stringify({ message: `Hello, ${name}!` }),
    {
      headers: {
        'content-type': 'application/json',
      },
    }
  )
}

Edge Middleware:

// middleware.ts(プロジェクトルート)

import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl

  // 認証チェック
  const token = request.cookies.get('token')

  if (pathname.startsWith('/admin') && !token) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  // A/Bテスト
  const bucket = Math.random() < 0.5 ? 'a' : 'b'
  const response = NextResponse.next()
  response.cookies.set('bucket', bucket)

  return response
}

export const config = {
  matcher: ['/admin/:path*', '/features/:path*'],
}

2. Cloudflare Workers

特徴:

  • 最大330拠点のエッジネットワーク
  • 圧倒的な無料枠(1日10万リクエスト)
  • Cloudflare D1(SQLite)、KV、R2との統合

制限:

  • 実行時間: 10-50ms(CPU時間)
  • メモリ: 128MB
  • スクリプトサイズ: 1MB

セットアップ:

// worker.ts

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

    // Cloudflare KVから取得
    const value = await env.MY_KV.get('key')

    return new Response(
      JSON.stringify({
        message: 'Hello from Cloudflare Workers!',
        value,
        location: request.cf?.colo, // エッジロケーション
      }),
      {
        headers: {
          'content-type': 'application/json',
        },
      }
    )
  },
}

interface Env {
  MY_KV: KVNamespace
}

wrangler.toml設定:

name = "my-worker"
main = "src/worker.ts"
compatibility_date = "2024-01-01"

# KVバインディング
kv_namespaces = [
  { binding = "MY_KV", id = "xxxxx" }
]

# ルート設定
routes = [
  { pattern = "example.com/api/*", zone_name = "example.com" }
]

デプロイ:

npm install -g wrangler
wrangler login
wrangler deploy

3. Deno Deploy

特徴:

  • TypeScript・Denoネイティブサポート
  • npm互換性(npm:指定子)
  • 世界35拠点のエッジ

制限:

  • 実行時間: 50ms(CPU時間)
  • メモリ: 512MB
  • 無料枠: 1日100万リクエスト

セットアップ:

// main.ts

import { serve } from "https://deno.land/std@0.200.0/http/server.ts"

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

  // DenoのネイティブAPIが使える
  const text = await Deno.readTextFile('./data.txt')

  return new Response(
    JSON.stringify({ message: 'Hello from Deno Deploy!', data: text }),
    {
      headers: {
        'content-type': 'application/json',
      },
    }
  )
})

デプロイ:

# GitHubと連携して自動デプロイ
# または deployctl CLI
deno install -Arf jsr:@deno/deployctl
deployctl deploy --project=my-project main.ts

パフォーマンス比較

レイテンシベンチマーク

実測値(東京からのアクセス):

プラットフォームコールドスタートウォームスタートTTFB
Vercel Edge15ms5ms20ms
Cloudflare Workers10ms3ms15ms
Deno Deploy12ms4ms18ms
AWS Lambda(東京)800ms20ms100ms

テスト条件: シンプルなJSON APIレスポンス

スループット

// ベンチマークコード
export const runtime = 'edge'

export async function GET() {
  const start = performance.now()

  // 100回の軽い処理
  let result = 0
  for (let i = 0; i < 100; i++) {
    result += Math.sqrt(i)
  }

  const duration = performance.now() - start

  return Response.json({ result, duration })
}

結果:

  • Vercel Edge: 0.2ms
  • Cloudflare Workers: 0.15ms
  • Deno Deploy: 0.18ms

地理的分散のメリット

// ユーザーに最も近いエッジで実行される

export async function GET(request: Request) {
  // Cloudflare Workers
  const location = request.cf?.colo // "NRT"(成田)など

  // Vercel Edge
  const geo = request.headers.get('x-vercel-ip-city') // 都市名

  return Response.json({
    location,
    message: `Served from ${location} edge`,
  })
}

実践的なユースケース

1. 認証・認可

// middleware.ts(Vercel Edge)

import { jwtVerify } from 'jose'
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

const JWT_SECRET = new TextEncoder().encode(
  process.env.JWT_SECRET || 'secret'
)

export async function middleware(request: NextRequest) {
  const token = request.cookies.get('auth-token')?.value

  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url))
  }

  try {
    const { payload } = await jwtVerify(token, JWT_SECRET)

    // ユーザー情報をヘッダーに追加
    const response = NextResponse.next()
    response.headers.set('x-user-id', payload.sub as string)

    return response
  } catch (error) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
}

export const config = {
  matcher: ['/api/protected/:path*', '/dashboard/:path*'],
}

2. レート制限

// Cloudflare Workers + KV

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const ip = request.headers.get('CF-Connecting-IP') || 'unknown'
    const key = `rate-limit:${ip}`

    // KVから現在のカウントを取得
    const count = parseInt((await env.KV.get(key)) || '0')

    if (count >= 100) {
      return new Response('Too Many Requests', { status: 429 })
    }

    // カウントを増やして60秒のTTLで保存
    await env.KV.put(key, (count + 1).toString(), {
      expirationTtl: 60,
    })

    return new Response('OK')
  },
}

3. A/Bテスト

// middleware.ts

export function middleware(request: NextRequest) {
  const bucket = request.cookies.get('ab-test')?.value

  if (!bucket) {
    // 新規ユーザーをランダムに振り分け
    const newBucket = Math.random() < 0.5 ? 'a' : 'b'
    const response = NextResponse.next()
    response.cookies.set('ab-test', newBucket, {
      maxAge: 60 * 60 * 24 * 30, // 30日
    })

    // 分析用ヘッダーを追加
    response.headers.set('x-ab-bucket', newBucket)

    return response
  }

  return NextResponse.next()
}

4. 地理的ルーティング

// Cloudflare Workers

const REGION_APIS = {
  'US': 'https://us-api.example.com',
  'EU': 'https://eu-api.example.com',
  'APAC': 'https://apac-api.example.com',
}

export default {
  async fetch(request: Request): Promise<Response> {
    // ユーザーの地理的位置を取得
    const country = request.cf?.country || 'US'

    // 地域に応じたAPIエンドポイントを選択
    let region = 'US'
    if (['GB', 'DE', 'FR'].includes(country)) {
      region = 'EU'
    } else if (['JP', 'CN', 'IN'].includes(country)) {
      region = 'APAC'
    }

    const apiUrl = REGION_APIS[region as keyof typeof REGION_APIS]

    // 適切な地域のAPIにプロキシ
    return fetch(apiUrl + new URL(request.url).pathname, request)
  },
}

5. 画像最適化

// Vercel Edge

export const runtime = 'edge'

export async function GET(request: Request) {
  const url = new URL(request.url)
  const imageUrl = url.searchParams.get('url')

  if (!imageUrl) {
    return new Response('Missing url parameter', { status: 400 })
  }

  // 画像を取得
  const imageResponse = await fetch(imageUrl)
  const imageBuffer = await imageResponse.arrayBuffer()

  // WebPに変換(仮想的な例)
  // 実際にはCloudflare Images、Vercel Image Optimizationを使用

  return new Response(imageBuffer, {
    headers: {
      'Content-Type': 'image/webp',
      'Cache-Control': 'public, max-age=31536000, immutable',
    },
  })
}

6. リアルタイム分析

// Cloudflare Workers + Analytics Engine

export default {
  async fetch(request: Request, env: Env): Promise<Response> {
    const startTime = Date.now()

    // メインの処理
    const response = await handleRequest(request)

    const duration = Date.now() - startTime

    // 分析データを記録(非ブロッキング)
    env.ANALYTICS.writeDataPoint({
      indexes: [request.url],
      blobs: [request.method, request.cf?.country || 'unknown'],
      doubles: [duration],
    })

    return response
  },
}

async function handleRequest(request: Request): Promise<Response> {
  return new Response('Hello!')
}

ベストプラクティス

1. 適切なユースケースの選択

Edge Runtimeが最適:

  • 認証・認可
  • リダイレクト・リライト
  • レート制限
  • A/Bテスト
  • ヘッダー操作
  • 軽量なAPI(JSON、HTML生成)

従来のサーバーレスが最適:

  • 長時間実行(5秒以上)
  • ファイルシステムアクセス
  • 大量のメモリ使用
  • ネイティブバイナリ依存

2. キャッシュ戦略

export async function GET(request: Request) {
  // キャッシュキーを生成
  const url = new URL(request.url)
  const cacheKey = new Request(url.toString(), request)

  // Cacheから取得を試みる
  const cache = caches.default
  let response = await cache.match(cacheKey)

  if (!response) {
    // キャッシュミス: データを取得
    const data = await fetchData()
    response = Response.json(data, {
      headers: {
        'Cache-Control': 'public, s-maxage=60',
      },
    })

    // キャッシュに保存
    await cache.put(cacheKey, response.clone())
  }

  return response
}

3. エラーハンドリング

export async function GET(request: Request) {
  try {
    const data = await fetchExternalAPI()
    return Response.json(data)
  } catch (error) {
    // エラーログ(Sentryなど)
    console.error('API Error:', error)

    // フォールバック
    return Response.json(
      { error: 'Service temporarily unavailable' },
      { status: 503 }
    )
  }
}

4. 環境変数の管理

// Vercel Edge
export const runtime = 'edge'

export async function GET() {
  // Edge Runtimeで使える環境変数
  const apiKey = process.env.API_KEY

  // セキュアに外部APIを呼び出す
  const response = await fetch('https://api.example.com', {
    headers: {
      'Authorization': `Bearer ${apiKey}`,
    },
  })

  return response
}

プラットフォーム選定のポイント

Vercel Edge Functionsを選ぶべき場合

  • Next.jsを使用している
  • Vercel KV、Postgres、Blobを活用したい
  • Vercelの開発体験を重視

Cloudflare Workersを選ぶべき場合

  • 最大のエッジネットワークが必要
  • 圧倒的な無料枠を活用したい
  • D1(SQLite)、KV、R2を使いたい

Deno Deployを選ぶべき場合

  • TypeScript・Denoエコシステムを使いたい
  • npmパッケージとの互換性が必要
  • シンプルなデプロイフローを求める

まとめ

Edge Runtimeは、以下の点で従来のサーバーレスを大きく上回ります:

  1. 低レイテンシ: 世界中どこからでも10-50msで応答
  2. 高速コールドスタート: 0-5msでの起動
  3. 地理的分散: ユーザーに最も近いエッジで実行
  4. コスト効率: 無料枠が大きく、従量課金も安価

一方で、実行時間・メモリ・APIの制約があるため、ユースケースに応じた適切な選択が重要です。

推奨される活用法:

  • Edge Runtime: 認証、ルーティング、軽量API
  • 従来のサーバーレス: 重い処理、長時間実行

両者を組み合わせることで、最適なパフォーマンスとコストを実現できます。

参考リンク: