



WithCodeMedia-1-pc
WithCodeMedia-2-pc
WithCodeMedia-3-pc
WithCodeMedia-4-pc




WithCodeMedia-1-sp
WithCodeMedia-2-sp
WithCodeMedia-3-sp
WithCodeMedia-4-sp








生徒ログイン状態によるページ分岐をサーバーで処理しているんですが、レスポンスが遅くて……もっと速くできませんか?



Edge Runtimeを使えばユーザーに最も近いエッジサーバーで処理できるんじゃ!認証チェック・リダイレクト・ABテストのような軽量処理はエッジで即座に判断することで、数msレベルのレスポンスが実現できるぞ。Cloudflare WorkersやNext.js Middlewareで簡単に始められるんじゃ!
結論:Edge Runtimeはユーザーに物理的に近いCDNノードでコードを実行するため、コールドスタートほぼゼロ・数msレベルのレスポンスを実現します。認証チェック・リダイレクト・ABテスト・ジオロケーション分岐などの軽量処理をエッジに移すことで、ユーザー体験を大幅に改善できます。
本記事では、Edge Runtimeの仕組みと主要プラットフォーム(Cloudflare Workers・Vercel Edge Functions・Netlify Edge Functions)の比較・Next.js Middlewareの実装・認証制御・ABテスト・ジオロケーション分岐・Cloudflare KV/D1との連携・Honoフレームワーク・デプロイとモニタリングを完全解説します。
Edge Runtimeとは、CDN(コンテンツデリバリーネットワーク)のエッジノード上でコードを実行できる仕組みです。Cloudflareであれば世界310箇所以上のエッジロケーションにデプロイでき、どのユーザーにも物理的に近いサーバーでリクエストを処理できます。
通常のAWS Lambda(東京リージョン)を使った場合、米国ユーザーからのアクセスには150〜200msのレイテンシが発生します。Edge Runtimeを使えばこのレイテンシを10〜30msに削減できます。コールドスタートも数ms以下(Lambdaは数百ms〜1秒)なので、パフォーマンス面で大きなアドバンテージがあります。


| 項目 | 通常のサーバーレス(AWS Lambda等) | Edge Runtime(Cloudflare Workers等) |
|---|---|---|
| 実行場所 | 特定リージョンのデータセンター | 世界200+箇所のエッジノード |
| コールドスタート | 数百ms〜1秒 | 数ms(ほぼゼロ) |
| メモリ上限 | 128MB〜3GB | 128MB(軽量処理向け) |
| ランタイム | Node.js / Python等フル環境 | V8(Node.js APIの一部は使えない) |
| 向いている処理 | DB接続・ファイル操作・重い処理 | 認証・リダイレクト・ABテスト・軽量API |
| 課金モデル | 実行時間(ms)×メモリ | リクエスト数ベース(Cloudflare Workers無料枠10万req/日) |
>【主要 Edge Runtime プラットフォーム比較】
Cloudflare Workers:
→ WASMベース。世界310+エッジロケーション
→ CPU制限:10ms(バースト50ms)
→ KV・D1(SQLite)・R2(オブジェクトストレージ)・Queues・Durable Objectsとの連携が容易
→ Hono フレームワークとの相性が最良
→ 無料枠:10万リクエスト/日
Vercel Edge Functions:
→ Next.js Middlewareとして組み込み済み
→ TypeScript ネイティブ対応
→ Next.js を使っているなら追加設定不要
→ 無料枠:Edge Middlewareは100,000実行/月
Netlify Edge Functions:
→ Denoベース(Node.js互換性あり)
→ TypeScriptをそのまま書ける
→ Netlify を使っているプロジェクトに最適
→ 無料枠:125,000回/月
選択指針:
→ Next.js 使用中 → Vercel Edge Functions (Next.js Middleware)
→ フレームワーク非依存・グローバル展開 → Cloudflare Workers
→ Netlify デプロイ中 → Netlify Edge Functions


>// middleware.ts(プロジェクトルートに配置)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { jwtVerify } from 'jose' // Edge対応のJWTライブラリ
const JWT_SECRET = new TextEncoder().encode(process.env.JWT_SECRET!)
export async function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname
// 管理画面への認証チェック
if (pathname.startsWith('/admin') || pathname.startsWith('/dashboard')) {
const token = request.cookies.get('auth_token')?.value
if (!token) {
// 未認証ならログインページへリダイレクト
const loginUrl = new URL('/login', request.url)
loginUrl.searchParams.set('redirect', pathname)
return NextResponse.redirect(loginUrl)
}
// JWTの検証(jose はEdge対応)
// ⚠️ Node.jsの crypto モジュールはEdgeでは使えないため jose を使う
try {
const { payload } = await jwtVerify(token, JWT_SECRET)
// ロールベースのアクセス制御
if (pathname.startsWith('/admin') && payload.role !== 'admin') {
return NextResponse.redirect(new URL('/unauthorized', request.url))
}
// 認証情報をヘッダーに付与してバックエンドに渡す
const requestHeaders = new Headers(request.headers)
requestHeaders.set('x-user-id', String(payload.sub))
requestHeaders.set('x-user-role', String(payload.role))
return NextResponse.next({ request: { headers: requestHeaders } })
} catch {
// JWTが無効(期限切れ・改ざん)ならログインページへ
const response = NextResponse.redirect(new URL('/login', request.url))
response.cookies.delete('auth_token')
return response
}
}
return NextResponse.next()
}
// Middlewareを適用するパスを指定
export const config = {
matcher: [
'/admin/:path*',
'/dashboard/:path*',
// 静的ファイルとAPIルートを除外
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
>// middleware.ts - A/Bテストの実装
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// ABテストの設定
const AB_TESTS = {
homepage: {
variants: ['control', 'variant-b', 'variant-c'],
weights: [50, 30, 20], // 配分(合計100)
}
}
function selectVariant(weights: number[]): number {
const total = weights.reduce((a, b) => a + b, 0)
let random = Math.random() * total
for (let i = 0; i < weights.length; i++) {
random -= weights[i]
if (random <= 0) return i
}
return 0
}
export function middleware(request: NextRequest) {
// トップページへのリクエストのみ対象
if (request.nextUrl.pathname !== '/') {
return NextResponse.next()
}
// 既存のバリアントCookieを確認
const existingVariant = request.cookies.get('ab_homepage')?.value
const config = AB_TESTS.homepage
const variantName = existingVariant && config.variants.includes(existingVariant)
? existingVariant
: config.variants[selectVariant(config.weights)]
// バリアントに対応するURLにリライト
const url = request.nextUrl.clone()
if (variantName !== 'control') {
url.pathname = `/${variantName}`
}
const response = NextResponse.rewrite(url)
// バリアントをCookieに保存(30日間)
if (!existingVariant) {
response.cookies.set('ab_homepage', variantName, {
maxAge: 60 * 60 * 24 * 30,
httpOnly: true,
sameSite: 'lax',
})
}
// レスポンスヘッダーでバリアント情報を付与(アナリティクス用)
response.headers.set('x-ab-variant', variantName)
return response
}
>// middleware.ts - ジオロケーション分岐(Vercel / Cloudflare で利用可能)
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
// 対応言語とリージョンのマッピング
const LOCALE_MAP: Record = {
JP: 'ja',
US: 'en',
GB: 'en',
DE: 'de',
FR: 'fr',
CN: 'zh',
KR: 'ko',
}
export function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname
// 既に言語プレフィックスがあればスキップ
const hasLocalePrefix = /^\/(ja|en|de|fr|zh|ko)/.test(pathname)
if (hasLocalePrefix) return NextResponse.next()
// Vercel が自動付与するジオロケーション情報
const country = request.geo?.country || 'US'
const city = request.geo?.city || ''
// Accept-Language ヘッダーからも判定
const acceptLanguage = request.headers.get('accept-language') || ''
const preferredLang = acceptLanguage.split(',')[0].split('-')[0].toLowerCase()
// ロケールを決定(country優先、次にaccept-language)
const locale = LOCALE_MAP[country] ?? preferredLang ?? 'en'
// ロケールプレフィックス付きURLにリダイレクト
if (locale !== 'en') {
const url = request.nextUrl.clone()
url.pathname = `/${locale}${pathname}`
const response = NextResponse.redirect(url)
response.headers.set('x-user-country', country)
response.headers.set('x-user-city', city)
return response
}
// レスポンスヘッダーに地域情報を追加(アナリティクス用)
const response = NextResponse.next()
response.headers.set('x-user-country', country)
response.headers.set('x-user-city', city)
return response
}
HonoはCloudflare Workersに最適化された超軽量Webフレームワークです。Expressライクなシンプルなルーティング・ミドルウェア・TypeScript対応を提供しながら、バンドルサイズはわずか数KBです。
># Cloudflare Workers + Hono プロジェクトの作成
npm create cloudflare@latest my-hono-worker -- --framework=hono
cd my-hono-worker
>// src/index.ts - Hono を使った Cloudflare Workers
import { Hono } from 'hono'
import { cors } from 'hono/cors'
import { logger } from 'hono/logger'
import { jwt } from 'hono/jwt'
type Bindings = {
KV: KVNamespace // Cloudflare KV
DB: D1Database // Cloudflare D1 (SQLite)
}
const app = new Hono<{ Bindings: Bindings }>()
// ミドルウェアの設定
app.use('*', logger())
app.use('/api/*', cors({
origin: ['https://myapp.com', 'https://staging.myapp.com'],
allowMethods: ['GET', 'POST', 'PUT', 'DELETE'],
}))
// JWTミドルウェア(認証が必要なルート)
app.use('/api/protected/*', jwt({ secret: c => c.env.JWT_SECRET }))
// ルーティング
app.get('/', (c) => c.text('Hello Edge!'))
// パーソナライズAPI
app.get('/api/personalize', async (c) => {
const country = c.req.header('cf-ipcountry') || 'US'
const ua = c.req.header('user-agent') || ''
const isMobile = /iPhone|Android|iPad/.test(ua)
// Cloudflare KV からユーザー設定を取得(超高速)
const userId = c.get('jwtPayload')?.sub
const userConfig = userId
? await c.env.KV.get(`user:${userId}:config`, 'json')
: null
return c.json({
country,
device: isMobile ? 'mobile' : 'desktop',
userConfig,
timestamp: new Date().toISOString(),
})
})
// Cloudflare D1 (SQLite) を使ったAPI
app.get('/api/articles', async (c) => {
const country = c.req.query('country') || 'JP'
// D1にSQLクエリ(エッジで直接DBアクセス)
const { results } = await c.env.DB
.prepare('SELECT * FROM articles WHERE country = ? LIMIT 10')
.bind(country)
.all()
return c.json({ articles: results })
})
export default app
Cloudflare KV(Key-Value Store)はエッジで使える分散型のKVストレージです。読み取りはエッジノードでキャッシュされるため、グローバルに高速なデータ読み取りが可能です。設定値・フィーチャーフラグ・ユーザーセッションの保存に適しています。
>// KV の基本操作
// wrangler.toml でKVをバインド
// [[kv_namespaces]]
// binding = "KV"
// id = "xxxxxxxxxxxxx"
// 書き込み(TTL付き)
await env.KV.put('feature:new-ui', 'true', { expirationTtl: 3600 })
// 読み取り
const isNewUi = await env.KV.get('feature:new-ui') // → 'true' or null
// JSON の読み書き
await env.KV.put('config:ab-test', JSON.stringify({ variant: 'b', ratio: 0.5 }))
const config = await env.KV.get('config:ab-test', 'json')
// キーの一覧
const keys = await env.KV.list({ prefix: 'user:' })
// 削除
await env.KV.delete('feature:new-ui')
Cloudflare WorkersのCPU時間制限は、バースト時でも50msです。これを超えると処理が強制終了されます。Edge Runtimeでは必ず「軽量な処理のみをエッジで行い、重い処理はオリジンサーバーに委譲する」設計が重要です。
>【Edge で行うべき処理 vs オリジンに委譲すべき処理】
✅ Edgeで行うべき処理(10ms以内で完了するもの):
→ JWT/Cookie の検証(josや軽量ライブラリ使用)
→ リダイレクト・リライト
→ レスポンスヘッダーの付与
→ ABテストのバリアント割り当て
→ ジオロケーション分岐
→ KV/D1 からの設定読み込み(単純なクエリ)
❌ オリジンに委譲すべき処理:
→ 重いDBクエリ(結合・集計など)
→ 外部APIへの大量リクエスト
→ 画像・動画の処理
→ 重い計算処理(機械学習推論など)
→ ファイルシステムへのアクセス
>// waitUntil を使うとレスポンスを返した後に処理を継続できる
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise {
// まず素早くレスポンスを返す
const response = new Response('OK', { status: 200 })
// レスポンス後にアナリティクスデータを非同期送信
// → ユーザーを待たせずにバックグラウンドで処理
ctx.waitUntil(
sendAnalytics(request, env) // 非同期処理
)
return response
}
}
async function sendAnalytics(request: Request, env: Env) {
const data = {
url: request.url,
country: request.headers.get('cf-ipcountry'),
timestamp: Date.now(),
}
await env.KV.put(`analytics:${Date.now()}`, JSON.stringify(data), {
expirationTtl: 86400 // 24時間後に自動削除
})
}
># wrangler(Cloudflare公式CLIツール)のインストール
npm install -g wrangler
# ログイン
wrangler login
# ローカル開発サーバーの起動(本番環境と同じ挙動)
wrangler dev
# ステージング環境へのデプロイ
wrangler deploy --env staging
# 本番環境へのデプロイ
wrangler deploy --env production
# ログのリアルタイム確認(デプロイ後のデバッグ)
wrangler tail
># wrangler.toml - Cloudflare Workers の設定ファイル
name = "my-hono-worker"
main = "src/index.ts"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"] # Node.js互換モード
# KV ストレージのバインド
[[kv_namespaces]]
binding = "KV"
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# D1 データベースのバインド
[[d1_databases]]
binding = "DB"
database_name = "my-database"
database_id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# 環境変数(シークレットは wrangler secret で設定)
[vars]
ENVIRONMENT = "production"
# ステージング環境の設定
[env.staging]
name = "my-hono-worker-staging"
[env.staging.vars]
ENVIRONMENT = "staging"
A. 多くのNode.js固有のAPIは使えません。主に使えないのは fs(ファイルシステム)・child_process・net・crypto(一部)などです。使えるのはWebブラウザの標準API(fetch・crypto.subtle・TextEncoder等)と一部のNode互換APIのみです。Cloudflare Workersはcompatibility_flags = ["nodejs_compat"]を設定することで多くのNode.jsモジュールを利用できます。
A. 従来のTCP接続を使うDBへの直接アクセスはできません。ただし Cloudflare D1(SQLite互換)・PlanetScale・Neon(HTTP接続対応のPostgreSQL)・Turso(エッジ対応SQLite)など、エッジ対応DBを使うことでデータアクセスが可能です。単純なKVアクセスならCloudflare KVやDurable Objectsが最も高速です。
A. 無料プランでは1日10万リクエストまで無料(Workers Free)。有料プランでは月$5(Workers Paid)で100万リクエストが含まれ、超過分は100万リクエストあたり$0.30です。KVは無料枠で100,000読み取り/日・1,000書き込み/日が含まれます。個人サイトや中規模サービスなら無料枠で十分カバーできます。
A. 非常に有効です。Cloudflareは東京・大阪を含む複数の日本のエッジノードを持っています。東京のユーザーが東京のエッジノードでリクエストを処理される場合、レイテンシは数ms以下です。一方、オリジンサーバーが米国の場合は往復100ms以上かかります。国内ユーザーへの高速化にも大きな効果があります。
A. MiddlewareはリクエストがページやAPIに届く前に実行される軽量処理(認証確認・リダイレクト・ヘッダー付与など)に使います。API RoutesはDB接続・外部API呼び出し・複雑なビジネスロジックなど、より重い処理に使います。原則として「ルーティング判断・認証確認・ヘッダー操作はMiddleware、実際のデータ処理はAPI Route」と覚えると整理しやすいです。
middleware.tsを配置するだけでEdge処理を組み込める。matcherでパスを絞る。Edge Runtimeは「全部エッジで処理しよう」ではなく、「軽量なリクエスト処理はエッジ・重い処理はオリジン」という使い分けが成功の鍵です。まずNext.js Middlewareでの認証制御やABテストから試してみましょう。Cloudflare WorkersとHonoの組み合わせは超高速APIの構築に最適です。
公式サイト より
今すぐ
無料カウンセリング
を予約!