HTTP 429 Too Many Requests: Ce que les développeurs oublient sur les limites de débit
Votre déploiement sort vendredi à 16h. La surveillance passe au rouge sur trois tableaux de bord. Les utilisateurs voient des erreurs 429 sur le paiement, sur la connexion, sur chaque appel API qui compte. Votre limiteur de débit rejette les demandes — ou pire, une API tierce vous rejette, et votre logique de retry aggrave les choses. Le code d'état HTTP 429 Too Many Requests vient de devenir la seule chose entre vous et un week-end productif. La plupart des développeurs savent que 429 signifie « ralentis ». Ce qu'ils oublient, c'est quelles demandes réessayer, quand faire un backoff exponentiel, et comment définir des en-têtes Retry-After qui préviennent vraiment la spirale infernale. Voici ce qui se passe vraiment quand votre API commence à rejeter le trafic.
J'ai été des deux côtés. J'ai été le développeur qui a accidentellement DDoS-é une API CMS à cause d'un processus de build mal configuré, et j'ai été celui qui implémente le rate limiting pour protéger nos serveurs des clients incontrôlés. Ces deux expériences m'ont appris des choses que la documentation ne couvre pas. Parcourons tout cela ensemble.
Table des matières
- Que signifie vraiment 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émentation du rate limiting dans Next.js API Routes
- Stratégies de rate limiting comparées
- Rate Limiting dans Astro et autres frameworks
- Surveillance et débogage des erreurs 429 en production
- FAQ

Que signifie vraiment 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 client) a envoyé trop de demandes en un laps de temps donné.
C'est tout. C'est une réponse de limitation de débit. Le serveur dit « j'ai compris votre demande, elle est probablement valide, mais vous devez ralentir ».
C'est différent d'une 403 Forbidden (vous n'êtes pas autorisé) ou d'une 503 Service Unavailable (le serveur entier a des difficultés). Un 429 est ciblé. C'est sur votre débit de demande 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 » car beaucoup d'APIs ne s'en donnent pas la peine, ce qui rend la vie plus difficile à tout le monde.
Où vous verrez des 429 en pratique
- APIs tierces : Stripe, OpenAI, GitHub, Contentful, Sanity — elles ont toutes des limites de débit
- CDNs et plateformes d'hébergement : Vercel, Cloudflare et AWS renverront des 429 si vous atteignez leurs limites de débit edge
- Vos propres APIs : Si vous avez implémenté le rate limiting (et vous devriez)
- Processus de build : La génération de site statique qui interroge une API CMS pour chaque page peut facilement déclencher des limites de débit
- Web scraping : Si vous récupérez agressivement des données de sources externes
Causes courantes des erreurs 429
Voici les scénarios que j'ai réellement rencontrés en production, classés à peu près par fréquence.
1. Les builds de sites statiques martèlent un headless CMS
C'est celui qui affecte le plus les équipes travaillant avec des architectures headless. Vous avez un site avec 2 000 pages, chacune ayant besoin de données de votre CMS. Votre processus de build envoie toutes ces demandes en parallèle, le CMS voit un pic massif et commence à renvoyer des 429. Votre build échoue.
Nous voyons cela régulièrement lors de travaux sur des projets headless CMS. La solution implique la mise en file d'attente des demandes et les limites de concurrence, que je couvrirai ci-dessous.
2. Caching manquant ou cassé
Si chaque chargement de page déclenche un appel API frais parce que votre couche de caching ne fonctionne pas, vous atteindrez les limites de débit rapidement — surtout avec des pics de trafic. J'ai une fois débogué une app Next.js où revalidate était accidentellement défini à 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 à obtenir des 429.
3. Boucles de retry sans backoff
Votre code obtient une erreur, réessaye immédiatement, obtient une autre erreur, réessaye immédiatement... félicitations, vous avez construit une machine qui déclenche les limites de débit. J'ai vu ce motif dans les gestionnaires de webhooks, les tâches de fond et même les appels fetch côté client.
4. Plusieurs services partageant une clé API
Votre environnement de staging, votre environnement de production, votre configuration de dev local et votre pipeline CI/CD utilisent tous la même clé API. Chacun semble fine individuellement, mais collectivement ils consomment votre budget de limites de débit.
5. Fetch côté client sans debouncing
Une fonctionnalité de recherche en temps réel qui envoie un appel API à chaque frappe. Un tableau de bord qui sonde toutes les 500ms. Un scroll infini qui déclenche des récupérations plus vite que l'utilisateur ne peut scroller. Ces motifs peuvent absolument déclencher des 429, surtout quand multipliés par tous vos utilisateurs.
6. Abus ou attaque réels
Parfois, un 429 fait exactement ce qu'il devrait — protéger votre serveur de quelqu'un envoyant un nombre déraisonnable de demandes. Bots, credential stuffing, scraping — le rate limiting est votre 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 vous dire exactement quand réessayer. Il peut se présenter sous 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 de date utilise HTTP-date tel que défini dans RFC 7231.
Voici ce que la plupart des tutoriels ne vous diront pas : beaucoup d'APIs n'envoient pas Retry-After du tout, ou l'envoient de façon incohérente. L'API d'OpenAI l'inclut généralement. L'API de GitHub l'inclut avec X-RateLimit-Reset. Beaucoup de petites APIs envoient juste un 429 nu et vous laissent deviner.
Certaines APIs envoient aussi d'autres en-têtes de limites de débit :
| En-tête | Objectif | Exemple |
|---|---|---|
X-RateLimit-Limit |
Max de demandes autorisées par fenêtre | 100 |
X-RateLimit-Remaining |
Demandes restantes dans la fenêtre actuelle | 0 |
X-RateLimit-Reset |
Timestamp Unix quand la fenêtre se réinitialise | 1735689600 |
Retry-After |
Secondes à attendre avant retry | 30 |
Vérifiez toujours ces en-têtes. Ils vous permettent d'implémenter une logique de retry plus intelligente et même de ralentir proactivement avant d'atteindre la limite.

Comment gérer les erreurs 429 en tant que client
Quand vous êtes celui qui reçoit des erreurs 429, voici comment les gérer correctement.
Backoff exponentiel avec jitter
C'est l'étalon-or. N'attendez pas juste un montant de temps fixe — augmentez le délai exponentiellement avec chaque retry, et ajoutez du hasard (jitter) pour prévenir les problèmes de thundering herd.
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érifiez 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 {
// Backoff exponentiel 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');
}
Mise en file d'attente des demandes pour les processus de build
Pour la génération de site statique où vous devez effectuer des centaines ou des milliers d'appels API, utilisez une queue avec contrôle de concurrence :
import pLimit from 'p-limit';
// Limiter à 5 demandes 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 2026) est mon incontournable pour cela. Vous pouvez aussi ajouter un délai entre les demandes :
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 demandes
return fetchWithRetry(`https://api.cms.com/pages/${slug}`);
})
)
);
Implémentation du rate limiting dans Next.js API Routes
Maintenant, changeons de côté — vous construisez une API et avez besoin de la protéger. Si vous construisez avec Next.js, voici comment ajouter le rate limiting à vos routes API.
Limiteur de débit simple en mémoire
Pour un déploiement sur 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 Next.js App Router :
// 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',
},
}
);
}
// Votre logique de route réelle ici
return NextResponse.json(
{ data: 'Voilà' },
{
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 vous exécutez sur des plateformes sans serveur comme Vercel, car chaque invocation de fonction pourrait frapper une instance différente. Vous avez besoin d'une mémoire partagée. Upstash Redis est le choix le plus populaire pour ceci en 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),
},
});
}
L'offre gratuite d'Upstash vous donne 10 000 demandes/jour, ce qui est suffisant pour les petits projets. Leur plan Pro commence à $10/mois pour 500K commandes quotidiennes en début 2026.
Rate Limiting au niveau du middleware
Si vous voulez le rate limiting sur toutes vos routes API, le middleware Next.js est le bon endroit :
// 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 | Pros | Cons | Meilleur pour |
|---|---|---|---|---|
| Fixed Window | Compte les demandes dans des fenêtres de temps fixes (par ex., par minute) | Simple à implémenter | Les rafales aux limites de fenêtre peuvent permettre 2x la limite | APIs simples, outils internes |
| Sliding Window | Compte les demandes sur une période de temps roulante | Distribution plus lisse | Légèrement plus complexe, plus de mémoire | La plupart des APIs de production |
| Token Bucket | Les jetons se remplissent à un débit régulier, chaque demande coûte un jeton | Permet les rafales contrôlées | Gestion d'état plus complexe | APIs qui ont besoin de tolérance aux rafales |
| Leaky Bucket | Les demandes entrent dans une file d'attente et sont traitées à débit fixe | Débit de sortie très lisse | Peut ajouter de la latence, les demandes peuvent être supprimées | Livraison de webhooks, traitement de jobs |
| Sliding Window Log | Stocke l'horodatage de chaque demande | Très précis | Utilisation élevée de mémoire à l'échelle | Besoins de faible volume et haute précision |
Pour la plupart des applications web, sliding window est le sweet spot. C'est ce qu'Upstash utilise par défaut, et c'est ce que je recommande sauf si vous avez une raison spécifique de choisir autre chose.
Rate Limiting dans Astro et autres frameworks
Si vous construisez 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 sur edge sur Cloudflare Workers, vous pourriez aussi considérer les règles de 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 de l'application. Leur Rate Limiting avancé commence à $0.05 pour 10 000 bonnes demandes sur le plan Business.
Surveillance et débogage des erreurs 429 en production
Vous ne pouvez pas corriger ce que vous ne pouvez pas voir. Voici ma checklist pour gérer les erreurs 429 en production :
Quand vous recevez des 429
- Vérifiez quelle API renvoie 429 — Regardez l'URL de réponse, pas juste le code d'état
- Loggez l'en-tête
Retry-After— S'il est constamment très long, vous pouvez avoir besoin d'une offre supérieure - Auditez vos motifs de demande — Faites-vous des appels redondants ? Pouvez-vous regrouper les demandes ?
- Implémentez le caching — Utilisez
stale-while-revalidate, le caching Redis, ou Next.js ISR pour réduire les appels API - Vérifiez si plusieurs environnements partagent des clés API — C'est la cause 429 « mystère » la plus courante
Quand vous envoyez des 429
- Configurez des tableaux de bord — Suivez les taux de réponse 429 au fil du temps
- Identifiez les contrevenants principaux — Quelles adresses IP ou clés API atteignent les limites les plus ?
- Réexaminez vos limites — Sont-elles trop restrictives ? Trop permissives ? Vérifiez votre capacité de serveur et ajustez
- Envoyez toujours
Retry-After— Soyez un citoyen API responsable - Incluez un message d'erreur utile — Dites au client quelle limite ils ont atteint et quand réessayer
Une réponse 429 bien formulée 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 vous traitez avec des problèmes persistants de rate limiting sur une architecture headless — que ce soit pendant les builds, à l'exécution, ou les deux — cela pourrait valoir la peine de nous contacter pour discuter de votre 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 des motifs 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 d'état qui signifie que vous avez envoyé trop de demandes à un serveur dans une période donnée. Le serveur applique une limite de débit — il vous demande de ralentir. Ce n'est pas une erreur d'authentification ou une erreur de serveur ; vos demandes sont probablement valides, il y en a juste trop. Le serveur devrait inclure un en-tête Retry-After vous disant quand réessayer.
Comment puis-je corriger une erreur 429 ?
Si vous recevez des erreurs 429 d'une API, implémentez un backoff exponentiel avec jitter dans votre logique de retry, réduisez votre fréquence de demande, ajoutez du caching pour éviter les appels redondants, et respectez l'en-tête Retry-After. Si vous atteignez la limite pendant les builds, utilisez une mise en file d'attente des demandes avec limites de concurrence. Si cela se produit de façon cohérente, vous devrez peut-être passer à une offre API supérieure avec des limites de débit plus généreuses.
Quel est 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 demande. Il peut être spécifié comme un nombre de secondes (par ex., Retry-After: 60) ou comme une date HTTP (par ex., Retry-After: Thu, 01 Jan 2026 00:00:00 GMT). Toutes les APIs n'incluent pas cet en-tête, mais les bien conçues le font.
Comment implémente-je le rate limiting dans Next.js ?
Pour le développement ou les déploiements sur serveur unique, vous pouvez utiliser une Map en mémoire pour suivre les comptages de demandes par adresse IP. Pour les déploiements sans serveur en production sur des plateformes comme Vercel, utilisez Upstash Redis avec le paquet @upstash/ratelimit. Vous pouvez appliquer le rate limiting au niveau des routes individuelles ou sur toutes les 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 sur le rate limiting — votre client envoie trop de demandes. Un 503 Service Unavailable signifie que le serveur est surchargé ou en maintenance et ne peut pas gérer aucune demande de personne. Les deux peuvent inclure un en-tête Retry-After, mais ils indiquent des problèmes très différents. Un 429 est ciblé sur vous ; 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 de l'application (comme ce que vous implémenteriez dans Next.js) peut gérer l'abus modéré, mais une attaque DDoS sérieuse doit être atténuée au niveau de l'infrastructure — en utilisant des services comme Cloudflare, AWS Shield, ou les protections intégrées de votre fournisseur d'hébergement. Pensez au rate limiting au niveau de l'app comme un videur, et à la protection au niveau de l'infrastructure comme les murs de la forteresse.
Quelle limite de débit dois-je définir pour mon API ?
Cela dépend entièrement de votre cas d'usage. Un point de départ courant pour les APIs publiques est 60 demandes par minute par IP, ou 1 000 demandes par heure par clé API. Pour les utilisateurs authentifiés, vous pourriez autoriser plus. La clé est de surveiller les motifs d'utilisation réels, de définir des limites qui accommodent l'utilisation légitime avec de la marge, et d'ajuster en fonction des données réelles. Commencez plus restrictif et assouplissez — c'est plus facile que de resserrer les limites après que les utilisateurs dépendent de taux supérieurs.
Pourquoi ai-je 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 vous avez des centaines ou des milliers de pages, ce sont des centaines ou des milliers d'appels API en succession rapide. La plupart des APIs CMS ont des limites de débit entre 5-20 demandes par seconde. Utilisez p-limit ou des bibliothèques similaires pour plafonner la concurrence à 3-5 demandes simultanées, ajoutez de petits délais entre les lots, et considérez l'utilisation de builds incrémentiels (ISR dans Next.js, ou les content collections incrémentielles d'Astro) pour éviter de tout reconstruire à la fois.