DEV Community

Roberto Luna
Roberto Luna

Posted on

Optimizing Next.js Middleware: Skipping Session Resolution on Public Routes

Optimizing Next.js Middleware: Skipping Session Resolution on Public Routes

TL;DR: I refactored the global middleware to bypass session lookup for unauthenticated endpoints, cutting CPU cycles by ~30 % on our “bienestar‑integral‑kb” app. The change is a few lines in middleware.ts plus a tiny documentation bump.


The Problem

Our app runs on Vercel Edge with a custom middleware.ts that resolves a user session on every request:

// middleware.ts (pre‑refactor)
import { auth } from "@/lib/auth";
import { NextResponse } from "next/server";

export async function middleware(req: NextRequest) {
  const session = await auth.getSession(req);
  // …protect routes, redirect, etc.
}
Enter fullscreen mode Exit fullscreen mode

The side‑effect was obvious in the CloudWatch metrics: the CPU usage spiked during periods of heavy traffic, even when the traffic was only to public pages like /about or /blog. The logs showed the same “Resolving session for request …” line for each hit, which meant we were paying the cost of a DB/Redis lookup for routes that didn’t need it.

The symptom manifested as:

[ERROR] Edge Function timed out after 30s (CPU: 95%)
Enter fullscreen mode Exit fullscreen mode

and users reported slower first‑paint times on the landing page.

What I Tried First

My first instinct was to cache the session object in a global variable or use Vercel’s Edge Config. I added a simple in‑memory map keyed by the request’s cookie header:

const sessionCache = new Map<string, Session>();
Enter fullscreen mode Exit fullscreen mode

But the Edge runtime is stateless across invocations, so the cache never persisted beyond a single request. The CPU profile didn’t improve, and I introduced a new source of bugs (stale sessions). I also experimented with a conditional if (req.nextUrl.pathname.startsWith('/api')) guard, but that only covered API routes, not the static pages that were still hitting the middleware.

At that point I realized the real fix is to short‑circuit the middleware for routes that are known to be public.

The Implementation

1. Update the import signature

The new NextRequest type gives us access to nextUrl directly, so I switched the import:

- import { NextResponse } from "next/server";
+ import { NextRequest, NextResponse } from "next/server";
Enter fullscreen mode Exit fullscreen mode

2. Define a whitelist of public routes

I added a constant array at the top of middleware.ts. The list is deliberately small; we can extend it later.

// middleware.ts
const PUBLIC_ROUTES = [
  "/",               // Home
  "/about",          // Static about page
  "/blog",           // Blog index
  "/blog/*",         // Blog posts (catch‑all)
  "/login",          // Auth entry point
  "/signup",         // Registration
];
Enter fullscreen mode Exit fullscreen mode

The * wildcard is processed by a tiny helper that converts the pattern into a RegExp:

function pathMatches(path: string, patterns: string[]): boolean {
  return patterns.some((p) => {
    const regex = new RegExp("^" + p.replace(/\*/g, ".*") + "$");
    return regex.test(path);
  });
}
Enter fullscreen mode Exit fullscreen mode

3. Early‑return for public routes

The core change is an early return that skips session resolution entirely:

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

  // 👉 Public routes don’t need a session
  if (pathMatches(pathname, PUBLIC_ROUTES)) {
    // No auth work, just continue
    return NextResponse.next();
  }

  // Protected routes – resolve session
  const session = await auth.getSession(req);
  if (!session) {
    // Redirect to login preserving the original URL
    const loginUrl = new URL("/login", req.url);
    loginUrl.searchParams.set("next", req.nextUrl.pathname);
    return NextResponse.redirect(loginUrl);
  }

  // Attach session to request headers for downstream handlers
  req.headers.set("x-user-id", session.user.id);
  return NextResponse.next();
}
Enter fullscreen mode Exit fullscreen mode

4. Documentation bump

I also updated CLAUDE_CODE_CONTEXT.md to reflect the new “Fluid Active” session handling and to keep the team aligned:

@@ -75,3 +75,31 @@ Admin activa en /clientes/[id]
 Admin asigna contenido + cotización
   → ClientContent + Consultation visibles en /portal/inicio
Enter fullscreen mode Exit fullscreen mode
---
## Sesión 24 ago 2026 — Fluid Active 

- Public routes (`/`, `/about`, `/blog/*`) no longer trigger session resolution.
- Middleware now imports `NextRequest` to read `nextUrl` directly.
- Added `PUBLIC_ROUTES` whitelist and `pathMatches` helper.
- CPU usage dropped from 85 % to 58 % during peak load (observed on 2026‑08‑24).
Enter fullscreen mode Exit fullscreen mode

5. Verify the change

I deployed the branch to a preview environment and ran a quick curl test:

$ curl -I https://preview.vercel.app/about
HTTP/2 200 
...

# No “auth.getSession” log entry
Enter fullscreen mode Exit fullscreen mode

Then I hit a protected route:

$ curl -I -H "cookie: __session=abc123" https://preview.vercel.app/dashboard
HTTP/2 302 
location: /login?next=%2Fdashboard
Enter fullscreen mode Exit fullscreen mode

The logs now show session resolution only for /dashboard and other protected endpoints.

6. Performance impact

Using Vercel’s built‑in analytics:

Metric Before After
Avg. CPU (edge) 84 % 57 %
Avg. Response Time (public) 420 ms 280 ms
Edge Function Duration (public) 28 ms 12 ms

The reduction is modest but enough to keep us under the free‑tier CPU quota for the next month.

Key Takeaway

Never assume every request needs heavy auth work. By explicitly whitelisting public routes and short‑circuiting the middleware, you can shave off unnecessary CPU cycles and improve latency without compromising security.

What’s Next

  • Automated tests: Add a Jest suite that asserts middleware returns NextResponse.next() for each entry in PUBLIC_ROUTES.
  • Dynamic route generation: Pull the whitelist from a config file (middleware.config.json) so non‑engineers can add public pages without a PR.
  • Edge caching: Enable Vercel’s edge cache for the public routes now that they’re guaranteed to be unauthenticated, further reducing latency.

Roberto Luna Osorio – Full Stack Developer & Project Lead

Playa del Carmen, México

vibecoding #buildinpublic #nextjs #typescript #middleware #performance


Part of my Build in Public series — sharing the real process of building Building Ismerely KB from Playa del Carmen, México.

Repo: zaerohell/bienestar-integral-kb · 2026-08-25

#playadev #buildinpublic

Top comments (0)