Arcjet vs Upstash Ratelimit vs Unkey 2026: Best Rate Limiting for Next.js
Rows of servers in a data center representing API rate limiting infrastructure
The first time a scraper, a runaway script, or a credential-stuffing bot hits your Next.js API, you learn the same lesson everyone does: an unprotected endpoint is a liability. Rate limiting is no longer a "nice to have" you bolt on before launch — it is table stakes for any route that signs people up, sends email, calls a paid LLM, or touches a database.
In 2026 three names dominate the "how do I rate limit my Next.js API" conversation: Arcjet, Upstash Ratelimit, and Unkey. They solve overlapping problems from very different angles. One is a security SDK. One is a Redis-backed library. One is an API-key platform. This guide breaks down how each actually works, what it costs, and the clear situations where one beats the other two.
The 30-second answer
If you remember nothing else: Upstash for control, Arcjet for all-in-one protection, Unkey for API-key products.
Why in-memory rate limiting fails on serverless
Before comparing the three, it helps to understand why you need any of them. The naive approach is a Map in memory that counts requests per IP. It works perfectly on your laptop and falls apart in production.
Next.js on Vercel, or any serverless platform, runs many instances of your function in parallel and spins them up and down on demand. Each instance has its own memory. A user making ten requests might hit ten different instances, each seeing "one request" and letting them all through. Your limit of ten per minute silently becomes ten per minute *per instance*.
Fixing this needs shared state that every instance reads from — a centralized counter. That is the problem Upstash, Arcjet, and Unkey each solve in their own way.
How each one actually works
Upstash Ratelimit
Upstash Ratelimit is an open-source SDK that runs against an Upstash Redis database over a REST API, which means it works from edge and serverless runtimes without a persistent TCP connection. You pick an algorithm and a window, and the library stores the counters in your Redis.
It supports fixed window (simple, cheap, allows bursts at window edges), sliding window (smoother, the common default), and token bucket (lets you model burst capacity plus a steady refill rate). You can define multiple limits for different plans and use ephemeral in-memory caching to short-circuit obviously-blocked clients and save Redis calls. Because you own the Redis instance and the config, you have the most control of the three — and the most to wire up.
Arcjet
Arcjet is a security SDK first and a rate limiter second. You install one package and compose rules: rate limiting (fixed window, sliding window, or token bucket), bot detection, a Shield WAF that catches common attacks like SQL injection, email validation that rejects disposable or invalid addresses, and more recent additions like PII detection. The decision runs close to your code, and the heavy analysis happens on Arcjet's side so your function stays light.
The appeal is consolidation: instead of gluing together a rate limiter, a bot filter, and an email validator from three vendors, you express all of it as rules in one place. It works across Next.js, Nuxt, SvelteKit, Astro, Express, Fastify, Hono, NestJS, Remix, and more. The trade-off is that you are adopting a whole protection layer, not just a counter.
Unkey
Unkey is built for a specific shape of product: you have an API and you sell or gate access to it. It issues and verifies API keys, meters usage, and — the part relevant here — does globally distributed rate limiting at the edge with no Redis for you to run.
It offers two modes. Standalone limits let you call limit() against any identifier (a user ID, an IP, a route) just like a classic rate limiter. Key-attached limits are set per API key and enforced automatically during key verification, so every customer's key carries its own ceiling. For an API product, that second mode is the killer feature — your rate limits live with your keys instead of being reimplemented in app code.
Side-by-side comparison
| Factor | Upstash Ratelimit | Arcjet | Unkey |
|---|---|---|---|
| Primary job | Rate limiting library | Full abuse-protection SDK | API-key management + limits |
| Backing store | Your Upstash Redis | Managed by Arcjet | Managed by Unkey |
| Infra to run | Upstash Redis database | None | None |
| Algorithms | Fixed, sliding, token bucket | Fixed, sliding, token bucket | Sliding window |
| Bot detection | No | Yes | No |
| WAF / Shield | No | Yes | No |
| Email validation | No | Yes | No |
| Per-API-key limits | Manual | Manual | Native |
| Edge-ready | Yes (REST) | Yes | Yes |
| Best framework fit | Any | Next.js, many more | Any API product |
| Pricing shape | Free SDK + Redis usage | Usage-based per request | Free tier + usage |
Pricing figures move constantly; always confirm the current numbers on each vendor's pricing page before you commit. The *shape* of the models rarely changes: Upstash bills you for Redis commands, Arcjet bills per million requests analysed, and Unkey bills per verification and rate-limit check above a free allotment.
Code: the same guard, three ways
All three slot into a Next.js route handler. Here is the same "limit this endpoint" guard in each.
// Upstash Ratelimit — sliding window, backed by your Upstash Redis
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({
redis: Redis.fromEnv(),
limiter: Ratelimit.slidingWindow(10, "60 s"),
analytics: true,
});
export async function POST(req: Request) {
const ip = req.headers.get("x-forwarded-for") ?? "anonymous";
const { success, limit, remaining } = await ratelimit.limit(ip);
if (!success) {
return new Response("Too many requests", {
status: 429,
headers: { "X-RateLimit-Remaining": String(remaining) },
});
}
return Response.json({ ok: true });
}// Arcjet — rate limit + bot detection in one rule set
import arcjet, { tokenBucket, detectBot } from "@arcjet/next";
const aj = arcjet({
key: process.env.ARCJET_KEY!,
rules: [
tokenBucket({ mode: "LIVE", refillRate: 5, interval: 10, capacity: 10 }),
detectBot({ mode: "LIVE", allow: ["CATEGORY:SEARCH_ENGINE"] }),
],
});
export async function POST(req: Request) {
const decision = await aj.protect(req, { requested: 1 });
if (decision.isDenied()) {
return new Response("Forbidden", { status: 403 });
}
return Response.json({ ok: true });
}// Unkey — standalone edge rate limit, no Redis to manage
import { Ratelimit } from "@unkey/ratelimit";
const unkey = new Ratelimit({
rootKey: process.env.UNKEY_ROOT_KEY!,
namespace: "api",
limit: 10,
duration: "60s",
});
export async function POST(req: Request) {
const ip = req.headers.get("x-forwarded-for") ?? "anonymous";
const { success } = await unkey.limit(ip);
if (!success) return new Response("Too many requests", { status: 429 });
return Response.json({ ok: true });
}Notice the shape is nearly identical — call a guard, check success, return a 429 or 403. What differs is everything *behind* that call: Upstash reads your Redis, Arcjet also runs bot analysis, and Unkey resolves against its global edge network. (APIs evolve; check each SDK's current docs for exact method names before copying.)
Global network of connected nodes representing edge-distributed rate limiting
When to choose each one
Choose Upstash Ratelimit if…
It pairs naturally with the rest of the Upstash stack — see how Upstash Redis itself stacks up in our Upstash vs Redis Cloud vs Dragonfly comparison.
Choose Arcjet if…
If bot traffic is your real problem, also weigh a dedicated challenge layer — compare the options in Turnstile vs reCAPTCHA vs hCaptcha, and lock down the auth routes themselves with a solid stack from Clerk vs Auth.js vs Better Auth.
Choose Unkey if…
Unkey fits neatly in front of an API built with something like Hono, and the choice of REST vs GraphQL vs tRPC does not change which protection layer you reach for.
The honest trade-offs
No tool is free of sharp edges.
Where the request runs matters
As with databases, latency is about *distance*. A rate-limit check that adds 150 ms because it crosses a continent will hurt more than the abuse it prevents. All three are built for serverless and edge runtimes, but confirm your limiter resolves near your compute. If you are still choosing that compute layer, our Cloudflare Workers vs AWS Lambda vs Vercel Functions guide covers the trade-offs that decide where your guard actually runs.
Bottom line
There is no single winner, because these three target different problems:
Decide based on whether you need more than counting (then Arcjet) and whether you are shipping an API product (then Unkey); otherwise Upstash Ratelimit is the dependable default.
---
Building a Next.js SaaS and want the API layer handled for you? Browse production-ready starters on CodeCudos — including Next.js auth templates and Stripe subscription SaaS boilerplates that wire auth, billing, and protected routes together so you can ship in a weekend.
