Next.js Middleware Explained in 5 Minutes
Learn how to use Middleware to run code before a request is completed

Next.js Middleware Explained in 5 Minutes

What is Next.js Middleware?

According to the official Next.js Middleware Docs:

"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."

It's very important to understand this definition well in order to master Next.js Middleware, so let's visualize it!

  • Without Middleware: The client requests the webpage, and gets the expected response back; the HTML webpage.
  • With Middleware: The client requests the webpage, but this time the request is intercepted, and a middleware() function is invoked.
  • If the client request/response is unchanged in the middleware function, the request proceeds as normal.
  • If the request/response is modified, e.g. redirecting the client when they are not authenticated, the client will receive a different response, e.g. a new webpage.

Knowing this, we'll now write 2 middleware functions to understand how it works in Next.js.

So without further ado… Let's dive right in!

1. Matcher

The matcher is used to configure the middleware to run for particular routes.

By default, Next.js middleware will be invoked for every route, which is not always necessary (E.g. you don't need middleware for an about page).

Lets imagine a client is trying to access the /account page but they are not authenticated.

In this case, we would expect the client to be redirected to another page, e.g. /login.

We can configure the matcher to ensure the middleware will only run for the login and account page to perform these checks.

Below is the code to make this happen; lets break it down step-by-step:

Copyimport { 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) {
  // Make sure to implement authentication properly.
  // This variable is set for instructional purposes.
  const authenticated = false

  const requestPath = request.nextUrl.pathname

  if (requestPath.startsWith("/login") && authenticated) {
    return NextResponse.redirect(new URL("/account", request.url))
  } else if (requestPath.startsWith("/account") && !authenticated) {
    return NextResponse.redirect(new URL("/login", request.url))
  }

  return NextResponse.next()
}

// Here is the matcher config.
// Set an array of paths that are able to invoke middleware.
// Also accepts wildcard paths.
export const config = {
  matcher: ["/login", "/account"],
}        

  • We declare a variable authenticated to mock user authentication.
  • We perform a conditional statement to redirect the user based on their authentication status.
  • At the bottom of the code, we set the matcher config to ensure the middleware only runs on the login and account pages.

NOTE: Middleware can only run on the Edge Runtime. It is currently not available in the Node.js Runtime.

2. Auth

In the last example we didn't implement auth. So let's do that now.

We will be implementing basic refresh token authentication here for demonstrational purposes.

In practice, an authentication library like next-auth would be a safer choice.

Copyimport { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
import { and, eq, gt } from "drizzle-orm"

import { db } from "./db"
import { sessions } from "./db/schema"

export async function middleware(request: NextRequest) {
  const token = request.cookies.get("token")

  // Validate the token. This is a placeholder function.
  // You should replace it with your actual token validation logic.
  const isAuthenticated = await validateToken(token?.value)

  const requestPath = request.nextUrl.pathname

  if (requestPath.startsWith("/login") && isAuthenticated) {
    return NextResponse.redirect(new URL("/account", request.url))
  } else if (requestPath.startsWith("/account") && !isAuthenticated) {
    return NextResponse.redirect(new URL("/login", request.url))
  }

  return NextResponse.next()
}

// Validating a session stored in a Database using DrizzleORM
async function validateToken(token: string | undefined): Promise<boolean> {
  if (!token) return false

  // Is the session token valid?
  // Using DrizzleORM syntax
  const result = await db
    .select()
    .from(sessions)
    .where(
      and(eq(sessions.sessionToken, token), gt(sessions.expires, new Date()))
    )

  return result.length > 0
}

export const config = {
  matcher: ["/login", "/account"],
}        

  • Instead of hard coding the auth status, we define a validateToken function.
  • This function accepts the token from request cookies, and this token is compared against the existing token stored in a database.
  • We query the database using DrizzleORM in this example, but you can also use Prisma, or no ORM for that matter.

Conclusion

Next.js Middleware is an advanced topic, and there is much more to explore.

While I covered the most common use cases, there are alternative options to configure to cover edge cases, such as:

And if you want to get really deep into this topic, I recommend checking out the Advanced Middleware Flags for advanced use cases.

If you enjoyed this article, please make sure to Subscribe, Clap, Comment and Connect with me today! ??

References

It's impressive how middleware capabilities in Next.js can greatly enhance the response handling process. The ability to customize requests and responses opens up a lot of possibilities for developers. How have you found middleware impacting your projects?

回复

要查看或添加评论,请登录

Hasibul Islam的更多文章

社区洞察

其他会员也浏览了