Rate Limiting Your Next.js API Routes
Cover photo by Taylor Vick on Unsplash
Rate limiting belongs on your server side, not your frontend.
You have likely heard that you should throttle clicks on your UI to prevent excessive API requests. People say this because it creates a snappier feeling for the user. That is true for preventing accidental double submissions, but it is a total myth that this counts as API protection. You can never trust the client. A person with a basic terminal can bypass your React component logic and spam your endpoint until your database cries for mercy. If you want to protect your routes, you have to do it on the backend.
Where does the limit live?
I prefer using a middleware approach in Next.js. Since API routes are essentially serverless functions, you need a way to track state across invocations. You cannot just use a local variable because the instance might spin down or spin up in a different region. I usually reach for Redis. It is fast, atomic, and keeps my logic clean.
Here is a simple example using the upstash-ratelimit package because I don’t feel like reinventing the wheel with manual Redis logic every time. (Honestly, who has the time to build a distributed counter from scratch unless they have to?)
import { Ratelimit } from "@upstash/ratelimit";import { Redis } from "@upstash/redis";
const ratelimit = new Ratelimit({ redis: Redis.fromEnv(), limiter: Ratelimit.slidingWindow(10, "60 s"),});
export async function POST(req) { const ip = req.ip ?? "127.0.0.1"; const { success } = await ratelimit.limit(ip);
if (!success) { return new Response("Too many requests", { status: 429 }); }
// Process your request here}Why sliding windows matter
Some folks suggest a simple fixed window. That means you reset the count at the start of every hour. The problem is that if someone hammers your API at the end of the hour and again at the start of the next, they get double the quota. A sliding window, like I showed above, prevents that spike. It feels smoother and provides better protection against bursts.
What about users behind a proxy?
“But Namir, what if my users are behind a shared NAT or a VPN? They will all share the same IP address and get blocked together.” This is a valid objection. If you run a corporate tool or a service accessed by a large office, rate limiting by IP is definitely going to hurt some innocent people. In those cases, you should switch to rate limiting by an authentication token or a user ID instead. If they are logged in, track them by their session. If they are anonymous, you are stuck with IP, and you just have to accept that some collision might happen.
Is this overengineering?
Maybe. If you are building a small personal site, you might not need this. I’ve definitely skipped it on projects where the cost of a few extra requests was effectively zero. However, if you are exposing a sensitive route, like a password reset or a heavy database query, don’t wait for your usage bill to spike before adding a few lines of code.
I typically start with a generous limit. You can always tighten the screws later once you see how people actually use the system. It is much easier to be lenient on day one than to deal with support tickets from frustrated users who hit your arbitrary limit because your numbers were too low.
What is your preferred way to handle traffic spikes without using a third party service? I am still searching for a zero dependency way to do this that doesn’t feel like I am fighting the serverless environment.