DEV Community

Dugg
Dugg

Posted on

Middleware in Next.js - The Right Way.

Middleware in Next.js – The Right Way

When you start working with middleware in Next.js, it feels easy enough:
add a couple of redirects, protect some routes, done.

But here’s the problem: most developers (including past me) end up writing middleware like this:

export async function middleware(request: NextRequest, user: User | null) {
  const { pathname } = request.nextUrl;

  if (pathname.startsWith("/dashboard")) {
    if (!user) {
      return NextResponse.redirect(new URL("/login", request.url));
    }
  } else if (pathname.startsWith("/onboarding")) {
    if (!user) {
      return NextResponse.redirect(new URL("/login", request.url));
    }
  }

  if (pathname.startsWith("/login")) {
    if (user) {
      return NextResponse.redirect(new URL("/dashboard", request.url));
    }
  } else if (pathname.startsWith("/signup")) {
    if (user) {
      return NextResponse.redirect(new URL("/dashboard", request.url));
    }
  }

  return NextResponse.next();
}

Enter fullscreen mode Exit fullscreen mode

Even though this works fine at a first glance. But there's a big problem in this approach.

As soon as you add more routes (e.g. /profile, /settings, /forgot-password, etc.)... your file turns into a giant mess of if/else.

And it will only become worse as your project scales.

Not fun, not scalable, and definitely - not clean.


Think in Rules, Not Conditions

Instead of hardcoding every redirect, we must only define conditions.

Think of a middleware as of a traffic cop.

The cop doesn't care who you are. It only checks where you’re going and whether you’re allowed to be there.

So let’s describe our rules in a structured way.


Step 1: Define Route Groups

We’ll split our routes into two simple groups:

const PROTECTED_ROUTES = ["/dashboard", "/onboarding"];
const AUTH_ROUTES = ["/login", "/signup"];
Enter fullscreen mode Exit fullscreen mode
  • Protected routes: only logged-in users can access them.
  • Auth routes: only logged-out users should see them.

Already, much cleaner than writing 20 if checks.


Step 2: Describe Rules

Now let’s create a type for rules:

interface RouteRule {
  routes: string[];
  condition: (user: User | null) => boolean;
  redirect: string;
}
Enter fullscreen mode Exit fullscreen mode

Each rule says:

  • Which routes it applies to
  • The condition that triggers a redirect
  • Where to redirect to

Step 3: Write the Rules

Here’s what our two rules look like:

const routeRules: RouteRule[] = [
  {
    routes: PROTECTED_ROUTES,
    condition: (user) => !user, // if no user, redirect
    redirect: "/login",
  },
  {
    routes: AUTH_ROUTES,
    condition: (user) => !!user, // if logged in, redirect
    redirect: "/dashboard",
  },
];
Enter fullscreen mode Exit fullscreen mode

That’s it. Add more groups? Just add more objects.
No need to touch the logic again.


Step 4: Apply Rules in Middleware

Now we loop through the rules:

export class Middleware {
  private routeRules = routeRules;

  async handle(request: NextRequest, user: User | null) {
    const { pathname } = request.nextUrl;

    for (const rule of this.routeRules) {
      const isMatch = rule.routes.some((r) =>
        pathname.startsWith(r)
      );

      if (isMatch && rule.condition(user)) {
        return NextResponse.redirect(
          new URL(rule.redirect, request.url)
        );
      }
    }

    return NextResponse.next();
  }
}
Enter fullscreen mode Exit fullscreen mode

No clutter. No chaos. Just clear logic.


Why This Is the “Right Way”

  1. Scalable – Add new routes in seconds
  2. Readable – No jungle of if/else
  3. Reusable – One central place for rules
  4. Extensible – Can be expanded for roles, permissions, etc.
  5. SOLID compliant.

Middleware must feel like a middleware, not a nightmare.


Final Thoughts

The trick is simple:
Stop hardcoding conditions. Start thinking in rules.

This pattern will save you headaches, especially if your app has authentication and a growing set of protected pages.

If you found this interesting, comment, and let's have a talk!

Top comments (0)