Web razvoj
Next.jsSigurnostOgraničavanje stopeZaštita od botovaEdgeRedisVercelCloudflare

Next.js ograničavanje stope zahtjeva i zaštita od botova: obrasci za API-je, Server Actions i Edge (vodič za 2026.)

AO
Adrijan Omićević
·16 min čitanja

# Što ćete naučiti#

Ovaj vodič objašnjava Next.js rate limiting za Route Handlers, Server Actions i Edge runtime te kako ga kombinirati sa zaštitom od botova. Dobit ćete kod koji možete kopirati i zalijepiti za token bucket ograničenja, Redis-backed konzistentnost kroz serverless instance i praktična WAF ili CDN pravila.

Napisano je za timove koji isporučuju Next.js aplikacije s javnim API-jima, auth tokovima, endpointovima za pretraživanje, kontakt formama ili bilo kojom rutom koja privlači scrapere i credential stuffing. Ako želite i širi sigurnosni baseline, krenite s našom kontrolnom listom sigurnosti web aplikacija.

# Zašto je ograničavanje stope posebno važno u Next.js-u#

Next.js olakšava izlaganje moćnih serverskih mogućnosti kroz Route Handlers i Server Actions. To istovremeno čini zloupotrebu jeftinijom za napadače, a skupljom za vas.

Tipični stvarni načini “pucanja” koje viđamo u auditima i post-mortem analizama incidenata:

  • Credential stuffing na /api/login stvara CPU skokove, opterećenje baze i troškove trećih auth servisa.
  • Scraperi gađaju search i product endpointove s visokom konkurentnošću, uzrokuju cache missove i origin promet.
  • Spam formi napuhuje račune email providera i puni vaš CRM smećem.
  • LLM crawleri i generički botovi pokreću skupe server komponente i server-side rendering putanje.

Dobra strategija nije jedan limiter. To je slojeviti sustav koji rano blokira očitu zloupotrebu i primjenjuje stroža pravila samo tamo gdje je važno.

# Pregled arhitekture: slojeviti model obrane#

Koristite najjeftiniju kontrolu prvu, zatim postupno skuplje provjere.

SlojGdje se izvršavaNajbolje zaTipični alati
CDN ili WAF pravilaPrije aplikacijePoznati loši botovi, geo pravila, osnovna pravila rate-aCloudflare WAF, Vercel WAF, AWS WAF
Edge middleware ili Edge Route HandlersNa edgeuBrzo oblikovanje prometa na razini IP-a, rana odbijanja, provjere po headerimaNext.js Middleware, Edge runtime
App-level limiter (Redis-backed)Node runtime ili Edge s Redis HTTPLimiti za autentificirane korisnike, kvote po korisniku, token bucket burstoviUpstash Redis, Redis na Fly, managed Redis
Throttle na razini poslovne logikeUnutar actiona i servisaKontrola troškova, zaštita downstreamaDB-level capovi, queueing, circuit breaker-i

Odabir runtimea je bitan jer Edge može odbiti ranije, ali ima ograničenja. Ako odlučujete između Edge i Node obrazaca, pročitajte Next.js Edge runtime vs Node.js runtime.

🎯 Ključna poruka: Promatrajte rate limiting kao sustav. CDN/WAF blokira šum, Edge jeftino odbija, a Redis-backed app limiti dosljedno provode stvarne kvote.

# Odabir pravog algoritma ograničavanja#

Većina timova krene s fiksnim prozorima i brzo požali zbog ponašanja burstova. Token bucket i leaky bucket su obično bolji za korisničko iskustvo.

Fiksni prozor (Fixed Window)#

  • Primjer: 100 zahtjeva u minuti.
  • Problem: napadač može poslati 100 zahtjeva na kraju jedne minute i 100 na početku sljedeće, čime efektivno udvostruči propusnost.

Klizni prozor (Sliding Window)#

  • Izravnava skokove, ali može biti složeniji i “teži” za pohranu.
  • Dobar za precizno provođenje, posebno na WAF sloju.

Token Bucket (preporučeno za većinu Next.js aplikacija)#

  • Zamislite kantu koja se puni stalnom brzinom.
  • Dopušta kratke burstove uz kontrolu dugoročne stope.
  • Dobro za UX: korisnici mogu refreshati ili retryati bez trenutne blokade.

Praktična početna točka za token bucket postavke:

Tip endpointaOdrživa stopaBurstNapomene
Login, OTP, reset lozinke5 po minuti10Dodajte i ključeve po računu i po IP-u
Search30 po minuti60Agresivno cacheirajte i razmotrite bot pravila
Kontakt forma2 po minuti5CAPTCHA dodajte tek nakon sumnje
Javni API (bez auth)60 po minuti120Preferirajte API ključeve kad je moguće
Server Actions koje mijenjaju podatke20 po minuti40Ključ po user ID-u i sessionu

# Strategija ključeva: izbjegnite limite samo po IP-u#

Limiti samo po IP-u su jednostavni, ali uzrokuju lažno pozitivne blokade za:

  • Korporativne NAT-ove
  • Mobilne operatere
  • Dijeljeni Wi‑Fi
  • Proxye i VPN-ove

Preferirajte kompozitne ključeve:

  1. 1
    Authenticated user ID kad je dostupan.
  2. 2
    API key za programatski pristup.
  3. 3
    Session ID za anonimne, ali cookie-based tokove.
  4. 4
    IP kao sekundarna dimenzija.
  5. 5
    Gruba UA kategorija (coarse UA bucket) kako biste smanjili trivijalno zaobilaženje.

Siguran obrazac je ograničavati i po korisniku i po IP-u, pa blokirati tek kada su oba “abusive”, ili primijeniti strožu akciju kad je jedna dimenzija ekstremna.

⚠️ Upozorenje: Nikad se ne oslanjajte na x-forwarded-for osim ako ste iza pouzdanog proxya i vaša platforma to garantira. Na većini platformi trebate koristiti request IP koji daje runtime ili provjerene headere.

# Next.js rate limiting za Route Handlers (Node Runtime)#

Node runtime je najfleksibilniji za Redis klijente i crypto biblioteke. Također je čest za pristup bazi i težu logiku.

Korak 1: Definirajte Token Bucket u Redis-u#

Token bucket treba pohraniti dvije vrijednosti po ključu: trenutne tokene i timestamp zadnjeg refill-a. Implementirat ćemo ga jednim atomskim Redis skriptom kako bi konkurentni zahtjevi bili ispravno obrađeni.

Ovaj primjer je zamišljen za Redis-kompatibilne servise koji podržavaju Lua skripte. Ako vaš provider ne podržava skripte, koristite server-side rate limit proizvod ili drugi algoritam.

TypeScript
// lib/rateLimit.ts
export type RateLimitResult = {
  allowed: boolean;
  remaining: number;
  retryAfterMs: number;
};
 
export type TokenBucketConfig = {
  capacity: number;       // max tokens
  refillPerSec: number;   // tokens per second
};
 
const LUA_TOKEN_BUCKET = `
local key = KEYS[1]
local capacity = tonumber(ARGV[1])
local refill_per_sec = tonumber(ARGV[2])
local now_ms = tonumber(ARGV[3])
 
local data = redis.call("HMGET", key, "tokens", "ts")
local tokens = tonumber(data[1])
local ts = tonumber(data[2])
 
if tokens == nil then tokens = capacity end
if ts == nil then ts = now_ms end
 
local delta_ms = math.max(0, now_ms - ts)
local refill = (delta_ms / 1000.0) * refill_per_sec
tokens = math.min(capacity, tokens + refill)
 
local allowed = 0
if tokens >= 1.0 then
  allowed = 1
  tokens = tokens - 1.0
end
 
redis.call("HMSET", key, "tokens", tokens, "ts", now_ms)
redis.call("PEXPIRE", key, math.ceil((capacity / refill_per_sec) * 1000) + 60000)
 
local retry_after_ms = 0
if allowed == 0 then
  retry_after_ms = math.ceil((1.0 - tokens) / refill_per_sec * 1000)
end
 
return {allowed, math.floor(tokens), retry_after_ms}
`;

Korak 2: Povežite ga s vašim Redis klijentom#

Ispod je minimalni wrapper koji pretpostavlja da vaš Redis klijent ima eval metodu.

TypeScript
// lib/rateLimitRedis.ts
import type { RateLimitResult, TokenBucketConfig } from "./rateLimit";
 
export async function tokenBucketLimit(opts: {
  redis: any;
  key: string;
  config: TokenBucketConfig;
  nowMs?: number;
}): Promise<RateLimitResult> {
  const nowMs = opts.nowMs ?? Date.now();
  const { capacity, refillPerSec } = opts.config;
 
  const res = await opts.redis.eval(LUA_TOKEN_BUCKET, {
    keys: [opts.key],
    arguments: [String(capacity), String(refillPerSec), String(nowMs)],
  });
 
  const allowed = res[0] === 1;
  const remaining = Number(res[1] ?? 0);
  const retryAfterMs = Number(res[2] ?? 0);
 
  return { allowed, remaining, retryAfterMs };
}

Ako vaša Redis biblioteka koristi drugačiji eval potpis, prilagodite samo taj dio. Logika ostaje ista.

Korak 3: Primijenite u Next.js Route Handleru#

Primjer rute: app/api/search/route.ts

TypeScript
// app/api/search/route.ts
import { NextResponse } from "next/server";
import { tokenBucketLimit } from "@/lib/rateLimitRedis";
import { redis } from "@/lib/redis";
 
export const runtime = "nodejs";
 
function getClientIp(req: Request) {
  return req.headers.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "unknown";
}
 
export async function GET(req: Request) {
  const ip = getClientIp(req);
  const key = `rl:search:ip:${ip}`;
 
  const limit = await tokenBucketLimit({
    redis,
    key,
    config: { capacity: 60, refillPerSec: 0.5 }, // 30/min sustained, 60 burst
  });
 
  if (!limit.allowed) {
    return new NextResponse("Too Many Requests", {
      status: 429,
      headers: {
        "Retry-After": String(Math.ceil(limit.retryAfterMs / 1000)),
      },
    });
  }
 
  // Continue with search logic...
  return NextResponse.json({ ok: true, remaining: limit.remaining });
}

Ovo je “dovoljno dobro” za mnoge javne endpointove, ali za autentificirane korisnike htjet ćete bolje ključeve.

# Next.js rate limiting za Server Actions#

Server Actions su moćne jer se izvršavaju na serveru i mogu se pozivati iz formi i komponenti. Također su atraktivne za zloupotrebu jer često pokreću mutacije i skupe downstream pozive.

Obrazac: Omotajte Server Actions limiterom#

Možete izgraditi wrapper koji:

  • Izvodi stabilan ključ iz user ID-a ili sessiona.
  • Kod neautentificiranih pada natrag na IP.
  • Vraća siguran error koji UI može obraditi.
TypeScript
// lib/withRateLimit.ts
"use server";
 
import { headers } from "next/headers";
import { tokenBucketLimit } from "@/lib/rateLimitRedis";
import { redis } from "@/lib/redis";
 
type LimiterOpts = {
  name: string;
  capacity: number;
  refillPerSec: number;
  keySuffix?: string;
};
 
function getIpFromHeaders() {
  const h = headers();
  return h.get("x-forwarded-for")?.split(",")[0]?.trim() ?? "unknown";
}
 
export function withRateLimit<TArgs extends any[], TResult>(
  action: (...args: TArgs) => Promise<TResult>,
  opts: LimiterOpts
) {
  return async (...args: TArgs): Promise<TResult> => {
    const ip = getIpFromHeaders();
    const key = `rl:action:${opts.name}:ip:${ip}:${opts.keySuffix ?? "default"}`;
 
    const limit = await tokenBucketLimit({
      redis,
      key,
      config: { capacity: opts.capacity, refillPerSec: opts.refillPerSec },
    });
 
    if (!limit.allowed) {
      throw new Error("RATE_LIMITED");
    }
 
    return action(...args);
  };
}

Zatim ga koristite:

TypeScript
// app/actions/submitContact.ts
"use server";
 
import { withRateLimit } from "@/lib/withRateLimit";
 
async function submitContactImpl(formData: FormData) {
  // validate, store, notify
  return { ok: true };
}
 
export const submitContact = withRateLimit(submitContactImpl, {
  name: "submitContact",
  capacity: 5,
  refillPerSec: 0.03, // roughly 2/min sustained
});

Edge vs Node za Server Actions#

U praksi, većina Server Actions radi u Node runtimeu na platformama poput Vercela, i to je obično ono što želite zbog Redis klijenata i auth SDK-ova. Ako forsirate Edge, provjerite da su sve ovisnosti Edge-kompatibilne i da Redis-u pristupate preko HTTP-a.

Ako želite dublju usporedbu runtimea, koristite naš vodič Edge vs Node.

# Edge runtime obrasci: brzo odbijanje, ograničeno stanje#

Edge runtime je odličan za rano i jeftino odbijanje te za oblikovanje prometa prije nego dođe do origina. Kompromis su ograničena kompatibilnost biblioteka i upravljanje stanjem.

Opcija A: Edge Middleware “Gate”#

Koristite middleware za blokiranje očite zloupotrebe za cijele grupe ruta, poput /api/ ili osjetljivih stranica.

  • Odbijte poznate loše botove po User-Agent signaturi.
  • Nametnite jednostavne per-IP limite koristeći edge KV ili vendor-specifični rate limit servis.
  • Za interne pozive zahtijevajte minimalni header.

Budući da se Next.js Middleware izvršava na Edgeu, to je dobro mjesto za gruba pravila i redirect ili block.

ℹ️ Napomena: Middleware nije idealno mjesto za stroge kvote jer može dodati latenciju i morate izbjegavati high-cardinality pozive stanja na svaki zahtjev. Koristite ga za shaping i jeftine provjere.

Opcija B: Edge Route Handler s Redis HTTP#

Neki managed Redis provideri nude HTTP-based API koji je Edge-friendly. Ista token bucket logika vrijedi, ali se mijenjaju karakteristike klijenta i latencije.

Kad ste na edgeu, budite realni oko latencije. Ako je vaš Redis daleko od edge POP-a, možete dodati 30 do 100 ms po zahtjevu, što je preskupo za high-volume rute. U tim slučajevima neka WAF ili CDN riješi većinu šuma, a Redis provjere zadržite za autentificirane ili skupe operacije.

# Redis-backed limiti: konzistentnost kroz serverless instance#

U serverless i autoscaled okruženjima, in-memory limiti se često resetiraju i nisu dijeljeni između instanci. Redis to rješava kao zajedničko spremište.

Kada se isplati Redis-backed ograničavanje#

  • Imate API ključeve i kvote.
  • Trebate per-user throttle za Server Actions.
  • Plaćate po requestu trećim stranama i želite kontrolirati trošak.
  • Trebate dosljedno provođenje kroz regije ili instance.

Kada se ne isplati Redis-backed ograničavanje#

  • Čisto statične stranice iza CDN-a.
  • Endpointovi već zaštićeni WAF pravilima i cachingom.
  • Interni alati s malim prometom.

Praktičan pristup je implementirati Redis-based limiting samo za ove kategorije:

KategorijaPrimjeri endpointovaPreporučeni ključZašto
Autentifikacija/api/login, /api/otpuser ID plus IPSprječava stuffing i lockoutove računa
Skupa čitanja/api/search, /api/reportsuser ID ili sessionŠtiti DB i sprječava scraping
MutacijeServer Actions koje pišuuser IDSprječava spam i zloupotrebu
Trošak trećih stranaAI pozivi, SMS, emailuser ID plus planSprječava “bill shock”

# WAF i CDN pravila: zaštita s najvećim ROI-jem#

Rate limiting na aplikacijskom sloju je precizniji, ali je skuplji. WAF i CDN pravila trebaju zaustaviti većinu lošeg prometa prije nego dotakne vaš Next.js runtime.

Praktična WAF pravila koja rade#

  • Request rate pravila za /api/login, /api/otp, /api/reset-password.
  • Bot score ili managed bot protection za /api/search, /products, /sitemap.xml.
  • Geo pravila ako je vaš business ograničen na regiju.
  • Challenge na sumnjive obrasce umjesto tvrdog blocka.

Mnogi timovi vide da top 1 do 5 posto zlonamjernih IP-ova generira velik dio zahtjeva. Uobičajeno je značajno smanjiti origin promet blokiranjem ili izazivanjem (challenge) tih IP-ova na edgeu. Cloudflare je javno dijelio case studyje gdje bot management značajno smanjuje bot promet, a u praksi često vidimo dvoznamenkasta smanjenja origin zahtjeva nakon uključivanja bot pravila i tuniranja.

💡 Savjet: Za rute visokog rizika krenite s “challenge”, ne s “block”. To smanjuje lažno pozitivne blokade, a i dalje automatiziranu zloupotrebu čini skupom.

Primjer pristupa pravilima prema osjetljivosti rute#

RutaZadana akcijaEskalacijaNapomene
/api/loginstrogi limitchallenge ili blockDodajte per-account throttle u aplikaciji
/api/searchumjeren limitchallengeCacheirajte rezultate i paginirajte
/api/public/*umjeren limitblock kod očite zloupotrebePotaknite API ključeve
/app/* autentificirane stranicelagani shapingapp-level user limitiFokus na zlonamjerne sessione

# Dizajn odgovora: 429, Retry-After i UX#

Uvijek vraćajte:

  • 429 Too Many Requests
  • Retry-After header u sekundama
  • Stabilan error code koji klijent može interpretirati

Za Server Actions mapirajte error na UI poruku poput “Previše pokušaja, pokušajte ponovno za 30 sekundi” i držite to konzistentno kroz aplikaciju.

Ako nudite API-je trećim stranama, dokumentirajte limite i vraćajte headere poput:

  • X-RateLimit-Limit
  • X-RateLimit-Remaining
  • X-RateLimit-Reset

Čak i ako danas ne implementirate sve, krenite s Retry-After.

# Monitoring i alerting: otkrijte zloupotrebu i prilike za tuniranje#

Rate limiting bez monitoringa postaje tihi utjecaj na korisnike. Pratite i dopušten i blokiran promet.

Što mjeriti:

MetričkaZašto je bitnoDobar trigger za alert
Ukupna stopa 429Otkriva pogrešne postavkeNagli porast iznad baselinea
429 po rutiNalazi “vruće” endpointoveJedan endpoint “iskoči”
429 po tipu ključaDijagnosticira false positiveMnogo različitih IP-ova blokirano
Top blokirani ključeviIdentificira izvore zloupotrebeJedan ključ dominira
Latencija dodana limiteromOsigurava da limiter nije usko grloP95 raste nakon rollouta
Redis greške i timeoutoviSprječava fail-open iznenađenjaError rate iznad 1 posto

Logirajte strukturirano. Uključite:

  • rutu
  • naziv limitera
  • tip ključa, ne puni ključ ako sadrži osobne podatke
  • preostale tokene
  • retryAfter

Ako gradite observability baseline, slijedite naš vodič za observability web aplikacija i osigurajte da se logovi i metrike mogu korelirati s request ID-jevima.

⚠️ Upozorenje: Ne logirajte sirovi IP zajedno s korisničkim identifikatorima ako to ne trebate. Tretirajte ove logove kao osjetljive i primijenite retention politike.

# Ublažavanje lažno pozitivnih blokada i siguran rollout#

Lažno pozitivne blokade su glavni razlog zašto timovi isključuju limite. Dizajnirajte za postupan rollout i brzo ublažavanje.

Koristite ove poluge za ublažavanje#

  1. 1

    Shadow mode
    Izračunavajte limite i logirajte would-block događaje, ali ne blokirajte. Rolloutajte 24 do 72 sata i pregledajte.

  2. 2

    Tiered pragovi
    Postavite više limite za autentificirane korisnike i korisnike koji plaćaju.

  3. 3

    Allowlists
    Dopustite IP-ove ureda, uptime monitore, CI i pouzdane third-party servise.

  4. 4

    Dizajn ključeva
    Koristite user ID kad je dostupan. Izbjegnite IP-only za autentificirane tokove.

  5. 5

    Grace za retry
    Token bucket burst kapacitet sprječava slučajne blokade zbog dvostrukog slanja i nestabilnih mobilnih veza.

  6. 6

    Eskalacija umjesto blokiranja
    Challenge, dodajte CAPTCHA ili zahtijevajte email verifikaciju prije hard blocka.

Uobičajeni false-positive scenariji i rješenja#

ScenarijSimptomRješenje
Korporativni NATMnogo korisnika dijeli jedan IPKljuč po user ID-u, povećajte IP burst
NAT mobilnog operateraNasumični block na mobiteluPreferirajte session ključ, smanjite težinu IP-a
Agresivni prefetchingDodatni GET prometIzuzmite prefetch headere ili specifične rute
WebhookoviProvider retrya kod greškeVeći burst, idempotency ključevi, allowlist IP-ova providera

# Kako sve složiti: praktična baseline konfiguracija#

Ako želite konfiguraciju koja radi za većinu SaaS i content platformi, krenite ovdje i prilagođavajte.

KomponentaBaseline postavkaPrimjenjuje se na
WAF pravilo20 zahtjeva u 10 sekundi po IP-u za /api/loginZaustavlja burstove credential stuffinga
Edge shapingBlokirajte očite loše botove po UA plus ponašanjuJeftino rano odbijanje
Redis token bucketLogin: 5/min održivo, 10 burst po IP-u plus po računuKonzistentno provođenje
Redis token bucketSearch: 30/min održivo, 60 burst po sessionuŠtiti DB
Action wrapperMutacije: 20/min održivo, 40 burst po korisnikuSprječava spam
ObservabilityAlert na stopu 429 i Redis greškeSprječava tihi utjecaj na korisnike

Povežite ovo s vašim ukupnim sigurnosnim programom. Rate limiting i zaštita od botova su ključne kontrole uz validaciju inputa, hardening autentifikacije i secure headere, sve pokriveno u našoj kontrolnoj listi sigurnosti web aplikacija.

# Ključne poruke#

  • Koristite slojevitu strategiju: prvo WAF ili CDN pravila, zatim Edge shaping, a Redis-backed token bucket limite za dosljedne kvote u Next.js-u.
  • Izbjegavajte IP-only ključeve za autentificirane tokove; preferirajte kompozitne ključeve poput user ID plus IP kako biste smanjili false positive iza NAT-a.
  • Implementirajte token bucket limite za bolji UX: dopustite kratke burstove uz održive stope i uvijek vraćajte 429 s Retry-After.
  • Ograničite Server Actions tako da ih omotate reusable limiterom i spremate brojače u Redis kako bi limiti vrijedili kroz serverless instance.
  • Pratite stopu 429, top blokirane ključeve, Redis latenciju i greške; rolloutajte u shadow modeu prije provođenja blokiranja.

# Zaključak#

Next.js aplikacije se brzo isporučuju, ali također izlažu endpointove visoke vrijednosti kroz API-je i Server Actions koje botovi vole iskorištavati. Implementirajte Next.js rate limiting kao slojeviti sustav, krenite s token bucket limitima koji su UX-friendly, i koristite WAF ili CDN pravila da zaustavite bučan promet prije nego dođe do vašeg origina.

Ako želite pomoć u dizajniranju pragova, implementaciji Redis-backed limita ili tuniranju bot zaštite bez štete za stvarne korisnike, javite se Samiodi. Pregledat ćemo vaše rute, preporučiti plan rollouta i implementirati monitoring kako biste limite mogli provoditi s povjerenjem.

FAQ

Share
A
Adrijan OmićevićOsnivač i senior developer

Osnivač i senior developer u Samiodi. 8+ godina iskustva u izradi React, Next.js, Flutter i n8n rješenja za klijente diljem Europe.

Trebate pomoć s projektom?

Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.