NextJS Middleware full NodeJS access

2 weeks ago
|
0 views

TL;DR: Next.js's middleware only allow access to limited Node.js's API. If you need to access the full Node.js's API, you need to use some crazy workaround or implement a custom server for your Next.js app.

Problem

Let's say you want to implement an rate limiting for you NextJS app, store the data in Redis, and also want to use the same connection pool for your API route. It seem like a perfect case for using NextJS's middleware, right ?

// middleware.ts
import Redis from "ioredis";
 
export const redis = new Redis('redis://localhost:6379');
 
const rateLimitCheck = (ip: string) => {
  // some logic to check the rate limit
  if (valid) {
    return true;
  }
  return false;
}
 
export async function middleware(req) {
  const res = NextResponse.next();
  const ip = req.ip;
  if (rateLimitCheck(ip)) {
    return res;
  }
  return new Response('Too many requests', { status: 429 });
}

This code seems totally fine, but it won't work. When you run it, you will see the error that explains that middleware cannot handle the Redis import because some API is missing in the middleware runtime. That because of this

Middleware currently only supports the Edge runtime. The Node.js runtime can not be used.

So, because ioredis uses some Node.js's API that Vercel's Edge runtime doesn't support, there is no way to use ioredis in the middleware of Next.js, and that just so sucks to realize. To be honest, I was really frustrated when I realize that, because my NextJS app is running fully on Node in my VPS and nothing is being used from Edge runtime.

Not just you, the community is also complaining about this. There is an one of the most upvoted and reacted issues about this annoying problem. NextJS's team know this but decide the put this in the deep of their backlogs and just say some promises about "we're working on it" and "it's in our roadmap". But trust me, it's been years and nothing has changed.

So, we must save ourselves and find a solution for this.

Workaround

Implement your logic in API route, and then call that API route from middleware. Crazy and seem anti-pattern, but it works.

// api/rate-limit.ts
import Redis from "ioredis";
 
export const redis = new Redis('redis://localhost:6379');
 
const rateLimitCheck = (ip: string) => {
  // some logic to check the rate limit
  if (valid) {
    return true;
  }
  return false;
}
 
export async function GET(req: NextApiRequest, res: NextApiResponse) {
  // get ip from request params
  const ip = req.query.ip;
  if (rateLimitCheck(ip)) {
    return res.status(200).json({ success: true });
  }
  return res.status(429).json({ success: false });
}
 
// middleware.ts
export async function middleware(req) {
  const res = NextResponse.next();
  const ip = req.ip;
  const response = await fetch(`${process.env.YOUR_DOMAIN}/api/rate-limit?ip=${ip}`);
  const data = await response.json();
  if (data.success) {
    return res;
  }
  return new Response('Too many requests', { status: 429 });
}

Long term solution

The best solution is to use a full NodeJS server for your NextJS app, and then you can use all the NodeJS's API in your server. You can use a custom server with NextJS, but you need to do some more work to make it works.

Happy coding! 🚀