Secure Next.js middleware.ts template

Copy-paste Next.js middleware that enforces auth on protected routes, blocks CVE-2025-29927, adds security headers, and handles rate limiting.

How to use

Drop into /middleware.ts. Adjust PROTECTED_PATHS and Ratelimit config to your needs.

Template (tsx)

copy-paste, replace {{PLACEHOLDERS}}
import { NextResponse, type NextRequest } from "next/server";
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

// Routes that require authentication
const PROTECTED_PATHS = ["/app", "/api/private"];

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(60, "1 m"),
});

export async function middleware(req: NextRequest) {
  // Block CVE-2025-29927 — reject any incoming x-middleware-subrequest header
  if (req.headers.has("x-middleware-subrequest")) {
    return new NextResponse("rejected", { status: 400 });
  }

  const { pathname } = req.nextUrl;

  // Rate limit all API routes
  if (pathname.startsWith("/api/")) {
    const ip = req.headers.get("x-forwarded-for")?.split(",")[0] ?? "anon";
    const { success } = await ratelimit.limit(ip);
    if (!success) return new NextResponse("slow down", { status: 429 });
  }

  // Enforce auth on protected paths
  if (PROTECTED_PATHS.some((p) => pathname.startsWith(p))) {
    const session = req.cookies.get("session");
    if (!session) {
      const url = new URL("/signin", req.url);
      url.searchParams.set("redirect", pathname);
      return NextResponse.redirect(url);
    }
  }

  return NextResponse.next();
}

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