0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js middleware.tsで実装するHTTPSリダイレクト・レート制限・サブドメインルーティング

0
Posted at

はじめに

Next.jsの middleware.ts は、リクエストがページやAPIに到達する前に実行される処理を記述できます。本記事では、実プロダクト HashFlops(768体のNFTキャラクターが決定論的PRNGでエンカウンターするプロジェクト)の middleware.ts を題材に、以下の3つの実装パターンを紹介します。

  1. HTTP→HTTPS 強制リダイレクト(Railway等のPaaS向け)
  2. In-memory レート制限
  3. サブドメインルーティング

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/42www.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 で稼働中です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?