Sécurisez votre app Next.js avec Arcjet : limitation de débit, protection anti-bot et Shield WAF en 2026

Déployer une application Next.js en 2026 sans couche de sécurité, c'est chercher les ennuis. Bots de scraping, credential stuffing, abus d'API et attaques par injection de prompt sur les endpoints IA sont peu coûteux, automatisés et incessants. Écrire un middleware sur mesure pour chacun est lent et fragile.
Arcjet est un SDK de sécurité orienté développeur qui s'exécute à l'intérieur de votre application Next.js. Un seul paquet, un seul appel protect(), et vous obtenez limitation de débit, détection de bots, WAF (Shield), validation d'email, filtrage PII et détection d'injection de prompt — tout en code, tout testable, avec des règles granulaires par route.
Dans ce tutoriel vous allez ajouter les six protections à un projet Next.js 15 App Router, les tester en local puis les déployer sereinement en production. Pas de reconfiguration DNS, pas de reverse proxy, pas d'infrastructure supplémentaire.
Ce que vous allez construire
Une application Next.js 15 avec :
- Un endpoint public
/api/helloprotégé par Shield WAF, détection de bots et limitation de débit à fenêtre fixe. - Un endpoint
/api/signupqui bloque les emails jetables et limite les inscriptions par IP. - Un endpoint IA
/api/chatqui impose un budget de jetons, bloque l'injection de prompt et masque les informations sensibles. - Un tableau de bord temps réel des décisions de sécurité dans la console Arcjet.
Prérequis
Avant de commencer, assurez-vous de disposer de :
- Node.js 20 ou plus récent
- Un projet Next.js 15 utilisant App Router (ou suivez l'étape 1 pour en créer un)
- Un compte Arcjet — le plan gratuit suffit pour ce tutoriel
- Une familiarité avec TypeScript et les route handlers Next.js
- Une compréhension de base des codes de statut HTTP et du middleware
Étape 1 : Créer un projet Next.js 15
Initialisez un nouveau projet (passez si vous en avez déjà un).
npx create-next-app@latest nextjs-arcjet-demo \
--typescript --app --tailwind --eslint \
--src-dir --import-alias "@/*"
cd nextjs-arcjet-demoLancez le serveur de développement pour vérifier qu'il démarre.
npm run devOuvrez http://localhost:3000 — vous devriez voir la page d'accueil Next.js par défaut.
Étape 2 : Installer Arcjet
Installez le SDK Next.js.
npm install @arcjet/nextPuis installez l'utilitaire optionnel @arcjet/inspect (utile pour vérifier les bots usurpés).
npm install @arcjet/inspectÉtape 3 : Obtenir votre clé de site Arcjet
Connectez-vous sur app.arcjet.com, créez un site et copiez la clé (commence par ajkey_).
Ajoutez-la dans .env.local.
# .env.local
ARCJET_KEY=ajkey_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxArcjet est sûr en edge runtime : la clé reste côté serveur et n'est jamais envoyée au navigateur.
Étape 4 : Protéger une route API publique
Créez src/app/api/hello/route.ts.
// src/app/api/hello/route.ts
import arcjet, { detectBot, shield, fixedWindow } from "@arcjet/next";
import { NextResponse } from "next/server";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
shield({ mode: "LIVE" }),
detectBot({
mode: "LIVE",
allow: [
"CATEGORY:SEARCH_ENGINE",
"CATEGORY:MONITOR",
],
}),
fixedWindow({
mode: "LIVE",
window: "1m",
max: 20,
}),
],
});
export async function GET(req: Request) {
const decision = await aj.protect(req);
if (decision.isDenied()) {
if (decision.reason.isRateLimit()) {
return NextResponse.json(
{ error: "Too many requests" },
{ status: 429 },
);
}
if (decision.reason.isBot()) {
return NextResponse.json(
{ error: "Automated access is not allowed" },
{ status: 403 },
);
}
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
return NextResponse.json({ message: "Hello, human." });
}Trois règles se déclenchent à chaque requête :
- Shield bloque les attaques OWASP Top 10 (injection SQL, traversée de chemin, charges utiles XSS dans les query strings).
- detectBot autorise les moteurs de recherche vérifiés et les services de monitoring mais bloque tout le reste, y compris curl, Puppeteer et Chromium headless.
- fixedWindow plafonne chaque client à 20 requêtes par minute.
Testez depuis deux terminaux.
# Terminal 1 — suivez les décisions dans votre tableau de bord Arcjet
# Terminal 2 — inondez l'endpoint
for i in {1..25}; do curl -s -o /dev/null -w "%{http_code}\n" \
http://localhost:3000/api/hello; doneVous devriez voir environ 20 réponses avec statut 200, puis 429 pour le reste.
Étape 5 : Bloquer les bots mais autoriser les vrais navigateurs
Arcjet fournit une taxonomie de bots. Liste partielle :
| Catégorie | Exemples de clients |
|---|---|
CATEGORY:SEARCH_ENGINE | Googlebot, Bingbot, Yandex |
CATEGORY:MONITOR | Uptime Robot, Pingdom, BetterStack |
CATEGORY:PREVIEW | Slackbot, Discordbot, Twitterbot |
CATEGORY:AI | GPTBot, ClaudeBot, PerplexityBot |
Pour autoriser les aperçus de liens sur les plateformes sociales mais bloquer les crawlers IA, ajustez la règle.
detectBot({
mode: "LIVE",
allow: [
"CATEGORY:SEARCH_ENGINE",
"CATEGORY:MONITOR",
"CATEGORY:PREVIEW",
],
// deny: ["CATEGORY:AI"], // blocage explicite de l'IA (optionnel)
}),Certains bots mentent sur leur User-Agent. Utilisez isSpoofedBot pour recouper l'IP de la requête avec les plages d'IP connues du bot déclaré.
import { isSpoofedBot } from "@arcjet/inspect";
const decision = await aj.protect(req);
if (decision.results.some(isSpoofedBot)) {
return NextResponse.json(
{ error: "Spoofed bot detected" },
{ status: 403 },
);
}Étape 6 : Protéger un formulaire d'inscription avec protectSignup
Les endpoints d'inscription sont une cible privilégiée : faux comptes, credential stuffing, listes de spam. Arcjet regroupe trois règles dans un seul helper.
Créez src/app/api/signup/route.ts.
// src/app/api/signup/route.ts
import arcjet, { protectSignup } from "@arcjet/next";
import { NextResponse } from "next/server";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
protectSignup({
rateLimit: {
mode: "LIVE",
interval: "10m",
max: 5,
},
bots: {
mode: "LIVE",
allow: [],
},
email: {
mode: "LIVE",
deny: ["DISPOSABLE", "INVALID", "NO_MX_RECORDS"],
},
}),
],
});
export async function POST(req: Request) {
const body = await req.json();
const email = String(body.email ?? "");
const decision = await aj.protect(req, { email });
if (decision.isDenied()) {
if (decision.reason.isEmail()) {
return NextResponse.json(
{ error: "Invalid or disposable email" },
{ status: 400 },
);
}
if (decision.reason.isRateLimit()) {
return NextResponse.json(
{ error: "Too many signup attempts" },
{ status: 429 },
);
}
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
return NextResponse.json({ ok: true });
}Testez avec une adresse jetable.
curl -X POST http://localhost:3000/api/signup \
-H "Content-Type: application/json" \
-d '{"email":"throwaway@mailinator.com"}'
# → 400 Invalid or disposable email
curl -X POST http://localhost:3000/api/signup \
-H "Content-Type: application/json" \
-d '{"email":"real@noqta.tn"}'
# → 200 okLa règle email vérifie trois couches : la validité syntaxique, la présence des enregistrements MX, et une liste de domaines jetables maintenue par Arcjet.
Étape 7 : Protéger un endpoint de chat IA
Les endpoints IA coûtent cher et présentent des vulnérabilités uniques. Une seule requête malveillante peut épuiser votre budget de jetons ou faire fuiter des PII depuis votre system prompt. Combinez limitation par token bucket, détection d'injection de prompt et filtrage des informations sensibles.
Créez src/app/api/chat/route.ts.
// src/app/api/chat/route.ts
import arcjet, {
detectBot,
detectPromptInjection,
sensitiveInfo,
shield,
tokenBucket,
} from "@arcjet/next";
import { NextResponse } from "next/server";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
characteristics: ["userId"],
rules: [
shield({ mode: "LIVE" }),
detectBot({ mode: "LIVE", allow: [] }),
tokenBucket({
mode: "LIVE",
refillRate: 2_000,
interval: "1h",
capacity: 5_000,
}),
sensitiveInfo({
mode: "LIVE",
deny: ["CREDIT_CARD_NUMBER", "EMAIL", "PHONE_NUMBER"],
}),
detectPromptInjection({ mode: "LIVE", threshold: 0.5 }),
],
});
export async function POST(req: Request) {
const { userId, message } = (await req.json()) as {
userId: string;
message: string;
};
const estimate = Math.ceil(message.length / 4);
const decision = await aj.protect(req, {
userId,
requested: estimate,
sensitiveInfoValue: message,
detectPromptInjectionMessage: message,
});
if (decision.isDenied()) {
if (decision.reason.isRateLimit()) {
return NextResponse.json(
{ error: "AI usage limit exceeded" },
{ status: 429 },
);
}
if (decision.reason.isSensitiveInfo()) {
return NextResponse.json(
{ error: "Please remove personal info" },
{ status: 400 },
);
}
if (decision.reason.isPromptInjection()) {
return NextResponse.json(
{ error: "Prompt injection detected" },
{ status: 400 },
);
}
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
return NextResponse.json({ reply: "pong" });
}La ligne characteristics: ["userId"] est critique. Sans elle, tous les utilisateurs partagent un unique token bucket indexé par IP — un seul power user épuise le quota pour tout le monde. Avec une caractéristique liée à l'utilisateur, chaque compte obtient son propre budget.
Étape 8 : Utiliser le middleware Arcjet pour une protection globale
La protection au niveau des routes est explicite mais répétitive. Pour une couverture globale, placez Arcjet dans middleware.ts.
// middleware.ts
import arcjet, { createMiddleware, detectBot, shield } from "@arcjet/next";
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
shield({ mode: "LIVE" }),
detectBot({
mode: "LIVE",
allow: ["CATEGORY:SEARCH_ENGINE", "CATEGORY:PREVIEW"],
}),
],
});
export default createMiddleware(aj);Gardez les règles du middleware au minimum. Les fonctionnalités coûteuses comme les token buckets appartiennent aux routes coûteuses, pas à chaque chargement d'asset statique.
Étape 9 : Utilisez DRY_RUN pour régler les règles
Ne déployez jamais une nouvelle règle directement en LIVE. Commencez en DRY_RUN pour qu'Arcjet journalise la décision sans bloquer le trafic.
fixedWindow({
mode: "DRY_RUN",
window: "1m",
max: 20,
}),Surveillez le tableau de bord Arcjet pendant quelques jours. Si le flux de décisions ne montre que du trafic légitime touchant la limite, resserrez la règle. Une fois les données saines, basculez en LIVE.
Étape 10 : Tester l'implémentation
Arcjet fournit un helper de test pour ne pas dépendre d'IP réelles dans les tests unitaires.
npm install -D @arcjet/test-utilsÉcrivez une spec qui vérifie que la règle d'inscription rejette les emails jetables.
// __tests__/signup.test.ts
import { POST } from "@/app/api/signup/route";
test("blocks disposable email", async () => {
const req = new Request("http://localhost/api/signup", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ email: "junk@mailinator.com" }),
});
const res = await POST(req);
expect(res.status).toBe(400);
});Pour un test local de bout en bout des limites de débit, lancez la boucle de l'étape 4 avec une fenêtre courte (ajustez temporairement window: "10s") pour ne pas attendre une minute entière avant de voir l'étranglement.
Étape 11 : Déployer en production
Arcjet n'est pas lié à une région. Déployez sur Vercel, Netlify, Cloudflare Pages, Fly.io ou un serveur Node auto-hébergé — le SDK fonctionne de la même façon partout.
Sur Vercel, ajoutez ARCJET_KEY comme variable d'environnement dans les paramètres du projet puis redéployez.
vercel env add ARCJET_KEY production
vercel --prodDans le tableau de bord Arcjet, ouvrez la vue Decisions. Vous devriez voir du trafic réel circuler avec, pour chaque règle, son verdict comptabilisé par minute.
Étape 12 : Monitoring et alertes
Le tableau de bord fait ressortir trois signaux à surveiller :
- Volume de requêtes par règle — un pic soudain sur
detectBotsignifie généralement qu'un nouveau scraper a découvert votre site. - Taux de blocage — si plus de 1 pour cent des utilisateurs réels sont bloqués, vos règles sont trop strictes.
- Ratio de bots usurpés — une part croissante signifie que des acteurs malveillants font tourner les User-Agents pour se faire passer pour Googlebot. Activez la vérification
isSpoofedBot.
Branchez l'intégration webhook sur Slack ou PagerDuty pour être alerté dès que le taux de blocage franchit un seuil.
Dépannage
Toutes les requêtes renvoient 403. Vérifiez que votre liste d'autorisation de bots contient bien CATEGORY:SEARCH_ENGINE si vous voulez que Googlebot vous atteigne. En développement local, votre propre navigateur ne devrait jamais être signalé — si c'est le cas, confirmez que vous avez mis mode: "DRY_RUN" sur la règle detectBot jusqu'à ce que la liste d'autorisation soit correcte.
Les limites de débit ne se réinitialisent pas. Les algorithmes fixed window et token bucket sont indexés par IP par défaut. Si vous testez derrière un NAT d'entreprise, tous les développeurs partagent la même clé. Ajoutez characteristics: ["userId"] et passez un identifiant utilisateur à l'appel protect() pour scoper les limites par compte.
Shield signale des requêtes de recherche légitimes. La détection OWASP de Shield peut se déclencher sur des champs de formulaire contenant des mots proches de SQL. Passez Shield en DRY_RUN pour cette route, inspectez l'onglet decisions, et ajoutez des exemptions en sortant la règle du middleware global.
Le détecteur d'injection de prompt passe à côté de certains jailbreaks. Le seuil vaut 0,5 par défaut sur une échelle de 0 à 1. Abaissez-le à 0,3 pour un filtrage plus strict, puis surveillez les faux positifs. Pour un chat adversarial, associez le détecteur à un durcissement du system prompt — aucune défense unique n'arrête toutes les attaques.
Prochaines étapes
- Explorez la liste de bots Arcjet et construisez une allow list personnalisée pour votre audience.
- Combinez Arcjet avec Upstash Redis pour la limitation de débit et le cache quand vous avez besoin de compteurs distribués sur plusieurs workloads.
- Ajoutez de l'observabilité avec le tracing et monitoring OpenTelemetry pour corréler les décisions Arcjet avec la latence en aval.
- Ajoutez Better Auth pour une authentification forte, puis utilisez les
characteristicsd'Arcjet pour scoper les limites par utilisateur. - Étendez au chat IA avec le tutoriel Claude Agent SDK pour que chaque appel d'outil passe d'abord par Arcjet.
Conclusion
En moins d'une heure vous avez ajouté six couches de protection de niveau production à une application Next.js 15 : un WAF, une détection de bots, des limites fixed window et token bucket, une validation d'email, un filtrage PII et une défense contre l'injection de prompt. Les six vivent dans votre codebase, les six sont testables, et aucune n'exige un reverse proxy ni un changement DNS.
La sécurité n'est pas une configuration unique. Déployez les règles en DRY_RUN, observez le tableau de bord, ajustez les seuils, puis passez en LIVE quand le signal est propre. Ensuite itérez — ajoutez une nouvelle règle à chaque fois que vous livrez une nouvelle surface.
Discutez de votre projet avec nous
Nous sommes ici pour vous aider avec vos besoins en développement Web. Planifiez un appel pour discuter de votre projet et comment nous pouvons vous aider.
Trouvons les meilleures solutions pour vos besoins.
Articles connexes

Upstash Redis et Next.js : Rate Limiting, Caching et Files de Messages
Apprenez à intégrer Upstash Redis dans une application Next.js pour implémenter le rate limiting, le caching côté serveur et les files de messages. Ce tutoriel couvre la configuration, les patterns de production et le déploiement serverless.

Construire un Agent IA Autonome avec Agentic RAG et Next.js
Apprenez a construire un agent IA qui decide de maniere autonome quand et comment recuperer des informations depuis des bases de donnees vectorielles. Un guide pratique complet avec Vercel AI SDK et Next.js, accompagne d'exemples executables.

Guide d'Integration de Chatbot IA : Construire des Interfaces Conversationnelles Intelligentes
Un guide complet pour integrer des chatbots IA dans vos applications en utilisant OpenAI, Anthropic Claude et ElevenLabs. Apprenez a construire des chatbots textuels et vocaux avec Next.js.