DEV Community

Anas Sheikh
Anas Sheikh

Posted on

Next.js 15 Middleware Everything Beyond Auth Checks

Most middleware tutorials stop at "check if the user is logged in, redirect if not." That is a real use case, but middleware runs before every matched request, which makes it useful for a lot more than auth checks. Here is what else I actually reach for it.


1. What Middleware Actually Is

Middleware runs on the Edge Runtime, before a request reaches your actual route, and before any rendering starts. It can read the request, redirect, rewrite the URL, or modify headers, but it cannot do everything a normal Server Component or route handler can, no direct database access, no Node-specific APIs, since it runs in a more limited environment optimized for speed.

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

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

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

That matcher pattern excludes static assets and images, since middleware running on every single asset request adds latency for no benefit. Scoping it tightly matters more than it looks like it should once traffic is real.


2. Redirecting Old URLs

For a site migration or a URL structure change, redirecting at the middleware level keeps old links and search rankings intact instead of breaking them.

// middleware.ts
const redirectMap: Record<string, string> = {
  '/old-blog': '/blog',
  '/services': '/templates',
};

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  if (redirectMap[pathname]) {
    return NextResponse.redirect(new URL(redirectMap[pathname], request.url));
  }

  return NextResponse.next();
}
Enter fullscreen mode Exit fullscreen mode

For a handful of redirects, next.config.ts redirects (covered in the SEO metadata setup) are actually the better choice, since they are handled at the CDN level without invoking middleware at all. Middleware redirects make more sense when the logic is dynamic, based on something in the request itself, not just a static old-path-to-new-path map.


3. www and https Canonicalization

The exact fix for the www versus non-www split that shows up as duplicate pages in Search Console:

export function middleware(request: NextRequest) {
  const url = request.nextUrl.clone();
  const host = request.headers.get('host');

  if (host === 'pixelanas.com') {
    url.host = 'www.pixelanas.com';
    url.protocol = 'https';
    return NextResponse.redirect(url, 301);
  }

  return NextResponse.next();
}
Enter fullscreen mode Exit fullscreen mode

A 301, not a 302, matters here specifically for SEO. A 301 tells search engines this redirect is permanent, consolidating ranking signals onto the canonical URL. A 302 signals a temporary redirect, which does not pass that same consolidation.


4. Geolocation-Based Routing

Next.js middleware has access to geolocation data derived from the request, useful for showing region-specific pricing, currency, or content without a client-side flicker after the page has already loaded.

export function middleware(request: NextRequest) {
  const country = request.geo?.country ?? 'US';
  const response = NextResponse.next();
  response.headers.set('x-user-country', country);
  return response;
}
Enter fullscreen mode Exit fullscreen mode
// app/pricing/page.tsx
import { headers } from 'next/headers';

export default async function PricingPage() {
  const headersList = await headers();
  const country = headersList.get('x-user-country') ?? 'US';

  const currency = country === 'GB' ? 'GBP' : country === 'PK' ? 'PKR' : 'USD';

  return <PricingTable currency={currency} />;
}
Enter fullscreen mode Exit fullscreen mode

Passing the detected country through as a header, then reading it in the Server Component, keeps the actual pricing logic out of middleware itself, middleware's job here is just detection and forwarding, not business logic.


5. A/B Testing Without Layout Shift

Client-side A/B testing often causes a visible flicker, the default variant renders first, then swaps once JavaScript determines which test group the visitor belongs to. Assigning the variant in middleware avoids this entirely, since the decision happens before any HTML is sent.

export function middleware(request: NextRequest) {
  const response = NextResponse.next();

  let variant = request.cookies.get('ab-variant')?.value;

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

  response.headers.set('x-ab-variant', variant);
  return response;
}
Enter fullscreen mode Exit fullscreen mode

Setting the cookie on first visit and reusing it on subsequent requests keeps a visitor in the same variant across their whole session instead of flipping randomly on every page load, which would make the test data meaningless.


6. Rate Limiting at the Edge

For public routes with tight abuse concerns, checking a rate limit in middleware blocks the request before it reaches your actual route handler or Server Action at all, slightly earlier than checking inside the handler itself as covered in the rate limiting setup.

import { ratelimit } from '@/lib/ratelimit';

export async function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/api/public')) {
    const ip = request.headers.get('x-forwarded-for') ?? 'unknown';
    const { success } = await ratelimit.limit(ip);

    if (!success) {
      return NextResponse.json({ error: 'Too many requests' }, { status: 429 });
    }
  }

  return NextResponse.next();
}
Enter fullscreen mode Exit fullscreen mode

This works well for a small number of genuinely public, high-risk routes. For rate limiting across many different Server Actions with different limits each, handling it per-action, like in the dedicated rate limiting setup, stays more maintainable than trying to encode every rule into one middleware file.


7. What Middleware Should Not Do

Heavy computation. Middleware runs on every matched request, on the Edge Runtime, with tighter execution limits than a normal server function. Anything expensive, a complex database aggregation, heavy processing, belongs in the actual route or Server Component, not middleware.

Direct database queries in most cases. The Edge Runtime does not support every Node.js database driver. Mongoose specifically does not run reliably in middleware. Auth checks in middleware should verify a signed token, not query the database to confirm a session is still valid, that check belongs in the layout or page itself.

Anything that needs to run only once per navigation, not once per request. Middleware fires on every matched request, including prefetches Next.js triggers automatically for links in the viewport. Logic that assumes "the user just navigated here" can fire more often than expected because of this.


Summary

Pattern Use case
Redirect map Dynamic redirects that don't fit a static next.config.ts list
Host-based 301 redirect www/non-www and http/https canonicalization
Geolocation header forwarding Region-specific pricing or content without client flicker
Cookie-based variant assignment A/B testing without a visible flash of the wrong variant
Edge rate limiting on public routes Blocking abuse before it reaches a route handler
Signed token check, not a DB query Fast auth gating without exceeding Edge Runtime limits

The mental model that keeps middleware useful instead of a dumping ground for logic: it is a fast, lightweight gate that runs on every request, not a place to put anything that needs real computation or a database. Detect, redirect, forward a header, block. Anything heavier belongs one layer further in.

I use several of these, canonical redirects, geolocation-based content, together on client sites where they genuinely matter.

Get the templates: https://pixelanas.gumroad.com

Do you use middleware for much beyond auth checks? Drop it below ๐Ÿ‘‡


Anas, full-stack Next.js developer building SaaS products and premium templates. X: @ASheikh69751

Top comments (0)