In Next.js, we can use file-based routing to create routes. Any folder inside the app directory with a page file will act as a route and when a user requests a route, its page file is rendered as a response.
So suppose you have a folder named “users” inside the app directory with a page.tsx file (for TypeScript) having a list of users as a component to be rendered. If a client opens the “/users” route, the list of users is rendered as a response. What if we don’t want to render this list of users to everyone? Here comes the middleware to intercept requests, apply logic, and decide how to proceed next.
In our previous tutorial, we learned how to add layouts in Next.js. In this tutorial, we will understand middleware in detail.
Introduction to Middleware
Middleware is used to intercept incoming requests and perform some actions before requests are completed. The action can be altering the response, redirecting to some route, modifying headers, etc.
For example, in the above case we can use middleware to check for the IP address of incoming requests and if it is an authorised person then only render the user/page.tsx otherwise we render an error message or redirect to another page.
Creating Middleware in Next.js
To create the middleware, create a file called middleware.ts at the root of the project folder.
Example:
Run the below command in the terminal to create a new Next.js application:
npx create-next-app@latest
Check Yes for both TypeScript and App Router to follow along with us:
If you have any problems using Next.js, see Next.js Installation and Getting Started with Next.js.
Now open the project inside a code editor, remove all the files inside the app directory and create a file page.tsx for our main page with the following code:
const main = () => {
return <h1>Main Page</h1>;
};
export default main;
Now let’s create another route ‘/home’ inside the app directory by creating a folder called home with a page.tsx file with the following code:
const home = () => {
return <h1>Home Page</h1>;
};
export default home;
Now create a file middleware.ts inside the project folder (not in the app) with the following code:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.rewrite(new URL('/home', request.url));
return response;
}
This rewrites the URL internally to ‘/home’, not redirects. This means the URL is still the same as the user requested but the route gets changed.
Execute the below command to run the project:
npm run dev
See here we are trying to access the main page and test (which does not exist) but we get the home page in response, it means our middleware successfully intercepts the request and redirects it to the ‘/home’ route.
Matching Paths
By default, the middleware is called for every route in the project so it is important to specify which route to be used by middleware. For this we have matchers.
export const config = {
matcher: '/',
};
For multiple paths:
export const config = {
matcher: ['/', '/about'],
}
Example:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.rewrite(new URL('/home', request.url));
return response;
}
export const config = {
matcher: '/',
};
See if we try to access the ‘/test’ route, the middleware will not redirect us.
Conditional Statements
There is only one middleware.ts or middleware.js, so we can use condition statements to handle requests for multiple routes differently.
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
// Simulated authentication status for example
const isAuthenticated = false;
export function middleware(request: NextRequest) {
if (request.nextUrl.pathname === '/') {
// Redirect to /home
const response = NextResponse.rewrite(new URL('/home', request.url));
return response;
}
if (/^\/admin/.test(request.nextUrl.pathname)) {
// Check authentication status
if (!isAuthenticated) {
// Redirect to /login if not authenticated
const response = NextResponse.rewrite(new URL('/login', request.url));
return response;
} else {
// Redirect to requested page
return NextResponse.rewrite(request.url);
}
}
}
export const config = {
matcher: ['/', '/admin/:path*'],
}
In the first “if” we use strict equality operators, but in the second we use regular expressions to check all routes that start with “admin”.
Setting Headers and Cookies
We can also see headers and cookies in the middleware using the NextResponse API.
if (/^\/admin/.test(request.nextUrl.pathname)) {
// Check authentication status
if (!isAuthenticated) {
// Redirect to /login if not authenticated
const response = NextResponse.rewrite(new URL('/login', request.url));
// Set custom header
response.headers.set('x-error-message', 'Unauthorized User');
// Set cookie for unauthorized access
response.cookies.set('user-access', 'denied');
return response;
} else {
// Redirect to requested page
return NextResponse.rewrite(request.url);
}
}
Producing Response
Additionally, we can also send responses from middleware.
if (request.nextUrl.pathname === '/api/helloworld') {
return NextResponse.json({ message: 'Hello, World!' });
}
*What’s Next?
Our next tutorial will cover a more advanced topic: pre-rendering and data fetching in Next.js, so check it out for the easiest explanation.
Summary
In short, the middleware is the code that executes for each page in a Next.js application to perform certain actions like changing the response, redirecting, modifying headers, producing responses etc.
We can create a middleware by creating a file called middleware.ts or middleware.js inside the root of your project (not in the app directory) and that’s it, we are ready to handle all the upcoming requests and responses on our application.
Reference
https://nextjs.org/docs/app/building-your-application/routing/middleware