はじめに
Next.jsの middleware.ts は、リクエストがページやAPIに到達する前に実行される処理を記述できます。本記事では、実プロダクト HashFlops(768体のNFTキャラクターが決定論的PRNGでエンカウンターするプロジェクト)の middleware.ts を題材に、以下の3つの実装パターンを紹介します。
- HTTP→HTTPS 強制リダイレクト(Railway等のPaaS向け)
- In-memory レート制限
- サブドメインルーティング
1. HTTP→HTTPS 強制リダイレクト
RailwayなどのPaaSでは、CDNがHTTPリクエストをそのままアプリに転送することがあります。アプリ層でHTTPSリダイレクトを強制する必要があります。
const proto = request.headers.get('x-forwarded-proto');
const host = request.headers.get('host') ?? '';
const isLocalhost = host.startsWith('localhost') || host.startsWith('127.0.0.1');
if (proto === 'http' && !isLocalhost) {
const httpsUrl = `https://${host}${pathname}${request.nextUrl.search}`;
return NextResponse.redirect(httpsUrl, 301);
}
ポイント:
-
x-forwarded-protoヘッダーでプロトコルを判定 - localhost/127.0.0.1 はスキップ(開発環境対応)
- 301(恒久リダイレクト)でSEOに影響なし
2. In-memory レート制限
外部サービス(Redis等)を使わずにシンプルなレート制限を実装します。
const RATE_LIMIT_WINDOW_MS = 60_000; // 1分
const RATE_LIMITS: Record<string, number> = {
'/api': 60, // API: 60 req/min
};
const rateLimitStore = new Map<string, { count: number; resetAt: number }>();
function checkRateLimit(ip: string, path: string): boolean {
// 最長プレフィクスマッチでルート別のリミットを取得
let limit = 0, prefix = '';
for (const [p, l] of Object.entries(RATE_LIMITS)) {
if (path.startsWith(p) && p.length > prefix.length) {
limit = l; prefix = p;
}
}
if (limit === 0) return true;
const key = `${ip}:${prefix}`;
const now = Date.now();
const entry = rateLimitStore.get(key);
if (!entry || now > entry.resetAt) {
rateLimitStore.set(key, { count: 1, resetAt: now + RATE_LIMIT_WINDOW_MS });
return true;
}
entry.count++;
return entry.count <= limit;
}
ポイント:
- Map ベースでRedis不要、依存ゼロ
- 5分間隔の setInterval で期限切れエントリをクリーンアップ
- 超過時は429 + Retry-After ヘッダーを返却
注意: In-memory方式はプロセス単位です。複数インスタンスで実行する場合、実効上限は N × 設定値 になります。
3. サブドメインルーティング
NFTコレクションごとのサブドメインを正規URLにリダイレクトします。
const SUBDOMAIN_FORM_MAP: Record<string, string> = {
monochrome: 'shadow',
color: 'primal',
multicolored: 'prism',
};
const TOKEN_PATH_REGEX = /^\/(\d{1,3})\/?$/;
const form = SUBDOMAIN_FORM_MAP[subdomain];
if (form) {
const match = pathname.match(TOKEN_PATH_REGEX);
if (match) {
const tokenId = parseInt(match[1], 10);
if (tokenId >= 1 && tokenId <= 256) {
return NextResponse.redirect(
`${CANONICAL_ORIGIN}/characters/${tokenId}/${form}`, 301
);
}
}
return NextResponse.redirect(CANONICAL_ORIGIN, 301);
}
例: monochrome.hashflops.com/42 → www.hashflops.com/characters/42/shadow
matcher設定
静的アセットをmiddlewareの対象から除外します。
export const config = {
matcher: ['/((?!_next/static|_next/image|favicon.ico|images/).*)'],
};
まとめ
Next.jsの middleware.ts だけで、HTTPSリダイレクト・レート制限・サブドメインルーティングの3機能を1ファイルに集約できます。外部依存なしで実現できるのは Edge Middleware の強力さを物語っています。
実際のサイトは HashFlops で稼働中です。