Middleware In Next.Js
Middleware in Next.js enhances security, performance, and SEO by processing requests before they reach their destination
What is Middleware?
Middleware is an important concept in web development. It is basically a function that placed between an incoming request from the client and the final response returned by the server, Middleware allows us to apply various checks and modifications on the request before it reaches its destination.
Advantages of Using Middleware in Next.js
1) Boosting Performance of Your Web App
So the middleware processes requests before they reach the final endpoint, it helps optimize performance by skipping unnecessary calls. Means that this may help us to block unauthenticated users from accessing protected routes, reducing the server load and ensuring a faster response time.
For example lets give access to visit the profile and dashboard to just the authenticated users
import { NextResponse } from 'next/server';
import { NextRequest } from 'next/server';
export function middleware(request : NextRequest){
const token = request.cookies.get('token')
if(!token){
return NextResponse.redirect(new URL('/login' , request.url))
}
return NextResponse.next();
}
export const config = {
matcher : ['/profile/:path*' , '/dashboard/:path*']
}
So in this code the middleware function is executing in each request that matches the routs (/profile and dashboard) that we define in the matcher configuration
So as the code say , when retrieving the token from the cookies , we check if it exist , if yes the function returns NextResponse.next() which refers in this case our private routs (/profile and dashboard)
But if the token not exist the middleware help us to redirect the unauthorized user to the login page
export const config = {
matcher : ['/profile/:path*' , '/dashboard/:path*']
}
Matcher configuration define which routs should the middleware apply the logic on , and the structure of ‘/profile/:path’ means the middleware will apply on /profile and any subpath of it Like (/profile/settings) for example
2)Middleware Acts Like a Bouncer at an Event
Middleware plays a crucial role in security by filtering requests before they reach protected endpoints , just like a bouncer at an event , only allowing people with VIP can access it , so the middleware can restrict access to sensitive routes based on user roles.
Where Can We Use Middleware?
1) Authentication and Authorization
Middleware is perfect for handling authentication and authorization , so instead of loading a protected page and then checking if the user is authenticated on the client-side, we can block unauthorized users at the middleware level and skipping unnecessary API calls , (also making a clean and strong code)
2) SEO Redirects
Middleware can enhance SEO by handling automatic redirects from old URLs to new ones by skipping broken links. It can also serve region-based content dynamically , for example if a visitor from morocco we can redirect him to the Arabic content /ar
Best Practices for Using Middleware in Next.js (based on my experience with it)
1) Divide and Conquer (فرق تسد)
Next.js allows us to create only one middleware file (middleware.ts or middleware.js). so to keep your middleware clean and readable, it’s best to divide the logic into separate functions and import them into the main middleware file
import { NextRequest, NextResponse } from 'next/server';
import { checkAuth, checkRole } from './middleware-helper';
export function middleware(request: NextRequest) {
if (!checkAuth(request)) {
return NextResponse.redirect(new URL('/login', request.url));
}
if (!checkRole(request, 'admin')) {
return NextResponse.redirect(new URL('/', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: '/admin/:path*',
};
As you can see, it's a masterpiece of clean and organized code that makes it easy to read and manage ❤️
2) Using Matcher Configuration
Instead of running middleware on every request, Next.js provides the matcher configuration, which allows us to specify which routes the middleware should run on, This makes the app more efficient by skipping unnecessary executions
export const config = {
matcher: ['/dashboard/:path*', '/admin/:path*', '/profile/:path*'],
};
So in this example the middleware only runs on these routes instead of applying to every request