Setting up rate limiting in a Remix app
Learn how to implement rate limiting in Remix using server framework middleware and platforms like Cloudflare and Vercel
January 21, 2025Rate limiting is an essential part of building a web app. Without it, you risk your server being overwhelmed, intentionally or unintentionally, by individual bots or users which can degrade the experience for everyone. Today I'm going to show you how easy it is to implement rate limiting in Remix via server framework middleware and using platforms like Cloudflare and Vercel.
Rate limiting as a middleware
The easiest and most common way to implement rate limiting is as a middleware as it allows you to apply granular rate limiting to specific routes or groups of routes in your code. Since Remix is ultimately just a server-side route handler, if you run it via Express, Fastify or any other server framework, you can make use of the existing rate limiting middleware available for those frameworks.
For example, if you're using Express, you can use the express-rate-limit
package to easily add rate limiting to your Remix app. Here's an example of how you might use it to protect your auth routes (e.g. /auth/login
, /auth/register
, etc.):
// rate-limiters.js import rateLimit from 'express-rate-limit'; const createError = (timeLeft) => ({ error: 'Too many requests', message: `Please try again in ${timeLeft}`, }); const getTimeLeft = (resetTime): string => { const seconds = Math.ceil((resetTime.getTime() - Date.now()) / 1000); return seconds < 60 ? `${seconds}s` : `${Math.ceil(seconds / 60)}m`; }; const baseConfig = { windowMs: 60 * 1000, standardHeaders: true, legacyHeaders: false, handler: (req, res) => { res.status(429).json(createError(getTimeLeft(req.rateLimit.resetTime))); }, }; export const rateLimiterMiddleware = (req, res, next) => { if (req.path.startsWith('/auth')) { authLimiter(req, res, next); } else { next(); } };
You can now simply add this as a middleware to your Express app:
// server.js import { createRequestHandler } from "@react-router/express"; import express from "express"; // 1. Import the rate limiter middleware import { rateLimiterMiddleware } from "./rate-limiters.js"; import * as build from "./build/server/index.js"; const app = express(); // 2. Add the rate limiter middleware app.use(rateLimiterMiddleware); app.use(express.static("build/client")); app.all("*", createRequestHandler({ build })); app.listen(3000, () => { console.log("App listening on http://localhost:3000"); });
That's it! Your authentication routes are now protected from attacks like brute force login attempts and your server is safe from being overwhelmed by too many requests. But what about the other routes?
You can easily extend the rateLimiterMiddleware
function to add rate limiting to other routes as well. Let's add a global rate limit, as well as one for our /api
routes:
// rate-limiters.js // // same as before // export const limiters = { // Existing auth rate limiter auth: rateLimit({ ...baseConfig, max: 10 }), // 10/min for auth // New rate limiters api: rateLimit({ ...baseConfig, max: 100 }), // 100/min for API global: rateLimit({ ...baseConfig, max: 300 }), // 300/min default }; export const rateLimiter = (req, res, next) => { const path = req.path; if (path.startsWith('/auth')) { limiters.auth(req, res, next); } else if (path.startsWith('/api')) { limiters.api(req, res, next); } else { limiters.global(req, res, next); } };
We now have three tiers of rate limiting throughout our app
- 10 requests per minute for auth routes
- 100 requests per minute for API routes
- 300 requests per minute for all other routes
Since this middleware is already being used, and is isolated from the rest of the app, adding or changing any rate limiters only require changes in this file.
By default, this will use in-memory storage to keep track of the rate limits. If you need to scale your app across more servers, or if you're using serverless, you can use a shared Redis store to keep track of the rate limits across multiple instances.
Rate limiting with other server frameworks
If you're not using Express, the code will be slightly different but the logic and flow will ultimately be the same as long as you can add middleware. For example, if you're using Fastify, you can use the fastify-rate-limit
package, or if using Hono you can use hono-rate-limiter
.
Rate limiting at the platform level
If you're using a platform like Vercel or Cloudflare, you can handle rate limiting at the platform level so that the requests never even reach your server. This can often be safer and more secure as there is basically no limit to the number of requests that can be handled by the platform, but it can be more expensive and less flexible than handling it yourself.
Cloudflare
Cloudflare offers free rate limiting as part of their firewall rules. On the free plan, it allows you to set up 1 rule which you can configure to limit requests based on IP, path, or other criteria.
There are more granular options on the paid plans, but the free plan should be sufficient if you only need to protect a few routes. If you're using Cloudflare you already be protected from DDoS attacks and other malicious traffic, so rate limiting is just an extra layer of protection.
Vercel
Vercel has a similar feature in their Web Application Firewall (WAF) offering which allows you to set up rate limiting rules based on the typical criteria. Like Cloudflare, the free plan is limited and in this case you can't return true 429
HTTP responses, but you can block or challenge requests that exceed the rate limit, so it's still useful.
Also like Cloudflare, hosting your app on Vercel will give you DDoS protection so if that's your main concern you might not need to worry about rate limiting.
Conclusion
As you can see, rate limiting is not that complicated. Regardless of how much traffic your app has, or how it's hosted, it's a no-brainer to add it to your app early to protect you and your users from malicious traffic.
If you're looking for a SaaS boilerplate with rate limiting and more best practices like this, check out Launchway. It also comes with authentication, subscriptions, database connections, tons of UI components and more to get your SaaS started.