Your Next.js Middleware Matcher Config Is Probably Skipping Routes You Think It Protects

●1 ●5 ●71
calendar_today ago • schedule3 min read

Middleware feels like the cleanest way to gate an entire section of a Next.js app behind auth, one file, one config, covering everything under a path. That confidence is exactly the problem, because the matcher config that's supposed to define "everything under this path" has a few specific gaps that don't show up until someone hits a nested route that quietly skipped the check entirely.

The Setup That Looks Complete

// middleware.ts
import { NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';

export async function middleware(request: NextRequest) {
  const token = await getToken({ req: request });

  if (!token) {
    return NextResponse.redirect(new URL('/login', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: '/dashboard/:path*',
};

This reads as "protect everything under /dashboard," and for the routes most people test during development, /dashboard, /dashboard/settings, /dashboard/analytics, it works exactly as expected. The gap shows up specifically with route groups and parallel/intercepting route conventions, which don't map onto the URL path the way the folder structure visually suggests they do.

Where It Actually Breaks

app/
├── (dashboard)/
│   ├── dashboard/
│   │   └── page.tsx          → /dashboard
│   └── settings/
│       └── page.tsx          → /settings  (NOT /dashboard/settings!)

Route groups, the parenthesized folders, don't appear in the URL at all. A page at app/(dashboard)/settings/page.tsx is served at /settings, not /dashboard/settings, even though it visually sits inside what looks like your dashboard's folder in the codebase. A matcher: '/dashboard/:path*' pattern never matches /settings, because as far as the URL is concerned, it was never under /dashboard to begin with.

This is exactly the kind of structural confusion that's easy to carry forward from a folder structure that leans on route groups for layout purposes, the same route groups I use throughout the Next.js templates on pixelanas.com, without re-checking that every protected page's actual URL still falls inside the matcher pattern protecting it, because the folder nesting and the URL nesting have quietly diverged.

A Second, Subtler Gap: Matcher Patterns and Encoded Characters

Next.js's own documentation for the matcher config notes that values must be literal strings or simple patterns, matcher patterns don't support arbitrary regex features, and certain edge cases around encoded path segments or unusual characters in dynamic segments can fail to match in ways that aren't obvious from reading the pattern itself. A route like /dashboard/reports/q1%2F2026 (an encoded slash inside a dynamic segment) can behave unpredictably against a pattern that looks like it should obviously cover it.

Why This Doesn't Get Caught

Testing auth-gated routes usually means manually clicking through the main pages of a protected section, the ones directly nested under the obvious path, and confirming the redirect works. Pages that technically live in the same conceptual section of your app but sit outside the matched URL pattern because of a route group, or a page that was added later and happens to resolve to a URL slightly outside what the matcher covers, simply never get manually tested against the auth gate, because nobody thinks to check a URL that doesn't look like it needs checking.

The Fix: Verify Matcher Coverage Against Actual Resolved URLs, Not Folder Structure

Don't trust the matcher pattern based on how your app/ folder looks. Check it against the actual resolved URL of every page you intend to protect:

export const config = {
  matcher: [
    '/dashboard/:path*',
    '/settings/:path*',       // explicitly added once the real URL was checked
    '/billing/:path*',
  ],
};

A more defensive pattern for anything genuinely sensitive is inverting the logic entirely, protect everything by default, and explicitly exclude the few public routes, rather than trying to enumerate every protected path and hoping the list stays complete as the app grows:

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

This pattern protects everything except the explicitly named public routes, which means a newly added page under a route group, or anywhere else, is protected by default rather than silently falling outside coverage until someone notices.

The Rule

A middleware matcher pattern should be verified against the app's actual resolved URLs, not assumed from how the app/ folder is visually organized. Route groups, parallel routes, and intercepting route conventions all affect the URL in ways that don't match the folder nesting, which is exactly where a matcher pattern that looks complete quietly isn't.

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

Go check your own middleware matcher against the real URLs of every page it's supposed to protect, not the folder structure. If any of them use route groups, this is worth double-checking today. Drop what you find in the comments.


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

2 Comments

1 vote
0
🔥 Join developers growing publicly
Share your knowledge, build in public, and grow your developer presence with a global community.

More Posts

TypeScript Complexity Has Finally Reached the Point of Total Absurdity

Karol Modelski - Apr 23

I’m a Senior Dev and I’ve Forgotten How to Think Without a Prompt

Karol Modelski - Mar 19

Your Backup Data Knows More Than You Think. HYCU aiR Is Finally Asking It the Right Questions.

Tom Smithverified - May 14

Comparison: Universal Import vs. Plaid/Yodlee

Pocket Portfolio - Mar 12

MCP Is the USB-C of AI. So Why Are You Plugging Everything In?

Ken W. Algerverified - Jun 10
chevron_left
1.3k Points • 77 Badges
Pakistan • pixelanas.com
47Posts
12Comments
7Connections
Full-stack developer building premium Next.js templates & web apps. Specializing in GSAP animations,... Show more

Related Jobs

View all jobs →

Commenters (This Week)

4 comments
1 comment
1 comment

Contribute meaningful comments to climb the leaderboard and earn badges!