Next.js is a powerful full-stack React framework known for its performance, developer experience, and tight integration with Vercel. However, even robust frameworks like Next.js can harbor subtle but impactful vulnerabilities. One such flaw involves the silent bypass of middleware, potentially exposing sensitive routes that developers believe are protected.

In this article, we’ll explore this vulnerability in detail, including how it arises, ways to reproduce and discover it, and most importantly, how to protect your application from unintentional exposure of protected resources. We’ll include practical coding examples throughout and conclude with a comprehensive checklist to harden your Next.js security posture.

Understanding Next.js Middleware

Middleware in Next.js (introduced in version 12 and improved in version 13+) allows you to run logic before a request is completed. Common use cases include:

  • Authentication/authorization

  • Bot protection

  • Localization

  • Logging and monitoring

Here’s a basic example:

ts
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(req: NextRequest) {
const token = req.cookies.get(‘auth_token’);
if (!token) {
return NextResponse.redirect(new URL(‘/login’, req.url));
}
return NextResponse.next();
}export const config = {
matcher: [‘/dashboard/:path*’, ‘/admin/:path*’], // Only protect these routes
};

At first glance, this appears to secure sensitive areas of your app. However, a flaw in route matching and server rendering behavior can be exploited to silently bypass this middleware.

The Vulnerability: Middleware Bypass via Static Optimization

Scenario:

  • You set up middleware to protect sensitive routes like /dashboard.

  • You accidentally or unknowingly statically generate the /dashboard route at build time using getStaticProps() or export.

Consequence:

  • Static pages are served directly from the edge/CDN and bypass middleware entirely.

  • Any protected route with getStaticProps will not go through your middleware unless explicitly handled.

Example of an unsafe static page:

tsx
// pages/dashboard/index.tsx
export async function getStaticProps() {
return {
props: {
message: 'Static dashboard page',
},
};
}
export default function Dashboard({ message }) {
return <h1>{message}</h1>;
}

Even if /dashboard is listed in your middleware matcher, the page can be accessed directly without authentication if deployed as static HTML.

This is particularly dangerous because:

  • It’s silent – no error or warning is shown.

  • It breaks assumptions – developers think routes are protected, but they’re not.

  • It may leak personal or privileged content if static data contains user-specific information.

How to Reproduce the Flaw

Create a Next.js app:

bash
npx create-next-app@latest next-middleware-flaw
cd next-middleware-flaw

Add middleware to protect /dashboard:

ts
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const isAuthenticated = request.cookies.get(‘token’);
if (!isAuthenticated) {
return NextResponse.redirect(new URL(‘/login’, request.url));
}
return NextResponse.next();
}export const config = {
matcher: [‘/dashboard/:path*’],
};

Add a static page at /dashboard/index.tsx:

tsx
// pages/dashboard/index.tsx
export async function getStaticProps() {
return {
props: {
secret: 'Protected dashboard content',
},
};
}
export default function Dashboard({ secret }) {
return <h1>{secret}</h1>;
}

Start the app (npm run dev) and access /dashboard.

Observe: Even without a token, you can access the dashboard.

If you check npm run build followed by npm start, you’ll see that this route is being served statically.

How To Discover Affected Routes

To identify potential vulnerabilities:

Audit All Routes Using getStaticProps

Use static analysis tools (like grep, eslint, or tsquery) to detect:

bash
grep -r "getStaticProps" ./pages

Manually review each file to ensure none of them expose user-specific or sensitive information.

Check Your Build Output

Run:

bash
npm run build

Then examine the .next output:

bash
cat .next/build-manifest.json | grep '"/dashboard"'

You’ll see whether the page is treated as static or SSR.

Enable Strict Middleware Warnings

If deploying to Vercel, enable middleware warnings in your project dashboard or use logging in your middleware function to confirm execution.

How To Protect Your Application

Here are several strategies to eliminate the risk of this flaw:

Use getServerSideProps for Protected Pages

Convert your page to use server-side rendering:

tsx
// pages/dashboard/index.tsx
export async function getServerSideProps(context) {
const token = context.req.cookies.token;
if (!token) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
return {
props: {
secret: ‘Protected content’,
},
};
}

With getServerSideProps, middleware will always run, and data can be tailored per request.

Guard Routes Using Custom Higher-Order Components (HOCs)

Wrap your page components with an authentication check on the client (though not a replacement for server protection):

tsx
import { useEffect } from 'react';
import { useRouter } from 'next/router';
function withAuth(Component) {
return function Authenticated(props) {
const router = useRouter();
useEffect(() => {
const token = document.cookie.includes(‘token’);
if (!token) router.push(‘/login’);
}, []);
return <Component {…props} />;
};
}

Apply it to pages:

tsx
export default withAuth(Dashboard);

Use Middleware with Edge Functions in Mind

Make sure your middleware is compatible with both static and dynamic rendering. This may involve:

  • Avoiding getStaticProps for protected routes

  • Using route conventions like /public/* and /private/* for easy matching

  • Logging all middleware hits and reviewing logs during QA and production

Disable Static Optimization Explicitly

Force a page to be dynamically rendered by using getServerSideProps even if the data doesn’t change:

tsx
export async function getServerSideProps() {
return {
props: {},
};
}

This is a simple hack to bypass static rendering when in doubt.

Add Automated Static Route Checks in CI/CD

Write a script in your build pipeline to check for static pages under protected routes:

js
const fs = require('fs');
const buildManifest = require('./.next/build-manifest.json');
Object.keys(buildManifest.pages).forEach((route) => {
if (route.startsWith(‘/dashboard’) && !buildManifest.pages[route].includes(‘ssr’)) {
console.warn(`WARNING: Route ${route} is static!`);
process.exit(1); // fail the build
}
});

Best Practices Summary

Practice Description
Avoid getStaticProps on protected routes Prevent bypass via static delivery
Use getServerSideProps for dynamic user-based pages Ensures request context is always evaluated
Use middleware + SSR for defense-in-depth Middleware should redirect unauthenticated users
Audit build output regularly Detect unintended static generation
Write CI checks Prevent regressions and enforce rules
Educate team members Make this flaw a known part of code reviews

Security in modern JavaScript frameworks like Next.js isn’t always as intuitive as it seems. Middleware offers a powerful mechanism to enforce authentication and route access, but it is not foolproof—especially when combined with features like static site generation. This article revealed a subtle yet critical flaw: middleware can be silently bypassed if a protected route is statically generated.

This misalignment can lead to serious security risks, especially if developers unknowingly expose authenticated areas like dashboards, admin panels, or internal documentation. We demonstrated how this vulnerability can be reproduced, how to detect it in your project, and outlined best practices to prevent such silent failures from reaching production.

To secure your app:

  • Avoid static generation for sensitive routes.

  • Use getServerSideProps to maintain dynamic access control.

  • Leverage middleware strategically, but not as your only gatekeeper.

  • Review build outputs and static paths continuously.

  • Automate detection of static exposure in CI/CD pipelines.

Frameworks improve, and Next.js may address this behavior more transparently in the future. Until then, awareness and deliberate architectural decisions are your best defenses.

Always assume nothing is secure by default—explicit is better than implicit, and verification is key.