金曜日の午後にデプロイしています(わかってます)、すべてが良好に見えます。その瞬間、モニタリングがクリスマスツリーのように光ります。ユーザーが429エラーを受け取っています。APIがリクエストを拒否しています。またはその逆かもしれません。サードパーティのAPIを呼び出していて、彼らあなたを拒否しています。いずれにせよ、HTTP 429 Too Many Requestsステータスコードが1日で最も重要なものになったばかりです。

私はこの両側を経験しています。misconfiguredなビルドプロセスのために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」と言ったのは、多くのAPIが手間をかけないためで、誰もの生活を難しくしています。

実際の場所で429を見かけるところ

  • サードパーティAPI: Stripe、OpenAI、GitHub、Contentful、Sanity — すべてレート制限があります
  • CDNとホスティングプラットフォーム: Vercel、Cloudflare、およびAWSは、エッジレート制限に達すると429を返します
  • 独自のAPI: レート制限を実装した場合(実装すべきです)
  • ビルドプロセス: すべてのページに対してCMS APIにアクセスする静的サイト生成は、レート制限を簡単にトリガーできます
  • Webスクレイピング: 外部ソースからデータを積極的に取得している場合

429エラーの一般的な原因

本番環境で実際に遭遇したシナリオを説明します。発生頻度でおおよくランク付けしています。

1. ヘッドレスCMSをハンマーのように叩く静的サイトビルド

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

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

2. 欠落または破損したキャッシング

キャッシングレイヤーが機能していないため、すべてのページロードが新しいAPI呼び出しをトリガーする場合、特にトラフィックスパイク時にはレート制限に速く達します。revalidateが誤って0に設定されたNext.jsアプリをデバッグしたことがあります。つまり、ISRが効果的に無効になっていました。すべてのビジターはContentfulへの新しいAPI呼び出しをトリガーしました。実際のトラフィックで429を取得し始めるのに約45分かかりました。

3. バックオフなしの再試行ループ

コードがエラーを取得し、すぐに再試行し、別のエラーを取得し、すぐに再試行します。おめでとうございます、レート制限をトリガーするマシンを構築しました。このパターンはWebhookハンドラー、バックグラウンドジョブ、さらにはクライアント側のフェッチ呼び出しで見かけました。

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ライブラリ(2025年時点で週200万以上の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は2025年の最も一般的な選択肢です。

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プランは2025年初頭の時点で$10/月から始まり、1日あたり500Kコマンドです。

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

すべての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 | | リーキーバケット | リクエストはキューに入り、固定速度で処理される | 非常に滑らかな出力速度 | レイテンシーを追加でき、リクエストが削除される可能性 | Webhook配信、ジョブ処理 | | スライディングウィンドウログ | 各リクエストのタイムスタンプを保存 | 最も正確 | スケール時にメモリ使用量が多い | 低ボリューム、高精度ニーズ |

ほとんどのWebアプリケーションの場合、スライディングウィンドウはスイートスポットです。これは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の組み込みレート制限ルール(インフラストラクチャレベルで動作し、アプリケーションレベルのソリューションよりもはるかに多くのトラフィックを処理できる)も検討できます。彼らの高度なレート制限は2025年のビジネスプランで1万件のリクエストあたり$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でレート制限をどのように実装しますか?

開発またはシングルサーバーのデプロイメントでは、インメモリMapを使用してIPアドレスごとのリクエスト数を追跡できます。VercelなどのプラットフォームでのProduction serverlessデプロイメントでは、@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ごとに1分あたり60リクエスト、またはAPI キーあたり1時間あたり1,000リクエストです。認証されたユーザーの場合、より多くを許可する場合があります。重要なのは、実際の使用パターンを監視し、正当な使用を一部のヘッドルームと一緒に調整し、実データに基づいて調整することです。より制限的に開始し、緩和してください — ユーザーがより高いレートに依存した後、制限を厳しくするのは簡単です。

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

Next.jsとAstroなどの静的サイトジェネレーターは、ビルド時にすべてのページのデータを取得します。数百または数千のページがある場合、それは数百または数千のAPI呼び出しを迅速に連続させています。ほとんどのCMS APIのレート制限は、秒あたり5~20リクエストの間です。p-limitまたは同様のライブラリを使用して、並行処理を3~5の同時リクエストで上限にし、バッチ間に小さな遅延を追加し、すべてを一度にリビルドするのを避けるために増分ビルド(Next.jsのISR、またはAstroの増分コンテンツコレクション)を使用することを検討してください。