DEV Community

Cover image for Edge Runtime vs Node.js in Next.js: Lessons from Role-Based Auth Middleware
Sayan Khan
Sayan Khan

Posted on

Edge Runtime vs Node.js in Next.js: Lessons from Role-Based Auth Middleware

Introduction

While building a Next.js application, I had to implement role-based access control so users could only access routes meant for their role. I was already using JWT authentication stored in cookies, and most of the authentication logic worked as expected.

However, when I introduced Next.js middleware to enforce these checks earlier in the request lifecycle, I noticed that some of my assumptions—based on how backend logic usually works—no longer held true.

This pushed me to understand how Next.js Edge runtime behaves differently from the normal Node.js runtime, and why that difference matters.

How My Application Initially Handled Authentication

In my application, authentication was based on JWTs stored in cookies. This allowed the app to remember the user without making repeated API calls.

This approach worked well in:

  • API routes running on the Node.js runtime
  • Server-side logic where user data could be extracted easily

Because of this, I initially expected the same logic to behave the same way everywhere.

Where Middleware Changed the Picture

The challenge appeared when I moved role-based checks into middleware.

The goal was simple:

  • Check the user’s role before routing
  • Prevent unauthorized users from reaching pages
  • Avoid waiting for API calls to fail

Even though the same JWT logic worked in backend routes, middleware behaved differently. That’s when I realized this wasn’t just an authentication issue—it was a runtime difference.

Node.js Runtime vs Edge Runtime (What I Learned)

Next.js Edge runtime vs Node.js runtime behavior

Based on this experience, here’s how I now think about the two runtimes in Next.js.

Node.js Runtime

  • Long-running execution model
  • Supports full Node.js APIs
  • Suitable for database access and sessions
  • Works well for business logic

This runtime behaves similarly to traditional Express-style servers.

Edge Runtime

  • Stateless and request-based
  • No persistent memory
  • No Node.js APIs
  • Designed for lightweight, fast checks

This difference explains why logic that works in backend routes may behave differently in middleware.

Why Edge Middleware Made Sense for Role-Based Checks

Next.js middleware request flow

Middleware runs before pages and API routes, which makes it ideal for enforcing access rules early.

In my case, middleware was used to:

  • Read JWTs from cookies
  • Verify the token using Edge-compatible logic
  • Extract the user role
  • Allow or block access before routing

This ensured role checks happened before any page rendering or API execution.

The Middleware Code

Here’s a simplified version of the middleware I worked with:

import { NextRequest, NextResponse } from "next/server";
import { verifyEdgeToken } from "@/lib/edgeAuth";

export async function middleware(req: NextRequest) {
  const { pathname } = req.nextUrl;

  const token = req.cookies.get("token")?.value;
  if (!token) {
    return NextResponse.json({ message: "Unauthorized" }, { status: 401 });
  }

  const payload = await verifyEdgeToken(token);

  if (pathname.startsWith("/ec") && payload.role !== "EC") {
    return NextResponse.json({ message: "Forbidden" }, { status: 403 });
  }

  if (pathname.startsWith("/voter") && payload.role !== "VOTER") {
    return NextResponse.json({ message: "Forbidden" }, { status: 403 });
  }

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

The key point is that this logic is intentionally minimal, because Edge middleware is meant for early decisions—not business logic.

What Confused Me About Edge Runtime (At First)

Initially, Edge runtime felt strange:

  • There was no persistent server
  • Each request felt isolated
  • Nothing stayed in memory

Coming from a Node.js mindset, this felt like something was broken.

Eventually, I understood that this is expected behavior in Edge environments.

What I Finally Understood

Edge runtime is:

  • Stateless
  • Request-scoped
  • Designed for scalability

This is why:

  • JWT-based authentication works well
  • Session-based or in-memory approaches do not
  • Middleware logic must remain lightweight

Once I accepted these constraints, middleware behavior made sense.

Where Edge Runtime Fits (and Where It Doesn’t)

Good use cases:

  • Authentication and authorization
  • Route protection
  • Redirects
  • Lightweight request validation

Poor use cases:

  • Database access
  • Heavy computation
  • Business logic

Understanding this boundary helped me design cleaner Next.js architecture.

Final Thoughts

This experience taught me that runtime choice in Next.js is an architectural decision, not just a performance detail.

Using middleware for role-based checks helped me understand how Edge runtime and Node.js runtime complement each other—and why using each in the right place matters.

Top comments (0)