Next 14 Rate Limiting made easy

Next 14 Rate Limiting made easy

Rate limiting plays a crucial role in preventing abuse by controlling the number of requests a user can make within a certain period. While, to keep track of requests and ips, we need to store them in-memory for ease of access and speed in response. ⛔✋

Running your Next.js app on Vercel’s serverless or edge runtimes offers incredible speed and scalability. These environments handle requests in isolation, spinning up lightweight instances as needed to deliver lightning-fast responses. However, this architecture has a key limitation: data stored in an instance’s memory isn’t accessible to others, as each instance is stateless.

To address this, we often need external solutions like Redis, a high-performance in-memory database. Services like Upstash Redis complement Vercel perfectly by enabling shared, low-latency data access across instances.

With that foundation set, let’s dive into the implementation!

Method 1: Upstash Redis 🙌

In your Vercel project page, go to Storage tab.
Click on Create Database button, then select Upstash for Redis:

Choose Vercel Storage

After creating the db, you'll see the list of secrets.
Keep in mind that these secret variables are already bound to your vercel project and you don't need to redefine them, but for local development you will need to copy them into your .env.local file.

Environment Variables

Don't forget to put this line in your .gitignore:

.env*.local
Enter fullscreen mode Exit fullscreen mode

Install the following modules:

$ npm i @upstash/redis @upstash/ratelimit 
Enter fullscreen mode Exit fullscreen mode

Create a ratelimit instance and use it

// route.ts
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const rateLimiter = new Ratelimit({
  limiter: Ratelimit.fixedWindow(1, "60 s"),
  redis: Redis.fromEnv(),
  analytics: true,
  prefix: "@upstash/ratelimit",
});

const getIP = (req: Request) => {
  const ip =
    req.headers.get("cf-connecting-ip") || 
    req.headers.get("x-forwarded-for") ||
    undefined;
  return Array.isArray(ip) ? ip[0] : ip;
};

export async function POST(req: Request) {
  // ...
  // Here we check if limit hit! 👇
  const { success } = await rateLimiter.limit(ip || "unknown");
  if (!success) return Response.json({ error: "Too many requests" }, { status: 429 } );
  // ...
}
Enter fullscreen mode Exit fullscreen mode

And done!

Method 2: Tile38 self-hosted

This method is not relied on third-party services with their limitations but requires you to have a server.

Install docker on your server if not installed already.

Run Tile38 docker with custom configs

$ docker run --name tile38 -p 9851:9851 tile38/tile38
Enter fullscreen mode Exit fullscreen mode

Config ufw-docker to open related port

$ sudo ufw-docker allow tile38
Enter fullscreen mode Exit fullscreen mode

Or you can use reverse-proxy on nginx if you want to keep it behind a domain.

Now that everything is set, it's time for usage.
To bypass the limitations, we can create our own adapter as below:

const axios = require('axios');
const { Ratelimit } = require('@upstash/ratelimit');

// Tile38 HTTP API URL
const TILE38_URL = 'replace_it_with_your_own_url';

// Custom adapter for Tile38
const customTile38Adapter = {
  // Custom function to simulate Lua script execution via HTTP API
  eval: async (script, keys, args) => {
    // Tile38 doesn't directly support Lua, but you can use its commands for rate limiting
    // We simulate the rate-limiting logic using Tile38's API.
    try {
      const key = keys[0]; // Assuming we're using a single key for rate-limiting
      const value = args[0]; // The value used for rate-limiting

      // Example: Get a key from Tile38 (you could implement rate-limiting logic here)
      const response = await axios.post(`${TILE38_URL}/set`, {
        key: key,
        value: value,
      });

      // Example: Set rate limit logic (for example, using a custom TTL or counter)
      const success = response.status === 200;
      return { success };
    } catch (error) {
      console.error('Tile38 error:', error);
      return { success: false };
    }
  },
};

// Initialize the rate limiter with Tile38 as the adapter
const ratelimit = new Ratelimit({
  redis: customTile38Adapter, // Using the custom adapter
  limiter: Ratelimit.fixedWindow(10, '10s'), // 10 requests per 10 seconds
});

...
Enter fullscreen mode Exit fullscreen mode

Congratulations! 🎉 You’ve unlocked the power of combining Vercel’s serverless capabilities with Upstash Ratelimit. This setup will not only elevate your app’s performance but also open the door to building highly scalable, fast, and reliable applications. With this powerful foundation, your Next.js app is ready to handle whatever comes next—smoothly and efficiently. 🚀