デプロイが金曜日の午後4時に配信される。3つのダッシュボード全体で監視画面が赤くなる。ユーザーはチェックアウト、ログイン、重要なあらゆるAPIコール上で429エラーを目にしている。レート制限がリクエストを拒否している。さらに悪いことに、サードパーティのAPIがあなたを拒否しており、リテイロジックが状況を悪化させている。HTTP 429 Too Many Requestsステータスコードは、あなたと仕事ができる週末の唯一の障壁になってしまった。ほとんどの開発者は429が「スローダウンしろ」を意味することを知っている。彼らが見落とすのは、どのリクエストをリトライするか、いつ指数バックオフするか、実際に連鎖的な問題を防ぐRetry-Afterヘッダーの設定方法だ。あなたのAPIがトラフィックを拒否し始める時に何が本当に起きているのか、ここで解説する。

この問題の両側を経験している。CMSのAPIを誤ったビルドプロセスで無意識のうちにDDoS攻撃していた開発者だったことがあり、独自のサーバーを暴走したクライアントから保護するためレート制限を実装していた側だったこともある。どちらの経験も、ドキュメントではカバーされていないことを教えてくれた。すべてを一緒に見ていこう。

目次

HTTP 429 Too Many Requests: Causes, Fixes, and Rate Limiting

HTTP 429の真の意味は何か?

HTTP 429はRFC 6585で定義されており、2012年に公開された。仕様は驚くほど短い。要点はこれだ:ユーザー(またはクライアント)が与えられた時間内に多数のリクエストを送信した。

それだけだ。これはレート制限の応答だ。サーバーは「あなたのリクエストを理解しました、おそらく有効ですが、スローダウンする必要があります」と言っている。

これは403 Forbidden(許可されていない)または503 Service Unavailable(サーバー全体が苦労している)と異なる。429はターゲットされている。それはあなたのリクエストレートに関するものだ。

レスポンスには、クライアントに再度試すまでの待機時間を伝えるRetry-Afterヘッダーを含む必要があります(SHOULDです)。「should」と言ったのは、多くのAPIがそれを気に掛けていないため、誰もの生活を難しくしているからだ。

実世界で429を見るところ

  • サードパーティAPI:Stripe、OpenAI、GitHub、Contentful、Sanity — それらはすべてレート制限を持っている
  • CDNとホスティングプラットフォーム:Vercel、Cloudflare、AWSは、エッジレート制限を超える場合429を返す
  • 独自のAPI:レート制限を実装している場合(そしてそうすべき場合)
  • ビルドプロセス:すべてのページに対してCMS APIを叩く静的サイト生成は、簡単にレート制限をトリガーできる
  • ウェブスクレイピング:外部ソースからデータを積極的に取得する場合

429エラーの一般的な原因

本番環境で実際に遭遇したシナリオを分解して、おおよその頻度でランク付けしよう。

1. 静的サイトビルドがヘッドレスCMSをハンマーで叩く

これは、ヘッドレスアーキテクチャで作業しているチームが最も悩まされるものだ。2,000ページのサイトがあり、それぞれがCMSからのデータが必要だ。ビルドプロセスはそれらのリクエストすべてを並行して発火し、CMSは大規模なスパイクを見て、429を返し始める。ビルドが失敗する。

ヘッドレスCMSプロジェクトで働くときにこれを定期的に見かける。修正には、リクエストキューイングと並行制限が含まれ、これについては以下で説明する。

2. キャッシュがない、または破損している

ページの読み込みごとに新しいAPIコールをトリガーする場合、キャッシュレイヤーが機能していないため、特にトラフィックスパイクがあると、レート制限に高速にヒットする。revalidateが誤って0に設定されたNext.jsアプリをデバッグしたことがある。つまり、ISRは事実上無効化された。すべての訪問者がContentfulへの新しいAPIコールをトリガーした。実際のトラフィックで429を取得し始めるのに約45分かかった。

3. バックオフなしのリトライループ

コードがエラーを取得し、すぐにリトライし、別のエラーを取得し、すぐにリトライする...おめでとう、レート制限をトリガーするマシンを構築した。ウェブフックハンドラー、バックグラウンドジョブ、さらにはクライアント側のフェッチコールでこのパターンを見た。

4. APIキーを共有する複数のサービス

ステージング環境、本番環境、ローカル開発セットアップ、CI/CDパイプラインはすべて同じAPIキーを使用している。個別には問題なく見えるが、集合的には率制限予算を燃やしている。

5. デバウンスなしのクライアント側フェッチ

入力ごとにAPIコールを発火する検索機能。500msごとにポーリングするダッシュボード。ユーザーがスクロールできるより速くフェッチをトリガーする無限スクロール。これらのパターンは、特にすべてのユーザーに乗じられると、確実に429をトリガーできる。

6. 実際の悪用または攻撃

時々429はそのように実行する必要があることをしている — 不合理な数のリクエストからサーバーを保護している。ボット、資格情報詰め込み、スクレイピング — レート制限は最初の防御線だ。

Retry-Afterヘッダーの説明

Retry-Afterヘッダーはサーバーの方法が再度試すときをあなたに正確に伝える。それは2つのフォーマットで来ることができる:

待機する秒数:

HTTP/1.1 429 Too Many Requests
Retry-After: 60

特定の日時:

HTTP/1.1 429 Too Many Requests
Retry-After: Thu, 01 Jan 2026 00:00:00 GMT

秒数フォーマットはより一般的だ。日付フォーマットはRFC 7231で定義されるHTTP-dateを使用する。

ほとんどのチュートリアルが教えないことはここにある:多くのAPIはRetry-Afterを全く送信しない、または一貫性がなく送信する。OpenAI APIは一般的にそれを含む。GitHub APIはそれをX-RateLimit-Resetと共に含む。より小さなAPIの多くはただ裸の429を送信し、あなたを推測に残す。

一部のAPIは追加のレート制限ヘッダーも送信する:

ヘッダー 目的
X-RateLimit-Limit ウィンドウごとに許可される最大リクエスト 100
X-RateLimit-Remaining 現在のウィンドウで残りのリクエスト 0
X-RateLimit-Reset ウィンドウがリセットされるUnixタイムスタンプ 1735689600
Retry-After リトライ前の待機秒数 30

常にこれらのヘッダーを確認する。これらはより賢いリトライロジックを実装し、制限にヒットする前に事前にスローダウンすることができる。

HTTP 429 Too Many Requests: Causes, Fixes, and Rate Limiting - architecture

クライアントとして429エラーを処理する方法

429エラーを受け取る側の場合、ここで適切に処理する方法がある。

ジッター付き指数バックオフ

これはゴールドスタンダードだ。固定時間待つだけでなく、各リトライでディレイを指数関数的に増加させ、雷群の問題を防ぐためにランダム性(ジッター)を追加する。

async function fetchWithRetry(
  url: string,
  options: RequestInit = {},
  maxRetries: number = 5
): Promise<Response> {
  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    const response = await fetch(url, options);

    if (response.status !== 429) {
      return response;
    }

    if (attempt === maxRetries) {
      throw new Error(`Still getting 429 after ${maxRetries} retries`);
    }

    // Retry-Afterヘッダーをまず確認
    const retryAfter = response.headers.get('Retry-After');
    let delay: number;

    if (retryAfter) {
      // 秒または日付である可能性がある
      const parsed = parseInt(retryAfter, 10);
      if (!isNaN(parsed)) {
        delay = parsed * 1000;
      } else {
        delay = new Date(retryAfter).getTime() - Date.now();
      }
    } else {
      // ジッター付き指数バックオフ
      const baseDelay = Math.pow(2, attempt) * 1000;
      const jitter = Math.random() * 1000;
      delay = baseDelay + jitter;
    }

    console.log(`Rate limited. Waiting ${Math.round(delay / 1000)}s before retry ${attempt + 1}`);
    await new Promise(resolve => setTimeout(resolve, delay));
  }

  // TypeScriptはこれを望むが、私たちはそこに到達しない
  throw new Error('Unexpected end of retry loop');
}

ビルドプロセス用のリクエストキューイング

数百または数千のAPIコールを行う必要がある静的サイト生成の場合、並行性制御を備えたキューを使用する:

import pLimit from 'p-limit';

// 同時リクエストを5に制限
const limit = pLimit(5);

const pages = await getAllPageSlugs(); // ['/', '/about', '/blog/post-1', ...]を返す

const results = await Promise.all(
  pages.map(slug =>
    limit(() => fetchWithRetry(`https://api.cms.com/pages/${slug}`))
  )
);

p-limitライブラリ(2026年時点で週間500万以上のnpmダウンロード)がこのための私の定番だ。リクエスト間の遅延を追加することもできる:

const limit = pLimit(3);

const delay = (ms: number) => new Promise(r => setTimeout(r, ms));

const results = await Promise.all(
  pages.map((slug, i) =>
    limit(async () => {
      if (i > 0) await delay(200); // リクエスト間に200ms
      return fetchWithRetry(`https://api.cms.com/pages/${slug}`);
    })
  )
);

Next.js APIルートにレート制限を実装する

ここで反対側に反転 — APIを構築していてそれを保護する必要がある。Next.jsで構築する場合、APIルートにレート制限を追加する方法がここにある。

シンプルなインメモリレート制限

シングルサーバーデプロイメントまたは開発中の場合、これは機能する:

// lib/rate-limit.ts
type RateLimitEntry = {
  count: number;
  resetTime: number;
};

const rateLimitMap = new Map<string, RateLimitEntry>();

export function rateLimit({
  windowMs = 60 * 1000,
  maxRequests = 100,
}: {
  windowMs?: number;
  maxRequests?: number;
} = {}) {
  return function check(identifier: string): {
    allowed: boolean;
    remaining: number;
    resetIn: number;
  } {
    const now = Date.now();
    const entry = rateLimitMap.get(identifier);

    if (!entry || now > entry.resetTime) {
      rateLimitMap.set(identifier, {
        count: 1,
        resetTime: now + windowMs,
      });
      return { allowed: true, remaining: maxRequests - 1, resetIn: windowMs };
    }

    if (entry.count >= maxRequests) {
      return {
        allowed: false,
        remaining: 0,
        resetIn: entry.resetTime - now,
      };
    }

    entry.count++;
    return {
      allowed: true,
      remaining: maxRequests - entry.count,
      resetIn: entry.resetTime - now,
    };
  };
}

Next.js App Router APIルートで使用する:

// app/api/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { rateLimit } from '@/lib/rate-limit';

const limiter = rateLimit({ windowMs: 60_000, maxRequests: 30 });

export async function GET(request: NextRequest) {
  const ip = request.headers.get('x-forwarded-for') ?? 'anonymous';
  const { allowed, remaining, resetIn } = limiter(ip);

  if (!allowed) {
    return NextResponse.json(
      { error: 'Too many requests. Please slow down.' },
      {
        status: 429,
        headers: {
          'Retry-After': String(Math.ceil(resetIn / 1000)),
          'X-RateLimit-Limit': '30',
          'X-RateLimit-Remaining': '0',
        },
      }
    );
  }

  // ここに実際のルートロジック
  return NextResponse.json(
    { data: 'Here you go' },
    {
      headers: {
        'X-RateLimit-Limit': '30',
        'X-RateLimit-Remaining': String(remaining),
      },
    }
  );
}

Upstash Redisでの本番レート制限

インメモリアプローチはVercelのようなサーバーレスプラットフォームで実行しているときに崩壊する。各関数呼び出しは異なるインスタンスにヒットする可能性があるためだ。共有ストアが必要だ。Upstash Redisは2026年現在、これに対する最も人気のある選択肢だ。

npm install @upstash/ratelimit @upstash/redis
// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

export const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(30, '60 s'),
  analytics: true,
  prefix: 'api-ratelimit',
});
// app/api/data/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { ratelimit } from '@/lib/rate-limit';

export async function GET(request: NextRequest) {
  const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
  const { success, limit, remaining, reset } = await ratelimit.limit(ip);

  if (!success) {
    const retryAfter = Math.ceil((reset - Date.now()) / 1000);
    return NextResponse.json(
      { error: 'Rate limit exceeded' },
      {
        status: 429,
        headers: {
          'Retry-After': String(retryAfter),
          'X-RateLimit-Limit': String(limit),
          'X-RateLimit-Remaining': '0',
          'X-RateLimit-Reset': String(reset),
        },
      }
    );
  }

  return NextResponse.json({ data: 'Success' }, {
    headers: {
      'X-RateLimit-Limit': String(limit),
      'X-RateLimit-Remaining': String(remaining),
    },
  });
}

Upstashの無料ティアは1日あたり10,000リクエストを提供し、小さなプロジェクトには十分だ。彼らのProプランは2026年初期現在、毎日500K個のコマンドで月額10ドルから始まる。

ミドルウェアレベルのレート制限

すべてのAPIルートでレート制限が必要な場合、Next.jsミドルウェアが場所だ:

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { ratelimit } from '@/lib/rate-limit';

export async function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/api/')) {
    const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
    const { success, reset } = await ratelimit.limit(ip);

    if (!success) {
      return NextResponse.json(
        { error: 'Too many requests' },
        {
          status: 429,
          headers: {
            'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
          },
        }
      );
    }
  }

  return NextResponse.next();
}

export const config = {
  matcher: '/api/:path*',
};

レート制限戦略の比較

すべてのレート制限アルゴリズムが等しいわけではない。主なものがどのように比較されるか:

アルゴリズム 動作方法 利点 欠点 最適用途
固定ウィンドウ 固定時間ウィンドウ(分当たりなど)内のリクエストをカウント 実装が簡単 ウィンドウ境界のバーストは2倍の制限を許可する可能性 シンプルなAPI、内部ツール
スライディングウィンドウ ローリング時間期間上のリクエストをカウント より滑らかな分布 わずかに複雑、メモリが多い ほとんどの本番API
トークンバケット トークンは一定のレートで補充、各リクエストはトークンをコスト 制御されたバーストを許可 より複雑な状態管理 バーストを許容する必要があるAPI
漏れるバケット リクエストはキューに入り、一定のレートで処理 非常に滑らかな出力レート レイテンシーを追加する可能性、リクエストは削除される可能性 ウェブフック配信、ジョブ処理
スライディングウィンドウログ 各リクエストのタイムスタンプを保存 最も正確 スケールでのメモリ使用量が多い 低ボリューム、高精度の必要性

ほとんどのウェブアプリケーションでは、スライディングウィンドウが最適なポイントだ。それはUpstashが標準で使用するものであり、他の理由がない限り私が推奨するものだ。

Astroおよびその他のフレームワークでのレート制限

Astroで構築する場合、Astroは主に静的優先フレームワークであるため、レート制限は異なる方法で機能する。ただし、Astroのサーバーエンドポイント(SSRモードで利用可能)を使用する場合、概念は同じだ:

// src/pages/api/data.ts
import type { APIRoute } from 'astro';
import { Ratelimit } from '@upstash/ratelimit';
import { Redis } from '@upstash/redis';

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(30, '60 s'),
});

export const GET: APIRoute = async ({ request }) => {
  const ip = request.headers.get('x-forwarded-for') ?? '127.0.0.1';
  const { success, reset } = await ratelimit.limit(ip);

  if (!success) {
    return new Response(JSON.stringify({ error: 'Rate limit exceeded' }), {
      status: 429,
      headers: {
        'Content-Type': 'application/json',
        'Retry-After': String(Math.ceil((reset - Date.now()) / 1000)),
      },
    });
  }

  return new Response(JSON.stringify({ data: 'Hello' }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
};

Cloudflare Workersでエッジにデプロイされたアプリケーションの場合、Cloudflareの組み込みレート制限ルールの使用も検討できます。これは、アプリケーションレベルのソリューションよりもはるかに多くのトラフィックを処理でき、インフラストラクチャレベルで動作する。Businessプランでは、Advanced Rate Limitingが良好なリクエスト10,000当たり0.05ドルから始まる。

本番環境での429エラーの監視とデバッグ

見えないものは修正できない。本番環境で429エラーを処理するためのチェックリストがここにある:

429を受け取る場合

  1. どのAPIが429を返しているか確認 — ステータスコードだけでなくレスポンスURLを見る
  2. Retry-Afterヘッダーをログ — それが一貫して非常に長い場合、より高い層プランが必要な場合がある
  3. リクエストパターンを監査 — 冗長なコールを作成していますか?リクエストをバッチできますか?
  4. キャッシュを実装stale-while-revalidate、Redisキャッシング、またはNext.js ISRを使用してAPIコールを減らす
  5. 複数の環境がAPIキーを共有しているか確認 — これは最も一般的な「謎」の429原因だ

429を送信する場合

  1. ダッシュボードをセットアップ — 時間とともに429レスポンスレートを追跡
  2. トップ違反者を特定 — どのIPアドレスまたはAPIキーが最も制限にヒットしていますか?
  3. 制限を確認 — それらは制限しすぎていますか?緩すぎますか?サーバー容量を確認して調整
  4. 常にRetry-Afterを送信 — 良いAPI市民になる
  5. 有用なエラーメッセージを含める — クライアントにどの制限にヒットしたか、いつリトライするかを伝える

よくできた429レスポンスボディは次のようになる:

{
  "error": {
    "type": "rate_limit_exceeded",
    "message": "You've exceeded 30 requests per minute. Please wait before retrying.",
    "retryAfter": 42,
    "documentation": "https://docs.yourapi.com/rate-limits"
  }
}

これは単なる{ "error": "Too many requests" }よりも無限に有用だ。

ヘッドレスアーキテクチャでの永続的なレート制限の問題に対処している場合 — ビルド中、実行時中、またはその両方 — アーキテクチャについて議論するために連絡を取る価値があるかもしれない。異なるCMSとフレームワークの組み合わせ全体で多くのこれらの問題を見ており、通常は症状にバンドエイドするだけでなくパターンレベルの修正がある。

FAQ

HTTP 429 Too Many Requestsの意味は何ですか?

HTTP 429は、与えられた時間内にサーバーに多数のリクエストを送信したことを意味するステータスコード。サーバーがレート制限をしている — スローダウンするように要求している。これは認証エラーやサーバーエラーではない。リクエストはおそらく有効であり、単に多すぎる。サーバーはクライアントに再度試すときを伝えるRetry-Afterヘッダーを含める必要がある。

429エラーをどのように修正しますか?

APIから429エラーを受け取る場合、リトライロジックで指数バックオフとジッターを実装し、リクエスト頻度を減らし、冗長なコールを避けるためのキャッシュを追加し、Retry-Afterヘッダーを尊重する。ビルド中に制限を再度ヒットしている場合、並行性制御でリクエストキューイングを使用する。それが一貫して起きている場合、より寛大なレート制限を備えた高いAPIプランにアップグレードする必要があるかもしれない。

Retry-Afterヘッダーとは何ですか?

Retry-Afterヘッダーは429(または503)レスポンスで送信され、別のリクエストを行う前にクライアントがどのくらい待つか伝える。秒数(例えば、Retry-After: 60)またはHTTP日付(例えば、Retry-After: Thu, 01 Jan 2026 00:00:00 GMT)として指定できる。すべてのAPIがこのヘッダーを含むわけではないが、設計がよいものは含む。

Next.jsにレート制限を実装するにはどうしますか?

開発またはシングルサーバーデプロイメント用の場合、IPアドレスごとのリクエストカウントを追跡するためにインメモリMapを使用できる。Vercelのようなプラットフォームでの本番サーバーレスデプロイメント用の場合、@upstash/ratelimitパッケージを使用してUpstash Redisを使用する。個々のルートレベルまたはNext.jsミドルウェアを使用してすべてのAPIルートでレート制限を適用できる。

429と503エラーの違いは何ですか?

429 Too Many Requestsはレート制限に関するもので、あなたのクライアントが多数のリクエストを送信している。503 Service Unavailableはサーバーがオーバーロードされているか、メンテナンス中であり、誰かからリクエストを処理できないことを意味している。どちらもRetry-Afterヘッダーを含めることができるが、非常に異なる問題を示している。429があなたをターゲットにしている。503はすべての人に影響を与える。

レート制限はDDoS攻撃を防ぐことができますか?

レート制限はDDoS攻撃への防御の1つレイヤーですが、単独では十分ではない。アプリケーションレベルのレート制限(Next.jsに実装するもの)は中程度の悪用を処理できますが、深刻なDDoS攻撃はインフラストラクチャレベルで軽減される必要がある — Cloudflare、AWS Shield、またはホスティングプロバイダーの組み込み保護などのサービスを使用。アプリレベルのレート制限をバウンサーと考え、インフラレベルの保護を砦の壁と考える。

APIに対してどのレート制限を設定すべきですか?

それはユースケースに完全に依存している。多くのパブリックAPIの一般的な出発点は、IPごとに分当たり60リクエスト、またはAPIキーごとに1時間に1,000リクエスト。認証されたユーザーの場合、より多くを許可できる。重要なのは実際の使用パターンを監視し、正規の使用にヘッドルーム込みで対応する制限を設定し、実データに基づいて調整することだ。より制限的に始めゆるく — それはユーザーが高いレートに依存した後の制限を緊める方が簡単だ。

静的サイトビルド中に429エラーが発生するのはなぜですか?

Next.jsやAstroのような静的サイトジェネレータはビルド時にすべてのページのデータを取得する。数百または数千のページがある場合、それは数百または数千のAPIコール呼び出し。ほとんどのCMS APIには、秒当たり5〜20リクエストのレート制限がある。p-limitまたは同様のライブラリを使用して同時実行を3〜5の同時リクエストに制限し、バッチ間に小さな遅延を追加し、(Next.jsのISRやAstroのインクリメンタルコンテンツコレクション)などを使用してインクリメンタルビルドを検討して、すべてを一度にリビルドしない。