Przejdź do treści

Rate limiting na Edge w Next.js z Upstash

Opanuj ochronę endpointów w Next.js App Router. Dowiedz się, jak skonfigurować algorytmy token bucket i sliding window z Redisem na brzegu sieci.

Maciej Sala

Founder StriveLab

9 min czytaniaOpublikowano 10 kwietnia 2026 (Aktualizacja 17 lipca 2026)

Po co rate limiting w aplikacji Next.js?

Bez , czyli ograniczania liczby żądań, każdy wyśle do Twojego API tyle zapytań, ile zechce: celowo (brute force, prosty flood) albo zupełnie przypadkiem (scraper, pętla z błędem). Serwer się dławi, rachunek rośnie, a użytkownik dostaje błąd. Limit z jednego źródła w oknie czasu ogranicza ten scenariusz. To jedno z podstawowych zabezpieczeń aplikacji, obok ochrony przed CSRF, XSS i injection, o których pisałem osobno. Nie zastępuje jednak ani ochrony przed .

W Next.js możesz połączyć ogólny limit w lub z dokładniejszym limitem bezpośrednio w albo . Nie jest to obowiązkowe połączenie, bo dwa limitery oznaczają dwa budżety, większy koszt i potencjalnie dwie operacje sieciowe. Ochronę wolumetryczną najlepiej zatrzymywać przed aplikacją, a Upstash wykorzystywać do limitów biznesowych per użytkownik, konto lub endpoint.

Upstash Redis i rate limiting na edge

to serverless Redis z HTTP API — działa w oraz Node.js/serverless bez trwałych połączeń TCP. Pakiet @upstash/ratelimit daje gotowe algorytmy rate limitingu.

Code
npm install @upstash/ratelimit @upstash/redis
Code
# .env.local
UPSTASH_REDIS_REST_URL=https://xxx.upstash.io
UPSTASH_REDIS_REST_TOKEN=AXxx...

Konfiguracja rate limitera w Next.js

Code
// lib/rate-limit.ts
import { Ratelimit } from '@upstash/ratelimit'
import { Redis } from '@upstash/redis'
 
export const redis = Redis.fromEnv()
 
// Algorytm sliding window — 10 requestów na 10 sekund
export const rateLimiter = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(10, '10 s'),
  analytics: true,
  prefix: '@app/ratelimit',
  // Wbudowany timeout jest fail-open; wynik ma wtedy reason: 'timeout'.
  timeout: 1000,
  // Lokalny cache w pamięci instancji — odsiewa źródła już
  // zablokowane bez odpytywania Redisa, mniej operacji i niższy koszt
  ephemeralCache: new Map(),
})
 
// Osobny limiter dla auth — 5 prób na minutę
export const authRateLimiter = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(5, '1 m'),
  prefix: '@app/auth-ratelimit',
  timeout: 1000,
})
 
// Łagodniejszy limiter dla ogólnych stron — 60 req/min
export const pageRateLimiter = new Ratelimit({
  redis,
  limiter: Ratelimit.slidingWindow(60, '1 m'),
  prefix: '@app/page-ratelimit',
})

ephemeralCache jest domyślnie włączony; jawny Map w przykładzie pokazuje, że cache powinien powstać poza handlerem i przetrwać kolejne wywołania tej samej ciepłej instancji. Cache przechowuje tylko identyfikatory, które już przekroczyły limit. Nie synchronizuje stanu między instancjami i nie zmienia decyzji zapisanej w Redisie.

analytics: true wykonuje dodatkową komendę i zapisuje identyfikatory używane przez limiter. Jeżeli są nimi adresy IP lub ID użytkowników, uwzględnij retencję, podstawę prawną i dostęp do danych albo wyłącz analytics. Token REST jest sekretem serwerowym — nie używaj prefiksu NEXT_PUBLIC_ i nie umieszczaj go w logach.

Proxy z rate limitingiem w Next.js

Code
// proxy.ts — w Next.js 16 działa w runtime Node.js
import {
  NextResponse,
  type NextFetchEvent,
  type NextRequest,
} from 'next/server'
import { rateLimiter, authRateLimiter } from '@/lib/rate-limit'
import { getTrustedClientIp } from '@/lib/client-ip'
 
export async function proxy(request: NextRequest, event: NextFetchEvent) {
  const { pathname } = request.nextUrl
 
  // Rate limiting dla API
  if (pathname.startsWith('/api')) {
    const isSensitive = pathname.startsWith('/api/auth')
    const limiter = isSensitive ? authRateLimiter : rateLimiter
    const ip = getTrustedClientIp(request.headers)
 
    if (!ip) {
      console.error('Brak zaufanego adresu klienta dla rate limitingu')
      return isSensitive
        ? NextResponse.json(
            { error: 'Usługa chwilowo niedostępna' },
            { status: 503 },
          )
        : NextResponse.next()
    }
 
    const identifier = `${isSensitive ? 'auth' : 'api'}:${ip}`
 
    try {
      const { success, limit, remaining, reset, pending, reason } =
        await limiter.limit(identifier)
 
      // pending obejmuje m.in. zapis analytics; nie blokuje odpowiedzi.
      event.waitUntil(pending)
 
      // Timeout SDK jest domyślnie fail-open; decyzję podejmujemy jawnie.
      if (reason === 'timeout') {
        return isSensitive
          ? NextResponse.json(
              { error: 'Usługa chwilowo niedostępna' },
              { status: 503, headers: { 'Retry-After': '1' } },
            )
          : NextResponse.next()
      }
 
      const retryAfter = Math.max(1, Math.ceil((reset - Date.now()) / 1000))
 
      if (!success) {
        return NextResponse.json(
          {
            error: 'Zbyt wiele żądań',
            message: `Spróbuj ponownie za ${retryAfter} sekund.`,
          },
          {
            status: 429,
            headers: {
              'RateLimit-Limit': limit.toString(),
              'RateLimit-Remaining': remaining.toString(),
              'RateLimit-Reset': retryAfter.toString(),
              'Retry-After': retryAfter.toString(),
              'Cache-Control': 'no-store',
            },
          },
        )
      }
 
      const response = NextResponse.next()
      response.headers.set('RateLimit-Limit', limit.toString())
      response.headers.set('RateLimit-Remaining', remaining.toString())
      response.headers.set('RateLimit-Reset', retryAfter.toString())
 
      return response
    } catch (error) {
      console.error('Rate limiter niedostępny', { error, pathname })
      return isSensitive
        ? NextResponse.json(
            { error: 'Usługa chwilowo niedostępna' },
            { status: 503 },
          )
        : NextResponse.next()
    }
  }
 
  return NextResponse.next()
}
 
export const config = {
  matcher: ['/api/:path*'],
}

Pola RateLimit-* są szeroko stosowaną konwencją, ale ich dokładny kontrakt powinien być udokumentowany dla klientów API. W przykładzie RateLimit-Reset i Retry-After oznaczają liczbę sekund do ponownej próby; nie przekazuj bez opisu wartości reset z Upstash, bo jest timestampem w milisekundach. Zwracaj 429 tylko wtedy, gdy limit faktycznie został przekroczony. Awaria zależności zasługuje na 503.

Algorytmy rate limitingu: fixed window, sliding window i token bucket

Upstash oferuje trzy algorytmy:

Fixed window w rate limitingu

to stałe okno czasu, np. 10 żądań na minutę. Licznik resetuje się co minutę. Problem pojawia się na granicy okien: użytkownik może wysłać 20 żądań w 2 sekundy, czyli 10 na końcu starego okna i 10 na początku nowego.

Code
Ratelimit.fixedWindow(10, '1 m')

Sliding window w rate limitingu

wygładza problem granicy okien. Implementacja Upstash wylicza przybliżenie na podstawie bieżącego i poprzedniego okna, zamiast zapisywać timestamp każdego żądania. Jest dokładniejsza od fixed window, ale wykonuje więcej operacji i nadal może zwrócić wynik przybliżony. W konfiguracji multi-region dodatkowe komendy zwiększają też opóźnienie.

Code
Ratelimit.slidingWindow(10, '10 s')

Token bucket w rate limitingu

W algorytmie tokeny odnawiają się w ustalonym tempie, a każde żądanie zużywa 1 token. Pozwala to na krótką serię żądań do pojemności bucketa, a następnie ogranicza ruch zgodnie z tempem uzupełniania. W poniższym przykładzie przybywa 10 tokenów na sekundę, a bucket mieści maksymalnie 30.

Code
Ratelimit.tokenBucket(10, '1 s', 30) // 10 tokenów/s, max 30 w buckecie

Rekomenduje sliding window dla większości API, token bucket dla endpointów, gdzie chcesz pozwolić na krótkie serie, np. przeszukiwanie z autocomplete, a z kolei fixed window tam, gdzie prostota i niższa liczba operacji są ważniejsze od precyzji na granicy okna.

Identyfikacja źródła żądania i ryzyko fałszywych nagłówków

Cały rate limiting zależy od tego, czym zidentyfikujesz źródło żądania. Surowy x-forwarded-for można sfałszować, jeżeli aplikacja nie wie, które proxy dopisało konkretną część łańcucha. Nie ma jednego uniwersalnego i bezpiecznego parsera dla każdego wdrożenia, ponieważ wybór odpowiedniej reguły zależy od specyfiki platformy, liczby zaufanych węzłów pośredniczących oraz tego, czy serwer źródłowy można wywołać bezpośrednio z pominięciem proxy.

Na Vercel użyj x-vercel-forwarded-for, który platforma nadpisuje adresem klienta. cf-connecting-ip jest wiarygodny dopiero wtedy, gdy serwer źródłowy przyjmuje ruch wyłącznie z Cloudflare. Przy self-hostingu skonfiguruj zaufane proxy tak, aby usuwało nagłówki przesłane przez klienta, i odczytuj adres zgodnie z liczbą zaufanych hopów. Dla zalogowanych użytkowników limituj przede wszystkim po ID ze zweryfikowanej sesji, a nie po wartości podanej przez klienta w body lub nagłówku.

Code
// lib/client-ip.ts
export function getTrustedClientIp(headers: Headers): string | null {
  const vercelIp = headers.get('x-vercel-forwarded-for')
  if (vercelIp) return vercelIp.split(',')[0]?.trim() || null
 
  if (process.env.TRUST_CLOUDFLARE_PROXY === 'true') {
    return headers.get('cf-connecting-ip')?.trim() || null
  }
 
  return null
}

Nie zastępuj braku adresu wspólnym kluczem unknown, bo jedna nierozpoznana osoba wyczerpie limit wszystkim pozostałym. Dla odczytu możesz świadomie zastosować , a dla logowania lub formularza zwrócić 503. Przy logowaniu warto utrzymywać dwa niezależne limity: per zaufany adres IP i per znormalizowane konto. Sam limit per konto pozwala na celowe blokowanie ofiary, a sam limit per IP nie zatrzyma rozproszonego ataku. Nie zapisuj surowego e-maila w kluczu Redis — użyj stabilnego hasha albo wewnętrznego ID.

Co zrobić, gdy Redis padnie: fail-open czy fail-closed?

Każde sprawdzenie limitu stanowi żądanie sieciowe do Upstash, dlatego kluczowe jest ustalenie, co dzieje się w przypadku timeoutu, awaria Redisa lub przekroczenia limitu planu. Do dyspozycji pozostają dwie strategie, przy czym wybór każdej z nich nie stanowi wyłącznie decyzji technicznej, a biznesową.

  • przepuszcza ruch, gdy limiter nie działa, co oznacza wybór dostępności kosztem ochrony. Użytkownicy nie zobaczą błędu w przypadku awarii Redisa, jednak aplikacja na ten czas traci zabezpieczenia. To rozsądny wybór domyślny dla standardowych endpointów służących do odczytu danych.

  • odrzuca ruch, gdy limiter nie działa, co stanowi wybór bezpieczeństwa kosztem dostępności. Takie podejście sprawdza się przy logowaniu, płatnościach oraz innych wrażliwych ścieżkach, gdzie brak mechanizmu ograniczeń niesie za sobą większe ryzyko niż chwilowa niedostępność usługi.

Code
async function isAllowed(
  limiter: Ratelimit,
  identifier: string,
  failClosed = false,
): Promise<boolean> {
  try {
    const { success, reason } = await limiter.limit(identifier)
 
    // Wbudowany timeout Upstash zwraca success: true zamiast wyjątku.
    if (reason === 'timeout') {
      console.error('Przekroczono timeout rate limitera')
      return !failClosed
    }
 
    return success
  } catch (error) {
    console.error('Rate limiter niedostępny:', error)
    // Auth/płatności: fail-closed. Reszta: fail-open.
    return !failClosed
  }
}

Domyślny timeout biblioteki wynosi pięć sekund i działa w trybie fail-open, dlatego warto ustawić go jawnie zgodnie z budżetem opóźnienia aplikacji, a przy strategii fail-closed kontrolować zarówno wyjątki, jak i sytuacje przekroczenia czasu oczekiwania. Niezależnie od wybranego podejścia należy rejestrować każde takie zdarzenie w logach, ponieważ cicha awaria mechanizmu ograniczeń zazwyczaj ujawnia się dopiero w momencie realnego ataku.

Rate limiting per endpoint w Next.js

Code
// lib/rate-limit.ts
 
// Różne limity per typ endpointu
export const apiLimiters = {
  // Odczyt — łagodny limit
  read: new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(60, '1 m'),
    prefix: 'rl:read',
    timeout: 1000,
  }),
 
  // Zapis — bardziej restrykcyjny limit
  write: new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(10, '1 m'),
    prefix: 'rl:write',
    timeout: 1000,
  }),
 
  // Uwierzytelnianie — bardzo restrykcyjny limit
  auth: new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(5, '5 m'),
    prefix: 'rl:auth',
    timeout: 1000,
  }),
 
  // Upload — limitowany
  upload: new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(5, '10 m'),
    prefix: 'rl:upload',
    timeout: 1000,
  }),
 
  // Formularz kontaktowy — 3 wiadomości na 10 minut
  contact: new Ratelimit({
    redis,
    limiter: Ratelimit.slidingWindow(3, '10 m'),
    prefix: 'rl:contact',
    timeout: 1000,
  }),
}

Podane wartości stanowią jedynie przykłady, a nie uniwersalne i bezpieczne progi. Należy dobrać je indywidualnie na podstawie analizy normalnego ruchu, kosztów operacji oraz konsekwencji przypadkowego zablokowania użytkownika. Stosowanie różnych prefiksów skutecznie izoluje liczniki, jednak każde dodatkowe wywołanie metody limit() generuje osobną kontrolę oraz dodatkowy koszt.

Rate limiting w Route Handlerach

Code
// app/api/search/route.ts
import { apiLimiters } from '@/lib/rate-limit'
import { getTrustedClientIp } from '@/lib/client-ip'
import type { NextRequest } from 'next/server'
 
// Upstash używa HTTP, więc sam limiter jest zgodny z Edge Runtime.
// Wszystkie pozostałe zależności tego handlera też muszą wspierać Edge.
export const runtime = 'edge'
 
export async function GET(request: NextRequest) {
  const ip = getTrustedClientIp(request.headers)
 
  // Dla publicznego odczytu świadomie wybieramy fail-open.
  if (!ip) return Response.json({ results: [] })
 
  try {
    const result = await apiLimiters.read.limit(`search:${ip}`)
 
    if (result.reason === 'timeout') {
      return Response.json({ results: [] })
    }
 
    if (!result.success) {
      const retryAfter = Math.max(
        1,
        Math.ceil((result.reset - Date.now()) / 1000),
      )
 
      return Response.json(
        { error: 'Zbyt wiele żądań' },
        {
          status: 429,
          headers: { 'Retry-After': retryAfter.toString() },
        },
      )
    }
 
    // ... wyszukiwanie
    return Response.json({ results: [] })
  } catch (error) {
    console.error('Rate limiter wyszukiwarki jest niedostępny', error)
    // ... wyszukiwanie bez limitu
    return Response.json({ results: [] })
  }
}

Jeżeli działa w środowisku Node.js, usuń deklarację runtime = 'edge', pamiętając przy tym, że sposób użycia Upstash pozostaje bez zmian. W przypadku endpointów dla zalogowanych użytkowników najpierw zweryfikuj sesję oraz uprawnienia, a dopiero potem zastosuj klucz oparty na identyfikatorze, na przykład user:$.

Rate limiting w Server Actions

Code
// lib/rate-limit-action.ts
import { apiLimiters } from '@/lib/rate-limit'
import { getTrustedClientIp } from '@/lib/client-ip'
import { headers } from 'next/headers'
 
export async function checkContactRateLimit() {
  const headersList = await headers()
  const ip = getTrustedClientIp(headersList)
 
  // Formularz jest ścieżką zapisu, więc brak wiarygodnego ID blokujemy.
  if (!ip) {
    return { allowed: false, unavailable: true }
  }
 
  try {
    const result = await apiLimiters.contact.limit(`contact:${ip}`)
 
    // Dla antyspamu wybieramy fail-closed także przy timeout SDK.
    if (result.reason === 'timeout') {
      return { allowed: false, unavailable: true }
    }
 
    if (!result.success) {
      const retryIn = Math.max(1, Math.ceil((result.reset - Date.now()) / 1000))
      return {
        allowed: false,
        error: `Zbyt wiele prób. Spróbuj ponownie za ${retryIn} sekund.`,
      }
    }
 
    return { allowed: true, remaining: result.remaining }
  } catch (error) {
    console.error('Rate limiter formularza jest niedostępny', error)
    return { allowed: false, unavailable: true }
  }
}
Code
// actions/contact-actions.ts
'use server'
 
import { checkContactRateLimit } from '@/lib/rate-limit-action'
 
export async function submitContactForm(formData: FormData) {
  // Rate limit — max 3 wiadomości na 10 minut
  const rateCheck = await checkContactRateLimit()
  if (!rateCheck.allowed) {
    return {
      error: rateCheck.unavailable
        ? 'Formularz jest chwilowo niedostępny.'
        : rateCheck.error,
    }
  }
 
  // ... walidacja danych i ochrona przed CSRF
  // ... wysyłanie formularza
}

Matcher Proxy ustawiony na /api/:path* nie obejmuje . Wywołanie akcji jest żądaniem POST do adresu strony, na której jej użyto, dlatego limit, uwierzytelnianie i autoryzację egzekwuj ponownie wewnątrz akcji. Dla akcji dostępnej tylko po zalogowaniu zamiast IP użyj ID ze zweryfikowanej sesji.

Alternatywa bez Redisa: rate limiting w pamięci podczas developmentu

Dla środowiska deweloperskiego lub pojedynczego, długo działającego procesu możesz użyć prostego limitera w pamięci:

Code
// lib/rate-limit-memory.ts
const requests = new Map<string, { count: number; resetAt: number }>()
 
export function rateLimit(
  identifier: string,
  limit: number = 10,
  windowMs: number = 60000,
): { success: boolean; remaining: number } {
  const now = Date.now()
  const record = requests.get(identifier)
 
  if (!record || now > record.resetAt) {
    if (record) requests.delete(identifier)
    requests.set(identifier, { count: 1, resetAt: now + windowMs })
    return { success: true, remaining: limit - 1 }
  }
 
  if (record.count >= limit) {
    return { success: false, remaining: 0 }
  }
 
  record.count++
  return { success: true, remaining: limit - record.count }
}

To uproszczony przykład: w realnym procesie potrzebujesz okresowego usuwania wygasłych kluczy, inaczej losowe identyfikatory mogą bez końca zwiększać pamięć. W i Edge Runtime każda instancja ma osobny Map, a cold start zeruje stan, więc nie uzyskasz globalnego limitu. W rozproszonym środowisku produkcyjnym użyj współdzielonego magazynu, np. Upstash Redis.

Monitoring, testy i koszty rate limitingu

Po wdrożeniu systemu kontroluj liczbę dozwolonych oraz zablokowanych żądań, przyczyny decyzji, opóźnienia wywołań i częstotliwość timeoutów. Twój system alarmowy powinien rozróżniać faktyczne odpowiedzi z kodem 429 od trybów awaryjnych typu fail-open oraz fail-closed. Pamiętaj też o ochronie prywatności i nigdy nie zapisuj w logach pełnych adresów IP, adresów e-mail ani tokenów. Jeśli potrzebujesz korelacji danych, skracaj lub hashuj identyfikator użytkownika.

Kompleksowy test integracyjny musi weryfikować przepuszczanie pierwszych żądań, poprawność zwracanego kodu 429 oraz nagłówka Retry-After po przekroczeniu limitu, a także powrót do obsługi ruchu po wygaśnięciu okna czasowego. Sprawdzaj też niezależność liczników dla różnych identyfikatorów, zachowanie systemu przy serii równoległych zapytań oraz reakcję na błędy lub timeouty połączenia z Upstash. Testy uruchamiaj na osobnym prefiksie kluczy i dedykowanej instancji Redisa, aby poszczególne przebiegi nie wpływały na siebie nawzajem.

Ostateczny koszt rozwiązania zależy od wybranego algorytmu i dodatkowych funkcji. Sliding window oraz token bucket generują więcej operacji niż prosty fixed window, moduł analityczny dokłada kolejne zapytania, a dwupoziomowe ograniczenia zwielokrotniają liczbę weryfikacji. Zadbaj o to, aby baza danych znajdowała się jak najbliżej środowiska uruchomieniowego, stale monitoruj opóźnienia na percentylach p95 oraz p99 i zachowaj szczególną ostrożność przy sliding window w architekturze wieloregionowej.

Elastyczne i wydajne narzędzia dla biznesu, które dotrzymają kroku Twojemu rozwojowi.
Next.js

Często zadawane pytania

Ile kosztuje Upstash Redis?

Cennik Upstash zmienia się w czasie, więc sprawdzaj aktualne limity w ich panelu. Plan darmowy może wystarczyć w małym projekcie, ale przy publicznym API policz realną liczbę żądań przed wdrożeniem. Pojedyncze sprawdzenie limitu może wykonać kilka komend Redis, zależnie od algorytmu, cache i analytics.

Czy rate limiting chroni przed DDoS?

Częściowo. Rate limiting na poziomie aplikacji chroni przed prostymi atakami brute force i nadużyciami. Przy wolumetrycznym DDoS, czyli zalewie ruchem z tysięcy źródeł, potrzebujesz ochrony na poziomie sieci, np. Cloudflare albo AWS Shield, bo do warstwy aplikacji takie żądania nie powinny w ogóle docierać.

Jak identyfikować użytkowników za NAT?

Sam adres IP nie jest idealny, ponieważ wielu użytkowników za NAT współdzieli jeden adres, przez co limit może uderzyć w niewinnych. Nie doklejaj nagłówka User-Agent do klucza ograniczenia, ponieważ klient może go modyfikować i obchodzić zabezpieczenia. Dla zalogowanych użytkowników stosuj zweryfikowane ID konta lub klucz API, a na szczególnie wrażliwych endpointach łącz oba podejścia: osobny limit per konto i osobny limit per zaufany adres IP.

Który algorytm rate limitingu wybrać?

Sliding window dla większości API — wygładza problem granicy okien, przy której fixed window pozwala na podwójny skok ruchu. Token bucket tam, gdzie chcesz świadomie dopuścić krótkie serie żądań, np. autocomplete albo przeszukiwanie. Fixed window tylko wtedy, gdy zależy Ci na maksymalnej prostocie.

Dlaczego in-memory rate limiter nie działa na produkcji?

Bo w serverless i Edge Runtime każda instancja funkcji ma osobną pamięć. Limit liczony w Map jednej instancji nie wie o żądaniach obsłużonych przez inną. Atakujący trafiający na różne instancje obejdzie limit. Potrzebujesz współdzielonego stanu, czyli np. Redisa w Upstash.

Co się dzieje, gdy Redis przestanie odpowiadać?

To zależy od strategii, którą świadomie wybierzesz. Fail-open przepuszcza ruch, gdy limiter nie działa. API zostaje na chwilę bez ochrony, ale użytkownicy nie widzą błędu; to rozsądny domyślny wybór dla odczytu. Fail-closed odrzuca ruch przy awarii limitera. Wybierasz bezpieczeństwo kosztem dostępności, co ma sens dla logowania i płatności. Owiń wywołanie limitera w try/catch, ale sprawdzaj też reason: 'timeout': wbudowany timeout SDK domyślnie zwraca wtedy wynik fail-open zamiast rzucać wyjątek. Zaloguj zdarzenie i zwróć decyzję zgodną z wybraną strategią.

Jak ograniczyć liczbę operacji na Redisie i koszt rate limitingu?

ephemeralCache jest domyślnie włączony w Ratelimit; możesz też przekazać własny Map utworzony poza handlerem. Zapamiętuje lokalnie już zablokowane identyfikatory do czasu resetu i dla kolejnych odrzuceń nie odpytuje Redisa. Działa tylko w obrębie ciepłej instancji, więc zmniejsza koszt podczas powtarzalnego nadużycia, ale nie zastępuje współdzielonego limitu.

O autorze

Maciej Sala

Maciej Sala — Product Manager i Frontend Developer z bogatym doświadczeniem w marketingu internetowym oraz SEO. Na co dzień pracuje z Reactem, Next.js i TypeScriptem, a ostatnio także z Astro i narzędziami do automatyzacji procesów AI. Sprawnie łączy perspektywę produktową z praktycznym podejściem do kodu. Przez kilka lat był związany z branżą gier wideo jako project manager i game designer. Absolwent historii na Uniwersytecie Jagiellońskim oraz studiów podyplomowych z marketingu internetowego na AGH w Krakowie. Po godzinach trenuje na siłowni, maluje figurki i rozwijam własne projekty.

Pomagam przekładać takie tematy na konkretne wdrożenia w frontendzie, SEO, analityce i procesie produktowym.

Skontaktuj się ze mną

Biblioteka wiedzy na temat Next.js

Czytaj dalej

Zobacz więcej wpisów
Edge Runtime w Next.js — kiedy Edge, a kiedy Node?

Edge Runtime obiecuje błyskawiczne działanie kodu tuż przy użytkowniku. To ultralekkie środowisko, oparte na V8 Isolates i rozproszone globalnie, eliminuje cold start , bo nie wymaga rozgrzewania ciężkich maszyn wirtualnych. Przez lata wydawało się to jedynym słusznym kierunkiem, ale dzisiaj jednak, w Next.js 16, automatyczny wybór Edge może okazać się strategicznym błędem.

Maciej Sala

Maciej Sala

Founder StriveLab

Middleware w Next.js — 7 zastosowań i typowych pułapek

Większość deweloperów może kojarzyć middleware w Next.js ze sprawdzaniem, czy użytkownik jest zalogowany, podczas gdy dla żądań objętych matcherem może ono podjąć decyzję jeszcze przed routingiem: przypisać wariant eksperymentu, zastosować limit, wykonać rewrite albo dodać zależny od requestu nagłówek. W artykule chciałem omówić siedem optymalnych i bezpiecznych zastosowań Proxy.

Maciej Sala

Maciej Sala

Founder StriveLab

Strategie cache w Next.js 16: unstable_cache czy Redis?

Zarządzanie pamięcią podręczną cache w Next.js może wydawać się proste, dopóki nie napotkamy wyzwań w środowisku produkcyjnym. Wiele instancji, webhooki z systemów CMS oraz dane, które bywają nieaktualne po wdrożeniu, potrafią skomplikować sytuację. App Router oferuje rozszerzony fetch, unstable cache, deduplikację żądań oraz Cache Components. Redis z kolei dostarcza wspólny magazyn, który jest łatwiejszy do monitorowania. Zastanówmy się więc, jaki konkretny problem próbujemy rozwiązać za pomocą mechanizmów buforowania?

Maciej Sala

Maciej Sala

Founder StriveLab