How to Fix: In Next.js version 15.1.0, the middleware is not working. I placed the middleware file under the src directory, at the same level as app.
Next.js 15.1.0 middleware not running from the src directory usually comes down to one thing: the framework only detects middleware in very specific filesystem locations, and if the file name, placement, or matcher setup is even slightly off, the middleware is silently skipped. In this case, placing middleware.ts under src at the same level as app is valid, but the project structure and runtime expectations must still match what Next.js actually scans.
Table of Contents
Understanding the Root Cause
In Next.js, middleware is not discovered like a normal route file. It is a special build-time entrypoint. That means Next.js looks for it only in supported root locations such as:
middleware.tsat the project rootsrc/middleware.tswhen using asrcdirectory layout
If the file is nested deeper, incorrectly named, or the app is mixing assumptions between root-level and src-level structure, the middleware will not execute.
For the reported issue, the important technical detail is that middleware placement alone is not enough. Next.js 15 still expects:
- a correctly named file:
middleware.tsormiddleware.js - a valid exported
middlewarefunction - an optional but correct
config.matcher - no unsupported Node.js-only APIs inside the middleware if it is running in the Edge Runtime
So even when the file is under src beside app, middleware may appear “broken” if:
- the matcher does not include the route being tested
- the request is for a static asset, which middleware may skip depending on matcher rules
- the middleware imports incompatible server-side code
- the app expects middleware behavior on routes that are actually bypassed
Another common source of confusion is that App Router and middleware are separate systems. The fact that src/app is working does not automatically prove that src/middleware.ts is being compiled or matched the way you expect.
Step-by-Step Solution
Use this checklist to make the middleware work reliably in Next.js 15.1.0.
1. Confirm the exact file location
If your project uses a src layout, the file should be here:
src/middleware.ts
Your structure should look like this:
project-root/
├── src/
│ ├── app/
│ └── middleware.ts
├── package.json
├── next.config.ts
└── tsconfig.json
Do not place middleware inside src/app, route groups, or nested folders like this:
src/app/middleware.ts ❌
src/app/(site)/middleware.ts ❌
src/lib/middleware.ts ❌
2. Use a minimal working middleware first
Strip the file down and verify execution before adding auth, cookies, redirects, or custom logic.
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
console.log('Middleware hit:', request.nextUrl.pathname)
return NextResponse.next()
}
export const config = {
matcher: '/:path*',
}
This does two important things:
- ensures the middleware exports the correct function
- matches all application paths with
/:path*
3. Test a real page route, not just assets
Open an actual application route such as the home page or another page rendered by App Router. Middleware matching can behave differently for:
/_next/static/...- images
- favicon requests
- other static files
If you only check the browser network casually, you may think middleware is not running when in reality your matcher simply excludes the request you are looking at.
4. Exclude static files correctly if needed
Once the basic version works, replace the broad matcher with a safer production-ready matcher:
export const config = {
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico).*)',
],
}
This pattern tells Next.js middleware to run for normal pages while skipping internal framework assets and common static resources.
5. Restart the dev server after moving the file
Because middleware is a special build entry, moving it between locations is not always picked up cleanly by hot reload. Stop and restart development after relocation:
pnpm dev
If it was previously in the wrong place, a full restart is often required before Next.js recognizes it.
6. Avoid unsupported imports in middleware
Middleware runs in the Edge Runtime. That means some Node.js APIs and server-only libraries will break it or prevent correct execution.
Avoid imports like these inside middleware:
import fs from 'fs' // ❌
import path from 'path' // ❌
import prisma from '@/lib/db' // often ❌ depending on runtime usage
Keep middleware lightweight and limited to tasks like:
- redirects
- rewrites
- reading headers
- reading cookies
- basic auth checks
7. Use this final recommended example
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const { pathname } = request.nextUrl
if (pathname.startsWith('/login')) {
return NextResponse.next()
}
return NextResponse.next()
}
export const config = {
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}
8. If the project still fails, try moving middleware temporarily to project root
Although src/middleware.ts is supported, temporarily placing the same file at the project root helps isolate whether the problem is project structure related.
project-root/
├── middleware.ts
├── src/
│ └── app/
If root placement works and src/middleware.ts does not, inspect the repository for unusual configuration, monorepo boundaries, custom build tooling, or an unexpected app root.
Common Edge Cases
1. The matcher never includes the route you are testing
If your route is /dashboard but your matcher only targets /admin/:path*, middleware will never run there.
2. The file is named incorrectly
These names will fail:
middlewares.ts ❌
middleware.js.ts ❌
src/app/middleware.ts ❌
Only the special file name works.
3. You are testing API behavior but only matching page routes
If your middleware is expected to affect /api routes, your matcher must include them explicitly.
4. Console logs are misleading in development
In some development scenarios, terminal logs may not appear the way you expect for every request. Test with visible behavior too, such as a redirect:
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname === '/') {
return NextResponse.redirect(new URL('/test', request.url))
}
return NextResponse.next()
}
5. Middleware logic throws at runtime
If the function crashes due to an invalid import or unsupported API, it may look like middleware is not working when it is actually failing during execution.
6. Monorepo or custom project root confusion
If the app lives in a workspace subfolder, Next.js only scans the actual app root. A middleware file placed in the wrong workspace level will be ignored.
FAQ
Can middleware.ts be inside src in Next.js 15.1.0?
Yes. src/middleware.ts is supported when your app uses the src directory convention. It must be directly under src, not inside src/app.
Why does my middleware work at the project root but not under src?
That usually indicates a project structure, workspace root, or configuration mismatch. The app may not actually be using the expected src root, or the file may not be in the exact scanned location.
Do I need a matcher for middleware to run?
Not always, but adding a matcher is the best way to verify and control which routes are processed. For debugging, /:path* is the simplest broad matcher.
For the repository linked in the issue, the practical fix is to verify that the file is exactly src/middleware.ts, reduce it to a minimal middleware function, add a broad matcher, restart the dev server, and then gradually reintroduce custom logic. In most cases, the bug is not that Next.js 15.1.0 cannot use middleware under src; it is that the middleware entrypoint is either not being matched, not being recognized due to placement details, or failing because of runtime constraints.
For official behavior and placement rules, review the Next.js middleware documentation and compare your repository structure carefully against the supported layout.