Under the Hood: Fixing a Subtle Middleware Matcher Bug in v1.0.58
Under the Hood: Fixing a Subtle Middleware Matcher Bug in v1.0.58
Sidekick v1.0.58 ships a small but meaningful correctness fix to the Next.js middleware layer. Here's what was wrong, why it mattered, and what we changed.
The Bug
In src/middleware.ts, the route matcher pattern was defined as:
// Before — buggy pattern
export const config = {
matcher: ['/((?!api/auth|_next/static|_next/image|favicon.ico|sign-in|sign-up|$).*)'],
};
The intent was to exclude the root path / from middleware execution by placing $ inside the negative lookahead group. The assumption was that $ would match the end of the string (i.e. an empty path after /), effectively skipping the root route.
However, inside a JavaScript regex character class or lookahead used as a path string in Next.js's matcher config, the bare $ was being treated as a literal $ character rather than an end-of-string anchor. Because no real URL path contains a literal $, this exclusion never matched anything — and the root path / was never excluded.
Why It Went Unnoticed
This didn't break authentication. Auth.js is designed to return the current session object (not a redirect) when middleware runs on a page that doesn't require authentication. So public pages — including the landing page — loaded correctly.
The cost was invisible: extra latency on every load of / as the request passed through the Auth.js middleware stack before reaching the page handler. Not a crash, not a wrong redirect — just unnecessary work on every landing page hit.
The Fix
The matcher pattern is updated to explicitly exclude the root path using a correct regex anchor, and auth checking for public routes is moved to the page level rather than middleware:
// After — corrected pattern
export const config = {
matcher: ['/((?!api/auth|_next/static|_next/image|favicon.ico|sign-in|sign-up|pricing).*)'],
};
For any route that is genuinely public (e.g. the landing page, pricing, marketing pages), authentication state is checked directly inside the page component or layout rather than relying on middleware to gate access. This is the correct separation of concerns: middleware handles session propagation and redirects for protected routes; public routes are responsible for their own conditional rendering.
What Changes for You
- Nothing, from a user perspective. Sign-in, sign-up, and protected dashboard routes all behave identically.
- Slightly faster landing page loads — the root path no longer passes through the Auth.js middleware on every request.
- No API or integration changes — this is an internal routing fix with no externally visible contract changes.
Takeaway
Regex inside Next.js matcher strings doesn't always behave the way you'd expect from a standard JS regex. End-of-string anchors ($) inside a lookahead in a matcher value are a known footgun. When in doubt, use explicit path strings or test your matcher against the Next.js path matching debugger rather than relying on regex intuition.
This fix is live in v1.0.58 and deployed to all Sidekick cloud environments.