Bird
Raised Fist0
NextJSframework~15 mins

Middleware.ts file convention in NextJS - Deep Dive

Choose your learning style10 modes available

Start learning this pattern below

Jump into concepts and practice - no test required

or
Recommended
Test this pattern10 questions across easy, medium, and hard to know if this pattern is strong
Overview - Middleware.ts file convention
What is it?
Middleware.ts is a special file in Next.js projects that runs code before a request reaches your pages or API routes. It lets you control requests globally, like checking if a user is logged in or redirecting them. This file uses TypeScript for type safety and better code quality. Middleware runs on the edge, meaning it works very fast and close to the user.
Why it matters
Without middleware, you would have to repeat the same checks or logic in every page or API route, which is slow and error-prone. Middleware.ts centralizes this logic, making your app faster and easier to maintain. It also improves user experience by handling things like redirects or authentication early, before loading full pages.
Where it fits
Before learning middleware.ts, you should understand basic Next.js routing and TypeScript basics. After mastering middleware.ts, you can explore advanced edge functions, server components, and security patterns in Next.js.
Mental Model
Core Idea
Middleware.ts is a gatekeeper that runs code on every request before your app responds, letting you control or modify requests globally.
Think of it like...
Middleware.ts is like a security guard at the entrance of a building who checks everyone before they enter, deciding if they can go in, need to be redirected, or stopped.
┌───────────────┐
│ Incoming      │
│ Request       │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Middleware.ts │  <-- runs first, checks/modifies request
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Page or API   │  <-- runs after middleware if allowed
│ Route Handler │
└───────────────┘
Build-Up - 7 Steps
1
FoundationWhat is Middleware.ts in Next.js
🤔
Concept: Middleware.ts is a file that runs code on every request before your app responds.
In Next.js, placing a file named middleware.ts at the root or in the app directory lets you run code on every incoming request. This code can inspect or change the request or response, like redirecting users or adding headers.
Result
Your app can handle requests globally, without repeating code in every page or API route.
Understanding middleware.ts as a global request handler helps you see how to centralize common logic and improve app performance.
2
FoundationBasic Middleware.ts Structure and Export
🤔
Concept: Middleware.ts exports a function named middleware that receives a request and returns a response or next action.
A minimal middleware.ts looks like this: import { NextResponse } from 'next/server'; import type { NextRequest } from 'next/server'; export function middleware(request: NextRequest) { // Your logic here return NextResponse.next(); } This function runs on every request and must return a NextResponse to continue or redirect.
Result
Middleware runs and lets the request continue to the page or API route.
Knowing the required function signature and return type is key to writing valid middleware that Next.js accepts.
3
IntermediateUsing Middleware.ts for Redirects
🤔Before reading on: do you think middleware can stop a request and send a redirect? Commit to yes or no.
Concept: Middleware.ts can redirect requests to other URLs before reaching the page or API route.
You can check conditions in middleware and return a redirect response: export function middleware(request: NextRequest) { if (!request.cookies.get('user-token')) { return NextResponse.redirect(new URL('/login', request.url)); } return NextResponse.next(); } This sends users without a token to the login page.
Result
Users without the token are redirected before loading protected pages.
Understanding middleware as a gatekeeper that can redirect users early improves security and user flow.
4
IntermediateConfiguring Middleware Matching Patterns
🤔Before reading on: do you think middleware runs on every URL by default or only on specified paths? Commit to your answer.
Concept: You can control which requests middleware runs on by specifying matcher patterns in middleware.ts.
Add a config export to limit middleware to certain paths: export const config = { matcher: ['/dashboard/:path*', '/profile'], }; This runs middleware only on URLs starting with /dashboard or exactly /profile, skipping others.
Result
Middleware runs only on specified routes, improving performance and focus.
Knowing how to limit middleware scope prevents unnecessary processing and keeps your app efficient.
5
IntermediateAccessing Request Data in Middleware.ts
🤔
Concept: Middleware.ts can read request details like headers, cookies, and URL to make decisions.
The NextRequest object provides methods: - request.nextUrl: URL object of the request - request.cookies: access cookies - request.headers: read headers Example: const userAgent = request.headers.get('user-agent'); const token = request.cookies.get('auth-token');
Result
Middleware can customize behavior based on request info, like device type or login status.
Understanding request data access lets you build smarter middleware that adapts to user context.
6
AdvancedMiddleware.ts Runs on the Edge Runtime
🤔Before reading on: do you think middleware runs on the same server as your Next.js app or on a special edge environment? Commit to your answer.
Concept: Middleware.ts runs on the edge runtime, a lightweight environment close to users for fast response.
Next.js middleware runs on the V8 JavaScript engine at the edge, not in Node.js. This means: - Limited APIs (no filesystem access) - Fast cold starts - Runs globally near users You must write middleware code compatible with this environment.
Result
Middleware executes quickly and globally, improving app speed and scalability.
Knowing middleware runs on the edge explains why some Node.js features are unavailable and why performance is better.
7
ExpertAdvanced Middleware.ts Patterns and Pitfalls
🤔Before reading on: do you think stacking multiple middleware files in nested folders runs them all or only the closest one? Commit to your answer.
Concept: Middleware.ts supports advanced patterns like chaining, conditional logic, and careful state handling, but has pitfalls like cold start delays and limited APIs.
You can place middleware.ts in nested folders to scope middleware, but only one middleware runs per request path. Middleware must be stateless and fast to avoid delays. Avoid heavy computations or blocking calls. Use NextResponse.rewrite to modify URLs without redirecting. Example advanced use: if (request.nextUrl.pathname.startsWith('/api')) { // special API logic } return NextResponse.next();
Result
Middleware can handle complex routing and logic but requires careful design to avoid performance issues.
Understanding middleware limitations and patterns helps build robust, scalable apps and avoid common production bugs.
Under the Hood
When a request arrives, Next.js intercepts it and runs the middleware.ts function on the edge runtime before any page or API code. Middleware receives a NextRequest object representing the request and returns a NextResponse to continue, redirect, or rewrite. This runs on V8 isolates close to the user, minimizing latency. Middleware cannot access Node.js APIs like filesystem or database directly. Instead, it uses web-standard APIs and Next.js helpers.
Why designed this way?
Middleware was designed to run on the edge to improve performance and scalability by handling requests globally and early. Running on V8 isolates avoids cold start delays of full Node.js servers and reduces server load. The limited API surface enforces fast, stateless code, preventing slowdowns. This design balances power and speed for modern web apps.
┌───────────────┐
│ User Request  │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Edge Runtime  │  <-- runs middleware.ts function
│ (V8 Isolate)  │
└──────┬────────┘
       │
       ▼
┌───────────────┐
│ Next.js Server│  <-- runs page or API handler
└───────────────┘
Myth Busters - 4 Common Misconceptions
Quick: Does middleware.ts run on every single request by default? Commit to yes or no.
Common Belief:Middleware.ts runs on every request to the app automatically.
Tap to reveal reality
Reality:Middleware.ts runs on every request only if no matcher config limits it. You can specify paths to run middleware selectively.
Why it matters:Assuming middleware runs everywhere can cause unexpected slowdowns or logic errors if you forget to limit its scope.
Quick: Can middleware.ts access Node.js APIs like filesystem? Commit to yes or no.
Common Belief:Middleware.ts can use all Node.js features since it runs in the Next.js server environment.
Tap to reveal reality
Reality:Middleware.ts runs on the edge runtime, which does not support Node.js APIs like filesystem or database connections.
Why it matters:Trying to use unsupported APIs causes runtime errors and breaks middleware functionality.
Quick: If you place multiple middleware.ts files in nested folders, do they all run for a request? Commit to yes or no.
Common Belief:All middleware.ts files in the folder path run in order for each request.
Tap to reveal reality
Reality:Only one middleware.ts runs per request path, the closest matching one. They do not stack or chain automatically.
Why it matters:Misunderstanding this leads to missing middleware logic or duplicated code.
Quick: Does returning NextResponse.next() in middleware always mean the request is unmodified? Commit to yes or no.
Common Belief:NextResponse.next() just passes the request unchanged to the next handler.
Tap to reveal reality
Reality:You can modify the request URL or headers before returning NextResponse.next(), so the request can be changed even when continuing.
Why it matters:Assuming NextResponse.next() means no changes can cause confusion about middleware effects.
Expert Zone
1
Middleware runs on the edge with strict limits, so you must write pure, stateless functions without side effects or heavy computations.
2
Middleware can rewrite URLs internally without redirecting users, enabling seamless routing changes invisible to the browser.
3
Middleware ordering depends on folder structure and matcher specificity, which can cause subtle bugs if misunderstood.
When NOT to use
Middleware is not suitable for heavy data processing, database access, or long-running tasks. Use API routes or server functions for those. Also avoid middleware for client-only logic or UI rendering.
Production Patterns
In production, middleware is used for authentication checks, A/B testing routing, geo-based redirects, and adding security headers. Teams often combine middleware with edge caching for performance.
Connections
HTTP Interceptors
Middleware.ts acts like an HTTP interceptor that inspects and modifies requests before they reach the main handler.
Understanding HTTP interceptors in other frameworks helps grasp middleware's role as a global request filter.
Operating System Kernel Hooks
Middleware.ts is similar to kernel hooks that run code on system calls before the OS processes them.
Knowing kernel hooks shows how middleware can control system behavior early and globally.
Airport Security Checkpoints
Middleware.ts functions like airport security checking passengers before boarding, deciding who can proceed or must be redirected.
This connection highlights middleware's role in security and flow control in web apps.
Common Pitfalls
#1Running middleware on every route without limiting scope.
Wrong approach:export const config = {}; // no matcher specified, middleware runs everywhere
Correct approach:export const config = { matcher: ['/protected/:path*'], };
Root cause:Not understanding the matcher config causes middleware to run unnecessarily, hurting performance.
#2Using Node.js APIs like fs in middleware.ts.
Wrong approach:import fs from 'fs'; export function middleware() { const data = fs.readFileSync('file.txt'); return NextResponse.next(); }
Correct approach:export function middleware() { // Use only web APIs and Next.js helpers return NextResponse.next(); }
Root cause:Confusing edge runtime environment with Node.js server environment.
#3Expecting multiple middleware.ts files in nested folders to run in sequence.
Wrong approach:Having middleware.ts in / and /dashboard expecting both to run on /dashboard requests.
Correct approach:Place middleware.ts only in the folder matching the routes you want to handle, knowing only one runs per request.
Root cause:Misunderstanding middleware scoping and execution order.
Key Takeaways
Middleware.ts in Next.js runs code on every matching request before your app responds, acting as a global gatekeeper.
It runs on the edge runtime, which is fast but limits available APIs to web standards only.
You can control which requests middleware runs on using matcher patterns to improve performance.
Middleware can redirect, rewrite, or modify requests early, improving security and user experience.
Understanding middleware's environment and limitations is key to writing effective, bug-free code.

Practice

(1/5)
1. What is the primary purpose of the middleware.ts file in a Next.js project?
easy
A. To run code before requests reach pages or API routes
B. To define React components for UI rendering
C. To store global CSS styles
D. To configure database connections

Solution

  1. Step 1: Understand middleware role

    Middleware runs before the request reaches pages or APIs, allowing pre-processing.
  2. Step 2: Compare with other file roles

    React components handle UI, CSS files style, and database configs are separate; middleware is for request handling.
  3. Final Answer:

    To run code before requests reach pages or API routes -> Option A
  4. Quick Check:

    Middleware = pre-request code [OK]
Hint: Middleware runs before pages or APIs handle requests [OK]
Common Mistakes:
  • Confusing middleware with UI components
  • Thinking middleware manages styles or database
  • Assuming middleware runs after page rendering
2. Which of the following is the correct way to export a middleware function in middleware.ts?
easy
A. export default function middleware(req) { return NextResponse.next(); }
B. export function middleware(req) { NextResponse.next(); }
C. export const middleware = (req) => NextResponse.next();
D. module.exports = function middleware(req) { return NextResponse.next(); }

Solution

  1. Step 1: Identify modern export syntax

    Next.js middleware uses named export with const arrow function for clarity and modern style.
  2. Step 2: Check syntax validity

    export const middleware = (req) => NextResponse.next(); uses export const middleware = (req) => NextResponse.next(); which is valid and recommended.
  3. Final Answer:

    export const middleware = (req) => NextResponse.next(); -> Option C
  4. Quick Check:

    Use named const export for middleware [OK]
Hint: Use named const arrow function export for middleware [OK]
Common Mistakes:
  • Using CommonJS module.exports instead of ES module export
  • Using default export instead of named export
  • Declaring middleware as a regular function without export
3. Given this middleware.ts snippet, what will happen when a request to /dashboard is made?
import { NextResponse } from 'next/server';

export const config = { matcher: ['/dashboard'] };

export const middleware = (req) => {
  if (!req.cookies.get('token')) {
    return NextResponse.redirect(new URL('/login', req.url));
  }
  return NextResponse.next();
};
medium
A. The user is redirected to /login if no token cookie is present
B. The request proceeds to /dashboard regardless of cookies
C. The middleware throws a runtime error due to missing cookie method
D. The middleware blocks all requests to /dashboard

Solution

  1. Step 1: Analyze matcher and cookie check

    The middleware runs only on /dashboard and checks if 'token' cookie exists.
  2. Step 2: Determine behavior based on cookie presence

    If no token cookie, it redirects to /login; otherwise, it allows the request.
  3. Final Answer:

    The user is redirected to /login if no token cookie is present -> Option A
  4. Quick Check:

    Missing token cookie triggers redirect [OK]
Hint: Check cookie presence to decide redirect or continue [OK]
Common Mistakes:
  • Assuming middleware runs on all routes
  • Thinking request proceeds without token cookie
  • Confusing redirect URL construction
4. Identify the error in this middleware.ts code snippet:
import { NextResponse } from 'next/server';

export const middleware = (req) => {
  if (req.cookies.token === undefined) {
    return NextResponse.redirect('/login');
  }
  return NextResponse.next();
};
medium
A. Missing import of NextRequest from 'next/server'
B. Accessing cookies directly as an object instead of using req.cookies.get()
C. Using arrow function instead of regular function
D. Not exporting config.matcher for route matching

Solution

  1. Step 1: Check cookie access method

    In Next.js middleware, cookies are accessed via req.cookies.get('name'), not as object properties.
  2. Step 2: Identify error cause

    Using req.cookies.token will be undefined or cause error; correct is req.cookies.get('token').
  3. Final Answer:

    Accessing cookies directly as an object instead of using req.cookies.get() -> Option B
  4. Quick Check:

    Use req.cookies.get('token') to access cookies [OK]
Hint: Use req.cookies.get('name') to read cookies in middleware [OK]
Common Mistakes:
  • Accessing cookies as properties instead of using get() method
  • Forgetting to import NextResponse
  • Assuming arrow functions are invalid in middleware
5. You want your middleware.ts to run only on API routes starting with /api/private and redirect users without a valid auth cookie to /api/auth/unauthorized. Which config and middleware code correctly implements this?
hard
A. export const config = { matcher: ['/api/private/:path*'] }; export const middleware = (req) => { if (!req.cookies.get('auth')) { return NextResponse.rewrite(new URL('/api/auth/unauthorized', req.url)); } return NextResponse.next(); };
B. export const config = { matcher: ['/api/private/*'] }; export const middleware = (req) => { if (req.cookies.auth === undefined) { return NextResponse.redirect('/api/auth/unauthorized'); } return NextResponse.next(); };
C. export const config = { matcher: ['/api/private'] }; export default function middleware(req) { if (!req.cookies.get('auth')) { return NextResponse.redirect('/api/auth/unauthorized'); } return NextResponse.next(); };
D. export const config = { matcher: ['/api/private/:path*'] }; export const middleware = (req) => { if (!req.cookies.get('auth')) { return NextResponse.redirect(new URL('/api/auth/unauthorized', req.url)); } return NextResponse.next(); };

Solution

  1. Step 1: Verify matcher pattern for API routes

    The pattern /api/private/:path* correctly matches all routes under /api/private.
  2. Step 2: Check cookie access and redirect method

    Using req.cookies.get('auth') is correct. Redirect uses NextResponse.redirect(new URL(...)) with full URL.
  3. Step 3: Compare options for correctness

    export const config = { matcher: ['/api/private/:path*'] }; export const middleware = (req) => { if (!req.cookies.get('auth')) { return NextResponse.redirect(new URL('/api/auth/unauthorized', req.url)); } return NextResponse.next(); }; uses correct matcher, cookie access, and redirect syntax. Others have errors like wrong cookie access, missing URL object, or rewrite instead of redirect.
  4. Final Answer:

    export const config = { matcher: ['/api/private/:path*'] }; export const middleware = (req) => { if (!req.cookies.get('auth')) { return NextResponse.redirect(new URL('/api/auth/unauthorized', req.url)); } return NextResponse.next(); }; -> Option D
  5. Quick Check:

    Use matcher with :path*, get() for cookies, and redirect with URL [OK]
Hint: Use :path* matcher and req.cookies.get() with NextResponse.redirect(URL) [OK]
Common Mistakes:
  • Using wildcard * instead of :path* in matcher
  • Accessing cookies as properties instead of get()
  • Using rewrite instead of redirect for unauthorized access
  • Not wrapping redirect URL in new URL()