ContentForger

Guides

Rate Limiting Explained: What It Is and Why Your App Needs It

Rate limiting explained for first-time app builders: what it is, which endpoints to protect, where to add it in Next.js, and how to answer with a 429.

By Thomas Lovas Lokoy — 2026-10-17, last updated 2026-10-17

Traffic lights over a city street
Photo: Strange traffic lights by Tilemahos Efthimiadis · CC BY 2.0 · cropped
On this page (7)
  1. What rate limiting is
  2. Why your app needs rate limiting
  3. Which endpoints to limit first
  4. Where to put rate limiting in a Next.js app
  5. Responding well when a limit is hit
  6. Choosing the numbers
  7. Sources

Rate limiting means capping how many requests a client can make in a given amount of time, and refusing or delaying the rest. It is one of the cheapest protections a web app can have and one of the most often skipped in a first build, because nothing goes wrong until someone, or something, starts sending requests in a loop. This guide explains what rate limiting is, what it protects you from, where to put it in a Next.js app, and how to choose limits that stop abuse without annoying real users.

What rate limiting is

The idea has a precise definition in the HTTP standard. RFC 6585, published by the IETF in April 2012, defines the status code 429 Too Many Requests:

The 429 status code indicates that the user has sent too many requests in a given amount of time ("rate limiting").

Every rate limiter, from a few lines in a route handler to a rule on a global network, has the same parts:

  • A key that identifies who is making the request: an IP address, a user ID, an API key or an email address.
  • A window, the period over which requests are counted, such as ten seconds or one hour.
  • A limit, the number of requests allowed per key per window.
  • An action when the limit is reached: reject with 429, slow the client down, or present a challenge.

Cloudflare's documentation for its rate limiting rules uses the same vocabulary: the characteristics that decide how requests are counted, a period, a number of requests per period, an action, and a mitigation timeout that keeps the action in place for a set time after the limit is hit.

Notably, the standard leaves the details to you. RFC 6585 says: "Note that this specification does not define how the origin server identifies the user, nor how it counts requests." Choosing the key and the numbers is a design decision for your app.

Why your app needs rate limiting

Brute-force and credential-stuffing attacks

A login form with no limit lets an attacker try thousands of passwords against one account, or try passwords leaked from other sites against thousands of accounts. OWASP's Top 10:2025 lists automated attacks such as credential stuffing and brute force under Authentication Failures, and recommends limiting or progressively delaying failed login attempts. The OWASP Authentication Cheat Sheet adds an important detail: the counter of failed logins "should be associated with the account itself, rather than the source IP address". Otherwise an attacker with many IP addresses can simply spread the attempts out.

Your bill

Many of the most valuable endpoints in a modern app call a paid service: sending an email, sending an SMS, calling an AI model, generating an image. OWASP's API Security Top 10 describes this as Unrestricted Resource Consumption and gives an example of a "forgot password" flow that sends an SMS through a paid provider on every request. An attacker repeats the call tens of thousands of times and the company pays for every message. A limit of a few requests per phone number per hour would have stopped it.

Availability

Even without a malicious actor, a buggy script, a retry loop in your own front end or an over-eager crawler can send more traffic than a small database can handle. OWASP's newest Top 10 category, Mishandling of Exceptional Conditions, recommends rate limiting, quotas and throttling, and states the principle plainly: "Nothing in information technology should be limitless, as this leads to a lack of application resilience, denial of service, successful brute force attacks, and extraordinary cloud bills."

Which endpoints to limit first

You do not need a limit on every route on day one. Start where abuse is cheap for the attacker and expensive for you:

EndpointSuggested keyWhy
LoginAccount (email) plus IPStops password guessing against one account and spraying across many
Sign-upIPStops mass fake accounts
Password reset and verification codesEmail or phone numberEach request sends a message you pay for
Contact and feedback formsIPStops spam floods into your inbox
AI, image or other paid API callsUser IDEach call has a direct cost
Search and other heavy queriesUser ID or IPProtects database performance

OWASP's API guidance recommends exactly this tiering: limits tuned to business needs, with stricter limits on sensitive operations such as one-time-password validation and password recovery.

Where to put rate limiting in a Next.js app

There are two layers, and the Next.js documentation recommends using both: "In addition to code-based checks, enable any rate limiting features provided by your host."

At the edge: your host or CDN

A rule at your hosting provider or CDN stops excess traffic before it reaches your code, which protects against floods and costs you nothing in compute. Cloudflare's rules, for example, can match a path such as a login endpoint and block or challenge clients that exceed a threshold. Two limits are worth knowing. On Cloudflare's Free and Pro plans, rules count by IP address only, and the documentation warns that "Rate limiting rules are not designed to allow a precise number of requests to reach your origin server": counters can lag by a few seconds, and they are kept per data centre rather than globally.

Edge rules are good at "too many requests from this IP". They are not good at "too many reset codes for this email address", because they do not know your users.

In your code: route handlers and server actions

Application-level limits can use keys that only your app knows: the signed-in user's ID, the email in a login form, the customer's plan. The Next.js backend guide shows the pattern: at the top of a Route Handler, call a check function, and if the client is over the limit, return a 429 response before doing any work. The data security guide recommends the same for Server Actions that do expensive things such as sending email or writing to the database.

One trap catches almost everyone. It is tempting to keep the counter in a variable in memory. The Next.js documentation notes that some hosts deploy Route Handlers as serverless functions, which means "Route Handlers cannot share data between requests." Each instance keeps its own count, instances come and go, and the limit quietly stops working. Store counters somewhere shared, such as a key-value store or your database, with an expiry that matches the window.

Responding well when a limit is hit

A good 429 response helps legitimate clients recover:

  • Send the right status code. Use 429, not 500 or 403, so clients and monitoring tools know what happened.
  • Say when to retry. RFC 6585 says the response "MAY include a Retry-After header indicating how long to wait before making a new request." MDN documents two formats for that header: a number of seconds, or an HTTP date.
  • Explain in plain language. "Too many attempts. Try again in 10 minutes." is enough. Do not reveal whether an account exists.
  • Do not cache it. RFC 6585 states that "Responses with the 429 status code MUST NOT be stored by a cache."

There is also ongoing standards work on telling clients their remaining quota before they hit it. The IETF draft "RateLimit header fields for HTTP" defines RateLimit-Policy and RateLimit response headers. It is still an Internet-Draft, a work in progress that may change, so treat it as optional for now.

Choosing the numbers

There is no universal right limit; RFC 6585's own example is a site that allows 50 requests per hour per logged-in user, which is reasonable for one app and absurd for another. A practical approach:

  • Start from real usage. How many times would an honest user plausibly submit this form in an hour? Set the limit comfortably above that.
  • Be strict where each request costs money or guards an account, and lenient on ordinary page reads.
  • Watch for side effects of locking accounts. The OWASP Authentication Cheat Sheet warns that "care must be taken to prevent it from being used to cause a denial of service by locking out other users' accounts." Letting the password reset flow still work for a locked account is one mitigation it suggests.
  • Log every 429. A sudden cluster is an early warning of an attack, and a steady trickle from real users means the limit is too tight.
  • Pair limits with spending caps at your paid providers, so a gap in your own code still has a ceiling.

Rate limiting is one item among many before launch. For the rest, see the pre-launch checklist for generated code, and read how to keep API keys and secrets out of your code so that the keys behind those paid endpoints stay private too.

Sources

ShareXLinkedInFacebook

Was this article helpful?

New guides in your inbox

One short email when we publish something useful — tutorials, comparisons and launch checklists. Unsubscribe in one click.

Related articles