
Why Next.js Middleware Might Be Unavailable with Pages Router

This week I've gone through the slightly arduous process of upgrading all the dependencies on my personal website, so at the time of writing, I'm now running on Next.js 15.5.5, whilst still using Pages Router (simply because I'm not a fan of how over‑opinionated and prescriptive the static metadada object is in App Router conventions).
Despite following every documented pattern, middleware simply never fires for me and never has in this project. No logs, no redirects, no signs of life whatsoever. This has been the case throughout every sub‑version of Next 15 so far.
I'm not alone on this either. Developers across GitHub (1, 2, 3), Stack Overflow, and other forums report the same issue: middleware appears to be entirely ignored in Pages‑based projects using recent Next 15 releases. The documentation still implies that it should work universally. Still, in practice (and my own experience) it seems to be an App‑Router‑only feature now, whether by accident or quiet design.
What Middleware Used to Be
Middleware first arrived as part of Next.js 12, allowing developers to run lightweight logic before a request was processed, essentially in between the front‑end and the server. This is really useful for things like detecting locale, redirecting, or authentication.
When deployed, it executes at the Edge runtime, intercepting requests and returning a NextResponse. Initially, it worked perfectly well with the Pages Router. Projects could drop a middleware.ts or middleware.js file into the root of their project and gain global pre‑route logic. However, as the framework evolved, middleware became increasingly tied to the App Router.
When Next 13 introduced the app/ directory, Vercel began focusing on new features (streaming, route segments, metadata generation) around that architecture rather than the old Pages Router. Middleware still worked for legacy projects, but its documentation and examples increasingly referred to usage in the context of App‑Router.
The Situation in Next 15
By the time we reach Next 15, the story seems to change.
Despite the official file‑conventions page still stating that middleware "runs for every route in your project," many developers ‑ myself included ‑ find that it simply never triggers at all when used alongside the Pages Router. Placing middleware.ts at the project root, or under src/, has no effect. Even the simplest test (a console log or redirect) fails silently.
Community evidence seems to confirm this pattern, although there's no clear statement from the Next.js team acknowledging this break, nor any known fix either.
Interestingly, Next 15.5 introduced "Node.js middleware (stable)" as a headline feature, referring to the ability to run middleware in a Node runtime rather than Edge, perhaps suggesting that the entire middleware execution pipeline has changed under the hood. It is plausible that the Pages Router was left behind in that process.
So, while middleware has not been officially deprecated for the Pages Router, its absence of effect in real deployments suggests it is now effectively non‑functional.
Possible Reasons
Architectural Divergence
It is fair to say that over the past few releases of Next.js, the App Router and Pages Router have diverged significantly. Middleware is tightly coupled to the modern routing layer and the React Server Components model that underpins App Router. It's conceivable that the middleware hook points used by Pages were quietly dropped during internal refactoring.
Build‑Time Omission
Middleware must be statically discovered during build. If Next 15's new compiler paths focus exclusively on App‑Router entry points, the Pages Router may no longer expose the hooks necessary for middleware inclusion.
Runtime Constraints
Edge‑runtime behaviour has shifted repeatedly since its introduction. Middleware now runs in isolated Edge contexts or, optionally as of v15.5, in Node contexts. If the Pages Router's rendering pipeline doesn't initialise those contexts, middleware simply never registers.
None of these explanations are documented, so it's only assumptions based on attempting to align my own experiences with the current situation.
Practical Workarounds
Until middleware support is either restored or formally deprecated for the Pages Router, there are a number of options we as developers can explore where middleware is a critical part of our architecture, either to reinstate the same behaviour as before, or replicate most common patterns through other means. None of these options are perfect, but each has its place depending on your priorities around stability, maintenance, and long‑term support.
Downgrading Next.js
The absolute most direct way to restore working middleware is simply to downgrade Next.js to a version before 15. In Next 14, middleware still functions correctly with the Pages Router.
Doing so is as simple as running yarn add next@14 and then ensuring that your @types/react and @types/node packages are compatible with that release. With your dependencies updated (down‑dated?), you can then clear your .next build cache and you're on your way again.
I should be really clear that even if I did recommend this choice, it will only ever be a temporary fix. Rolling back instantly solves your middleware problems, but in doing so, you also lock your project to an older framework version that will soon stop receiving updates and security patches altogether. New React features, improved bundling, and the stabilised Node.js middleware runtime introduced in 15.5 are all lost.
You might find that downgrading is an acceptable short‑term patch on a small personal project, but it is not a sustainable path for production systems. The better long‑term strategy is to rebuild the relevant logic using configuration, server‑side rendering hooks, or API routes ‑ all approaches that continue to work under Next 15.
Moving to the App Router
It seems likely that if you want to stay on the current release (and probably those that follow it), then you will eventually have to accept and adopt App Router. This is where all new middleware development appears to be occurring, and where Vercel's documentation and examples remain fully valid.
It isn't an immediately straightforward process, but to begin migrating, you would create an app/ directory at your project root and move one or two routes across. You can still keep pages/ in place whilst gradually transitioning. Middleware defined at the project root will start firing for App Router routes immediately, even if Pages‑based routes remain unaffected.
Redirects and Rewrites
If you prefer to remain with the Pages Router, many simple middleware use cases ‑ such as path rewrites or redirects ‑ can be replicated in next.config.js. For example:
export default { async redirects() { return [ { source: '/old-section/:path*', destination: '/new-section/:path*', permanent: true, }, ]; },};Configuration‑level redirects execute before the request reaches your page, which offers you the same routing control that middleware would provide.
Authentication and Access Control
Server‑side logic inside getServerSideProps can replace most authentication checks that would otherwise be handled in middleware:
// pages/dashboard.tsximport type { GetServerSideProps } from 'next';export const getServerSideProps: GetServerSideProps = async ({ req }) => { const session = req.cookies.session; if (!session) { return { redirect: { destination: '/login', permanent: false }, }; } return { props: {} };};Although this is on a page‑level, it executes reliably, works with all Pages Router routes, and avoids dependency on undocumented or middleware behaviour.
Header or Cookie Manipulation
For more complex request pre‑processing, use API routes to handle mutations before the main request is served. They can set or adjust headers, manage cookies, and return modified responses, effectively acting as controlled gateways.
Functional Composition
Middleware was always elegant because it encouraged functional composition: a pipeline of small, testable functions operating on requests and producing responses. That same structure can be recreated in your server‑side logic or API handlers.
By composing request processors as pure functions, we can maintain clarity and testability even without a formal middleware layer. The result behaves differently but remains conceptually faithful to the same pattern.
Wrapping up
In theory, middleware should remain part of Next.js regardless of routing style, but in practice, as of Next 15.5, it appears non‑functional in Pages‑based applications. This isn't officially documented, but it is reliably reproducible, consistent, and widely observed.
Developers who rely on the Pages Router for its simplicity or metadata flexibility, or who simply aren't yet ready to migrate to App Router, may therefore need to rebuild middleware‑style logic using configuration, API routes, or server‑side rendering hooks. Those moving to the App Router will find middleware alive and well, but with it comes a more prescriptive and opinionated structure.
Key Takeaways
- Middleware in Next 15 Pages Router projects appears non‑functional despite remaining in documentation.
- The issue persists across multiple sub‑versions, including 15.5.5 (the latest at the time of writing).
- Likely causes include internal architectural divergence and build‑time omission rather than deliberate deprecation.
- Equivalent behaviour can be recreated using redirects, API routes, or getServerSideProps.
- Middleware remains reliable in the App Router, but adopting it means embracing that newer, more prescriptive model.
Categories:
Related Articles

Optimising Next.js Performance with Incremental Static Regeneration (ISR). 
Memoization in JavaScript: Optimising Function Calls. Memoization in JavaScript: Optimising Function Calls

ParseInt in JavaScript: The Significance of Radix. parseIntin JavaScript: The Significance of Radix
Disabling Text Selection Highlighting with CSS. Disabling Text Selection Highlighting with CSS

Some of the Most‑Misunderstood Properties in CSS. Some of the Most‑Misunderstood Properties in CSS

CSS visibility: Hiding Elements Without Affecting Layout. CSS
visibility: Hiding Elements Without Affecting Layout
What Makes a Great JavaScript Developer? What Makes a Great JavaScript Developer?

Caching Strategies for Data Fetching in Next.js. Caching Strategies for Data Fetching in Next.js

Break Out of CSS Nesting with Sass. Break Out of CSS Nesting with Sass

Unravelling JavaScript: Commonly Misunderstood Methods and Features. Unravelling JavaScript: Commonly Misunderstood Methods and Features

If Not Internet Explorer Conditional HTML. If Not Internet Explorer Conditional HTML

String to Integer (atoi): Decoding Strings in JavaScript. String to Integer (atoi): Decoding Strings in JavaScript