HTTP 429 Too Many Requests: Causes, Fixes, and Rate Limiting
Vous déployez un vendredi après-midi (je sais, je sais), tout semble bon, et puis votre monitoring s'illumine comme un sapin de Noël. Les utilisateurs reçoivent des erreurs 429. Votre API rejette les requêtes. Ou peut-être que c'est l'inverse — vous appelez une API tierce et eux vous rejettent. Dans tous les cas, le code de statut HTTP 429 Too Many Requests vient de devenir la chose la plus importante de votre journée.
J'ai connu les deux situations. J'ai été le développeur qui accidentellement DDoS-ait une API CMS à cause d'un processus de build mal configuré, et j'ai aussi été celui implémentant le rate limiting pour protéger nos propres serveurs des clients incontrôlés. Ces deux expériences m'ont enseigné des choses que la documentation ne couvre pas. Parcourons tout cela ensemble.
Table des matières
- Que signifie réellement HTTP 429?
- Causes courantes des erreurs 429
- L'en-tête Retry-After expliqué
- Comment gérer les erreurs 429 en tant que client
- Implémenter le rate limiting dans les routes API Next.js
- Stratégies de rate limiting comparées
- Rate limiting dans Astro et autres frameworks
- Monitoring et débogage des erreurs 429 en production
- FAQ

Que signifie réellement HTTP 429?
HTTP 429 est défini dans RFC 6585, publié en 2012. La spécification est étonnamment courte. Voici l'essentiel: l'utilisateur (ou le client) a envoyé trop de requêtes dans un laps de temps donné.
C'est tout. C'est une réponse de rate limiting. Le serveur dit: "J'ai compris ta requête, elle est probablement valide, mais tu dois ralentir."
C'est différent d'une 403 Forbidden (tu n'es pas autorisé) ou d'une 503 Service Unavailable (le serveur entier a des problèmes). Un 429 est ciblé. C'est à propos du taux de tes requêtes spécifiquement.
La réponse DEVRAIT inclure un en-tête Retry-After indiquant au client combien de temps attendre avant de réessayer. J'ai dit "devrait" parce que beaucoup d'APIs ne s'en soucient pas, ce qui complique la vie de tout le monde.
Où tu verras des 429s en pratique
- APIs tierces: Stripe, OpenAI, GitHub, Contentful, Sanity — elles ont toutes des limites de taux
- CDNs et plateformes d'hébergement: Vercel, Cloudflare, et AWS retourneront des 429s si tu atteins leurs limites de taux edge
- Tes propres APIs: Si tu as implémenté le rate limiting (et tu devrais)
- Processus de build: La génération statique qui contacte une API CMS pour chaque page peut facilement déclencher les limites
- Web scraping: Si tu récupères des données de sources externes agressivement
Causes courantes des erreurs 429
Laisse-moi décomposer les scénarios que j'ai réellement rencontrés en production, classés à peu près par leur fréquence.
1. Les builds de sites statiques martèlent un CMS headless
C'est celui qui affecte le plus les équipes travaillant avec des architectures headless. Tu as un site avec 2 000 pages, chacune ayant besoin de données de ton CMS. Ton processus de build lance toutes ces requêtes en parallèle, le CMS voit un pic massif, et commence à retourner des 429s. Ton build échoue.
Nous voyons cela régulièrement en travaillant sur des projets de CMS headless. Le correctif implique la mise en queue des requêtes et les limites de concurrence, que je vais couvrir ci-dessous.
2. Caching manquant ou brisé
Si chaque chargement de page déclenche un appel API frais parce que ta couche de caching ne fonctionne pas, tu atteindras les limites rapidement — surtout avec les pics de trafic. Une fois, j'ai débogué une application Next.js où revalidate était accidentellement défini sur 0, ce qui signifiait que l'ISR était effectivement désactivé. Chaque visiteur déclenchait un nouvel appel API à Contentful. Il a fallu environ 45 minutes de trafic réel pour commencer à recevoir des 429s.
3. Boucles de retry sans backoff
Ton code reçoit une erreur, réessaie immédiatement, reçoit une autre erreur, réessaie immédiatement... félicitations, tu as construit une machine qui déclenche le rate limiting. J'ai vu ce motif dans les gestionnaires de webhook, les jobs en arrière-plan, et même dans les appels de fetch côté client.
4. Plusieurs services partageant une clé API
Ton environnement staging, ton environnement production, ta configuration dev locale, et ton pipeline CI/CD utilisent tous la même clé API. Chacun semble bien individuellement, mais collectivement ils brûlent ton budget limite de taux rapidement.
5. Fetch côté client sans debouncing
Une fonctionnalité de search-as-you-type qui déclenche un appel API à chaque frappe. Un tableau de bord qui interroge toutes les 500ms. Un scroll infini qui déclenche des récupérations plus vite que l'utilisateur ne peut faire défiler. Ces motifs peuvent absolument déclencher des 429s, surtout multiplié par tous tes utilisateurs.
6. Véritable abus ou attaque
Parfois un 429 fait exactement ce qu'il devrait — protéger ton serveur de quelqu'un envoyant un nombre déraisonnable de requêtes. Bots, credential stuffing, scraping — le rate limiting est ta première ligne de défense.
L'en-tête Retry-After expliqué
L'en-tête Retry-After est la façon du serveur de te dire exactement quand réessayer. Il peut venir en deux formats:
Secondes à attendre:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Date/heure spécifique:
HTTP/1.1 429 Too Many Requests
Retry-After: Thu, 01 Jan 2026 00:00:00 GMT
Le format en secondes est beaucoup plus courant. Le format date utilise HTTP-date tel que défini dans RFC 7231.
Voici ce que la plupart des tutoriels ne te diront pas: beaucoup d'APIs n'envoient pas Retry-After du tout, ou l'envoient de manière incohérente. L'API OpenAI l'inclut généralement. L'API GitHub l'inclut avec X-RateLimit-Reset. Beaucoup de petites APIs envoient juste un 429 nu et te laissent deviner.
Certaines APIs envoient également des en-têtes limite de taux supplémentaires:
| En-tête | Objectif | Exemple |
|---|---|---|
X-RateLimit-Limit |
Max requêtes autorisées par fenêtre | 100 |
X-RateLimit-Remaining |
Requêtes restantes dans la fenêtre actuelle | 0 |
X-RateLimit-Reset |
Timestamp Unix quand la fenêtre réinitialise | 1735689600 |
Retry-After |
Secondes à attendre avant de réessayer | 30 |
Vérifie toujours ces en-têtes. Ils te permettent d'implémenter une logique de retry plus intelligente et même de ralentir de manière proactive avant d'atteindre la limite.

Comment gérer les erreurs 429 en tant que client
Quand tu es celui qui reçoit des erreurs 429, voici comment les gérer correctement.
Exponential Backoff avec Jitter
C'est l'étalon-or. N'attends pas juste un laps de temps fixe — augmente le délai exponentiellement avec chaque retry, et ajoute du hasard (jitter) pour éviter les problèmes de troupeau en fuite.
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`);
}
// Vérifie d'abord l'en-tête Retry-After
const retryAfter = response.headers.get('Retry-After');
let delay: number;
if (retryAfter) {
// Pourrait être des secondes ou une date
const parsed = parseInt(retryAfter, 10);
if (!isNaN(parsed)) {
delay = parsed * 1000;
} else {
delay = new Date(retryAfter).getTime() - Date.now();
}
} else {
// Exponential backoff avec jitter
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 veut ceci, bien que nous ne l'atteindrons jamais
throw new Error('Unexpected end of retry loop');
}
Request Queuing pour les processus de build
Pour la génération statique où tu dois faire des centaines ou des milliers d'appels API, utilise une queue avec contrôle de concurrence:
import pLimit from 'p-limit';
// Limite à 5 requêtes concurrentes
const limit = pLimit(5);
const pages = await getAllPageSlugs(); // Retourne ['/', '/about', '/blog/post-1', ...]
const results = await Promise.all(
pages.map(slug =>
limit(() => fetchWithRetry(`https://api.cms.com/pages/${slug}`))
)
);
La bibliothèque p-limit (2,5M+ téléchargements npm hebdomadaires en 2025) est mon choix pour cela. Tu peux aussi ajouter un délai entre les requêtes:
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 entre les requêtes
return fetchWithRetry(`https://api.cms.com/pages/${slug}`);
})
)
);
Implémenter le rate limiting dans les routes API Next.js
Maintenant, basculons vers l'autre côté — tu construis une API et dois la protéger. Si tu construis avec Next.js, voici comment ajouter le rate limiting à tes routes API.
Rate Limiter en mémoire simple
Pour un déploiement serveur unique ou pendant le développement, cela fonctionne:
// 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,
};
};
}
L'utiliser dans une route API du routeur Next.js App:
// 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',
},
}
);
}
// Ici va ta logique de route réelle
return NextResponse.json(
{ data: 'Here you go' },
{
headers: {
'X-RateLimit-Limit': '30',
'X-RateLimit-Remaining': String(remaining),
},
}
);
}
Rate limiting en production avec Upstash Redis
L'approche en mémoire s'effondre quand tu exécutes sur des plateformes serverless comme Vercel, parce que chaque invocation de fonction pourrait toucher une instance différente. Tu as besoin d'un magasin partagé. Upstash Redis est le choix le plus populaire pour cela en 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),
},
});
}
Le niveau gratuit d'Upstash te donne 10 000 requêtes/jour, ce qui est beaucoup pour les petits projets. Leur plan Pro commence à 10 $/mois pour 500K commandes quotidiennes au début 2025.
Rate limiting au niveau Middleware
Si tu veux appliquer le rate limiting à toutes tes routes API, le middleware Next.js est la place:
// 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*',
};
Stratégies de rate limiting comparées
Tous les algorithmes de rate limiting ne sont pas égaux. Voici comment les principaux se comparent:
| Algorithme | Comment ça fonctionne | Avantages | Inconvénients | Meilleur pour |
|---|---|---|---|---|
| Fixed Window | Compte les requêtes dans des fenêtres de temps fixes (par ex. par minute) | Simple à implémenter | La rafale à la limite de fenêtre peut permettre 2x la limite | APIs simples, outils internes |
| Sliding Window | Compte les requêtes sur une période de temps roulante | Distribution plus lisse | Un peu plus complexe, plus de mémoire | La plupart des APIs en production |
| Token Bucket | Les tokens se remplissent à un taux constant, chaque requête coûte un token | Permet les rafales contrôlées | Gestion d'état plus complexe | APIs qui ont besoin de tolérance de rafales |
| Leaky Bucket | Les requêtes entrent dans une queue et sont traitées à un taux fixe | Très lisse taux de sortie | Peut ajouter de la latence, les requêtes peuvent être abandonnées | Livraison de webhook, traitement de jobs |
| Sliding Window Log | Stocke l'horodatage de chaque requête | Plus précis | Utilisation mémoire élevée à l'échelle | Besoins à faible volume et haute précision |
Pour la plupart des applications web, sliding window est le point doux. C'est ce qu'Upstash utilise par défaut, et c'est ce que je recommanderais à moins que tu aies une raison spécifique de choisir quelque chose d'autre.
Rate limiting dans Astro et autres frameworks
Si tu construis avec Astro, le rate limiting fonctionne différemment parce qu'Astro est principalement un framework static-first. Mais avec les endpoints de serveur d'Astro (disponibles en mode SSR), les concepts sont les mêmes:
// 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' },
});
};
Pour les applications déployées edge sur Cloudflare Workers, tu pourrais aussi considérer les règles Rate Limiting intégrées de Cloudflare, qui opèrent au niveau de l'infrastructure et peuvent gérer beaucoup plus de trafic que les solutions au niveau application. Leur Rate Limiting avancé commence à 0,05 $/10 000 bonnes requêtes sur le plan Business.
Monitoring et débogage des erreurs 429 en production
Tu ne peux pas corriger ce que tu ne peux pas voir. Voici ma checklist pour gérer les erreurs 429 en production:
Quand tu reçois des 429s
- Vérifie quelle API retourne 429 — Regarde l'URL de réponse, pas juste le code de statut
- Log l'en-tête
Retry-After— S'il est constamment très long, tu as peut-être besoin d'un plan plus élevé - Audite tes motifs de requête — Fais-tu des appels redondants? Peux-tu faire des requêtes par batch?
- Implémente le caching — Utilise
stale-while-revalidate, le caching Redis, ou l'ISR Next.js pour réduire les appels API - Vérifie si plusieurs environnements partagent les clés API — C'est la cause "mystère" 429 la plus courante
Quand tu envoies des 429s
- Configure les tableaux de bord — Suivi les taux de réponse 429 au fil du temps
- Identifie les contrevenants majeurs — Quelles adresses IP ou clés API atteignent les limites le plus?
- Examine tes limites — Sont-elles trop restrictives? Trop libérales? Vérifie ta capacité de serveur et ajuste
- Envoie toujours
Retry-After— Sois un bon citoyen API - Inclus un message d'erreur utile — Dis au client quelle limite ils ont dépassée et quand réessayer
Une réponse 429 bien conçue ressemble à ceci:
{
"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"
}
}
C'est infiniment plus utile que juste { "error": "Too many requests" }.
Si tu fais face à des problèmes de rate limiting persistants sur une architecture headless — que ce soit pendant les builds, au runtime, ou les deux — ça pourrait valoir le coup de nous contacter pour discuter de ton architecture. Nous avons vu beaucoup de ces problèmes à travers différentes combinaisons de CMS et de frameworks et il y a généralement un correctif au niveau du motif plutôt que juste de mettre un pansement sur les symptômes.
FAQ
Que signifie HTTP 429 Too Many Requests?
HTTP 429 est un code de statut qui signifie que tu as envoyé trop de requêtes à un serveur dans un laps de temps donné. Le serveur t'applique un rate limit — il te demande de ralentir. Ce n'est pas une erreur d'authentification ou une erreur de serveur; tes requêtes sont probablement valides, il y en a juste trop. Le serveur devrait inclure un en-tête Retry-After te disant quand réessayer.
Comment corriger une erreur 429?
Si tu reçois des erreurs 429 d'une API, implémente un exponential backoff avec jitter dans ta logique de retry, réduis ta fréquence de requête, ajoute du caching pour éviter les appels redondants, et respecte l'en-tête Retry-After. Si tu atteins la limite pendant les builds, utilise une queue de requête avec limites de concurrence. Si cela se produit constamment, tu as peut-être besoin de passer à un plan API supérieur avec des limites de taux plus généreuses.
Qu'est-ce que l'en-tête Retry-After?
L'en-tête Retry-After est envoyé avec une réponse 429 (ou 503) pour dire au client combien de temps attendre avant de faire une autre requête. Il peut être spécifié comme un nombre de secondes (ex. Retry-After: 60) ou comme une date HTTP (ex. Retry-After: Thu, 01 Jan 2026 00:00:00 GMT). Pas toutes les APIs incluent cet en-tête, mais les bien conçues le font.
Comment j'implémente le rate limiting dans Next.js?
Pour le développement ou les déploiements serveur unique, tu peux utiliser un Map en mémoire pour tracker les compteurs de requête par adresse IP. Pour les déploiements serverless en production sur des plateformes comme Vercel, utilise Upstash Redis avec le package @upstash/ratelimit. Tu peux appliquer le rate limiting au niveau d'une route individuelle ou à travers toutes tes routes API en utilisant le middleware Next.js.
Quelle est la différence entre les erreurs 429 et 503?
Un 429 Too Many Requests est spécifiquement à propos du rate limiting — ton client envoie trop de requêtes. Un 503 Service Unavailable signifie que le serveur est surchargé ou en maintenance et ne peut gérer aucune requête de personne. Les deux peuvent inclure un en-tête Retry-After, mais ils indiquent des problèmes très différents. Un 429 te cible; un 503 affecte tout le monde.
Le rate limiting peut-il prévenir les attaques DDoS?
Le rate limiting est une couche de défense contre les attaques DDoS, mais ce n'est pas suffisant seul. Le rate limiting au niveau application (comme ce que tu implémenterais dans Next.js) peut gérer les abus modérés, mais une sérieuse attaque DDoS doit être atténuée au niveau infrastructure — en utilisant des services comme Cloudflare, AWS Shield, ou les protections intégrées de ton fournisseur d'hébergement. Pense au rate limiting au niveau app comme un videur, et à la protection au niveau infrastructure comme les murs de la forteresse.
Quelle limite de taux dois-je fixer pour mon API?
Cela dépend entièrement de ton cas d'usage. Un point de départ courant pour les APIs publiques est 60 requêtes par minute par IP, ou 1 000 requêtes par heure par clé API. Pour les utilisateurs authentifiés, tu pourrais en permettre plus. La clé est de monitorrer les motifs d'utilisation réels, fixer les limites qui accommodent l'utilisation légitime avec une certaine marge, et ajuste en fonction des données réelles. Commence plus restrictif et assouplit — c'est plus facile que de resserrer les limites après que les utilisateurs dépendent de taux plus élevés.
Pourquoi j'obtiens des erreurs 429 pendant mon build de site statique?
Les générateurs de sites statiques comme Next.js et Astro récupèrent les données pour chaque page au moment du build. Si tu as des centaines ou des milliers de pages, c'est des centaines ou des milliers d'appels API en succession rapide. La plupart des APIs CMS ont des limites entre 5-20 requêtes par seconde. Utilise p-limit ou des bibliothèques similaires pour limiter la concurrence à 3-5 requêtes simultanées, ajoute des petits délais entre les batches, et considère l'utilisation de builds incrémentiels (ISR dans Next.js, ou les collections de contenu incrémentielles d'Astro) pour éviter de reconstruire tout à la fois.