For session management in a Next.js project using Auth.js or Clerk, middleware is almost essential. However, until now, I have simply copied and pasted the code provided in the documentation into my middleware.ts
file without giving much thought to what middleware actually is, why it is used, or what its benefits are.
So today, I am taking the time to deep dive into middleware and explore its purpose and advantages!
Let's start with the official Next.js documentation:
Middleware allows you to run code before a request is completed. Then, based on the incoming request, you can modify the response by rewriting, redirecting, modifying the request or response headers, or responding directly.
In simple terms, middleware intercepts every request, allowing us to add various logic or directly manipulate the request/response.
A page request occurs when:
/dashboard
/dashboard
using <Link>
/dashboard
Requests also occur when fetching data, such as:
app/api/*
or pages/api/*
)Some people mistakenly believe that middleware runs when a component re-renders. However, middleware is NOT triggered by component re-renders.
According to the official Next.js documentation:
Middleware execution is NOT directly triggered by a component re-render. Middleware runs only on incoming HTTP requests from the browser or API calls.
In short, middleware does NOT run when a component re-renders on the client side.
Integrating Middleware into your Next.js application can significantly improve performance, security, and user experience. Below are some common use cases:
To apply middleware correctly, you must follow two key conventions:
1. The middleware.ts
file must be in the root directory of your project.
2. The filename must be exactly middleware.ts
or middleware.js
.
If your middleware is not working, check these two points first!
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
return NextResponse.redirect(new URL('/home', request.url));
}
In the example above:
request
parameter of type NextRequest
.NextRequest
The NextRequest
object extends the Web API Request and provides additional convenience methods.
NextRequest
// request.ts
export declare class NextRequest extends Request {
[INTERNALS]: {
cookies: RequestCookies;
url: string;
nextUrl: NextURL;
};
constructor(input: URL | RequestInfo, init?: RequestInit);
get cookies(): RequestCookies;
get nextUrl(): NextURL;
get url(): string;
}
NextRequest is a subclass of Request. It inherits all the standard request properties (headers, body).
export declare class NextRequest extends Request {
// extends means to inherit all the properties.
But it stores internal metadata related to the Next.js-specific features, such as
[INTERNALS]: { //Internal is unique key for object properties
cookies: RequestCookies;
url: string;
nextUrl: NextURL;
};
In the next post, we will explore case studies on how middleware is used in each scenario.