Next.js is a popular framework for building React applications that support server-side rendering, static site generation, and incremental static regeneration. One of the features that Next.js provides is a file-system based router, where folders and files in the app directory are mapped to URL paths.
However, sometimes you may want to perform some actions or modify the request or response before the page is rendered. For example, you may want to add some headers, redirect to another URL, or check for authentication.
This is where middleware comes in handy. Middleware in Next.js is a piece of code that runs before the page is rendered, and allows you to intercept, modify, and control the flow of requests and responses in your application. By using middleware, you can achieve the following benefits:
In this article, we will show you how to use middleware in Next.js, and how to create some common use cases with middleware.
To create a middleware, you need to create a file named _middleware.js
in the app directory or any subdirectory. This file should export a default function that receives the request and response objects as parameters, and returns a response object or nothing. For example, a simple middleware that logs the request method and URL could look something like this:
export default function logger(req, res) {
console.log(`${req.method} ${req.url}`);
}
The middleware function can also be async, and use the await keyword to perform asynchronous operations. For example, a middleware that fetches some data from an API could look something like this:
export default async function fetchData(req, res) {
const data = await fetch('https://example.com/api/data');
// Do something with the data
}
The middleware function can also modify the request or response objects, such as adding headers, cookies, or body. For example, a middleware that adds a custom header could look something like this:
export default function addHeader(req, res) {
res.setHeader('X-Custom-Header', 'Hello, world!');
}
The middleware function can also return a response object, which will end the request and send the response to the client. The response object should have a status
property that indicates the HTTP status code, and optionally a headers
property that contains an object of headers, and a body
property that contains a string or a buffer of the response body. For example, a middleware that returns a JSON response could look something like this:
export default function jsonResponse(req, res) {
return {
status: 200,
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ message: 'Hello, world!' }),
};
}
The middleware function can also return nothing, which means that the request will continue to the next middleware or the page handler. For example, a middleware that does nothing could look something like this:
export default function noop(req, res) {
// Do nothing
}
To apply a middleware to a page or a group of pages, you need to place the _middleware.js
file in the same directory or a parent directory of the page or the group of pages. For example, if you have the following folder structure:
app
├── _middleware.js
├── blog
│ ├── _middleware.js
│ ├── [slug].js
│ └── index.js
├── products
│ ├── [id].js
│ └── index.js
├── users
│ ├── [name].js
│ └── index.js
└── index.js
The corresponding middleware application for the pages are:
/ -> app/_middleware.js
/blog -> app/_middleware.js, app/blog/_middleware.js
/blog/[slug] -> app/_middleware.js, app/blog/_middleware.js
/products -> app/_middleware.js
/products/[id] -> app/_middleware.js
/users -> app/_middleware.js
/users/[name] -> app/_middleware.js
As you can see, the middleware files are applied from the root to the leaf of the directory tree, and the order of execution is from left to right. You can also use the next-connect
library to compose multiple middleware functions into one file, and use methods like use
, get
, post
, etc. to apply them to specific HTTP methods. For example, a composed middleware file could look something like this:
import nc from 'next-connect';
const middleware = nc();
// Apply this middleware to all methods
middleware.use((req, res, next) => {
// Do something common
next();
});
// Apply this middleware only to GET method
middleware.get((req, res, next) => {
// Do something for GET requests
next();
});// Apply this middleware only to POST method
middleware.post((req, res, next) => {
// Do something for POST requests
next();
});export default middleware;
Middleware can be used for various purposes, such as authentication, authorization, redirection, data fetching, etc. Here are some examples of how to create some common use cases with middleware.
Middleware can be used to check if the user is authenticated or not, and redirect them to the login page or the protected page accordingly. For example, a middleware that checks for a cookie named auth
and redirects the user to the login page if it is not present could look something like this:
export default function auth(req, res) {
// Get the cookie from the request headers
const cookie = req.headers.cookie;
// Check if the cookie contains the auth token
if (!cookie || !cookie.includes('auth')) {
// If not, redirect the user to the login page
return {
status: 302,
headers: {
Location: '/login',
},
};
}
}
You can place this middleware in the directory of the pages that require authentication, such as the admin or the profile pages.
Middleware can also be used to check if the user has the right permissions or roles to access a page or a resource, and return an error or a message accordingly. For example, a middleware that checks if the user is an admin or not, and returns a 403 Forbidden response if they are not could look something like this:
export default function admin(req, res) {
// Get the user data from the request object
// This could be done by another middleware that fetches the user data from an API or a database based on the auth token
const user = req.user;
// Check if the user is an admin or not
if (!user || !user.isAdmin) {
// If not, return a 403 Forbidden response
return {
status: 403,
headers: {
'Content-Type': 'text/plain',
},
body: 'You are not authorized to access this page.',
};
}
}
You can place this middleware in the directory of the pages that require admin privileges, such as the dashboard or the settings pages.
Middleware can also be used to redirect the user to another URL based on some conditions, such as the request method, the query parameters, or the user agent. For example, a middleware that redirects the user to the mobile version of the site if they are using a mobile device could look something like this:
export default function mobile(req, res) {
// Get the user agent from the request headers
const userAgent = req.headers['user-agent'];
// Check if the user agent contains the keywords for mobile devices
if (userAgent && /Android|iPhone|iPad|iPod/i.test(userAgent)) {
// If yes, redirect the user to the mobile version of the site
return {
status: 302,
headers: {
Location: `https://m.example.com${req.url}`,
},
};
}
}
You can place this middleware in the root directory of the site, so that it applies to all pages.
Middleware can also be used to fetch some data from an API or a database, and pass it to the page component as props. For example, a middleware that fetches the blog post data based on the slug and passes it to the page component as props could look something like this:
export default async function blogData(req, res) {
// Get the slug from the request query
const { slug } = req.query;
// Fetch the post data from an API or a database based on the slug
const post = await fetchPost(slug);
// Pass the post data to the page component as props
res.props = {
post,
};
}
You can place this middleware in the directory of the blog post page, and use the post
prop to render the post content.
Middleware in Next.js is a powerful tool that allows you to intercept, modify, and control the flow of requests and responses in your application. By using middleware, you can customize the behavior of your application based on the request or response, and implement various use cases such as authentication, authorization, redirection, or data fetching.
If you want to learn more about middle you can check out the offical documentation.