HarshPatel

Ahmedabad, Gujarat
Back to Blog
Next.jsMiddlewareEdge FunctionsAuthA/B Testing

Next.js Middleware: Protecting Routes, A/B Testing, and Geo-Redirects in One File

Harsh PatelMay 4, 20262 min read9 views
Next.js Middleware: Protecting Routes, A/B Testing, and Geo-Redirects in One File

Introduction

Next.js middleware is one of its most powerful and underused features. It runs at the edge — meaning it executes before your page even starts rendering, with near-zero latency. You can intercept every request, inspect headers and cookies, and redirect, rewrite, or modify the response.

Here are three real-world use cases I use in production.

How middleware works

Create a file called middleware.ts in the root of your project (next to app/). It exports a function that receives a NextRequest and returns a NextResponse.

import { NextRequest, NextResponse } from "next/server";

export function middleware(request: NextRequest) {
  return NextResponse.next();
}

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};

Use case 1: Protect authenticated routes

import { NextRequest, NextResponse } from "next/server";

export function middleware(request: NextRequest) {
  const token = request.cookies.get("auth-token")?.value;
  const isProtected = request.nextUrl.pathname.startsWith("/dashboard");

  if (isProtected && !token) {
    const loginUrl = new URL("/login", request.url);
    loginUrl.searchParams.set("redirect", request.nextUrl.pathname);
    return NextResponse.redirect(loginUrl);
  }

  return NextResponse.next();
}

This runs at the edge — the user is redirected before the dashboard even starts rendering. No flash of protected content.

Use case 2: A/B testing with cookies

export function middleware(request: NextRequest) {
  const bucket = request.cookies.get("ab-bucket")?.value;
  const response = NextResponse.next();

  if (!bucket) {
    const newBucket = Math.random() < 0.5 ? "a" : "b";
    response.cookies.set("ab-bucket", newBucket, { maxAge: 60 * 60 * 24 * 30 });

    if (newBucket === "b" && request.nextUrl.pathname === "/") {
      return NextResponse.rewrite(new URL("/home-v2", request.url));
    }
  }

  if (bucket === "b" && request.nextUrl.pathname === "/") {
    return NextResponse.rewrite(new URL("/home-v2", request.url));
  }

  return response;
}

Users in bucket B see /home-v2 but the URL stays as /. The cookie persists for 30 days so the same user always sees the same variant.

Use case 3: Geo-based redirects

export function middleware(request: NextRequest) {
  const country = request.geo?.country || "US";

  if (request.nextUrl.pathname === "/" && country === "IN") {
    return NextResponse.redirect(new URL("/in", request.url));
  }

  if (request.nextUrl.pathname === "/" && country === "GB") {
    return NextResponse.redirect(new URL("/uk", request.url));
  }

  return NextResponse.next();
}

request.geo is available on Vercel deployments automatically. No third-party IP geolocation service needed.

Combining all three

You can chain all of these in a single middleware.ts. Just make sure the auth check runs first, since you don't want to A/B test unauthenticated users on protected pages.

Conclusion

Next.js middleware is one of those features that, once you start using it, you wonder how you shipped without it. Auth protection, experimentation, and localisation — all in one file, running at the edge before your server ever touches the request.

All Posts
Next.jsMiddlewareEdge FunctionsAuthA/B Testing