Techtales.

Rate Limiting Server Actions in NextJS
TD
The Don✨Author
Mar 17, 2026
4 min read

Rate Limiting Server Actions in NextJS

00

It's easy to look at Next.js Server Actions and forget that they are, effectively, public API endpoints. The "magic" abstraction layer often tricks us into skipping standard backend best practices like validation and security.

But under the hood, Server Actions are still triggered by standard HTTP requests, meaning they are just as vulnerable to spam and abuse as any other route. If you have an action that is computationally expensive or costs money (like an AI call), you need to protect it.

Here is how to properly rate-limit your Server Actions to keep your application resilient.


Rate-limiting based on User ID

Rate-limiting requires a unique identifier for the incoming request. If you are dealing with authenticated users, this is straightforward: use their database ID.

Here's how you might handle this in a standard API route:

When you move to Server Actions, the logic remains almost identical. The main difference is that instead of returning a 429 status code, you return a structured error object.


Rate-limiting based on IP address

Things get trickier when you need to rate-limit anonymous users (e.g., a public waitlist form or a "contact us" message). Since there is no user ID, the only reliable identifier is the IP address.

In a standard API route, you have direct access to the Request object that you can use to extract the IP address from the request headers.

For context, getIP is a small helper that looks at the x-forwarded-for header:

The Server Action problem

Let's try the same in a server action. Just extract the IP address from the request head... Hold on, we don't have a request object in a server action.

By design, Next.js Server Actions do not expose the raw Request object. However, we can work around this using the headers() function provided by next/headers. This gives us read-only access to the request headers associated with the current incoming request.

By accessing the IP address through the headers() function, we effectively work around the limitation of not having a request object in Server Actions and can apply rate-limiting just as we would in a standard API route.

Bonus

To make things spicy, we can create a custom rate limiter function that we can invoke in each route and return specific data based on the number of requests. Here is my custom rate-limiter function that takes params and returns whether the user is blacklisted or not:

This function requires the IP address when called, and therefore we must create a custom function that reads the headers and returns the client IP address.

0
0