DEV Community

Abdul Rahman
Abdul Rahman

Posted on • Originally published at blog.abdulrdeveloper.me

Next.js Middleware — Route Protection Without Any Library

Most beginners think route protection means installing NextAuth or Clerk. It does not always have to be that.

For a small project, a hackathon prototype, or just something you are building to learn — a simple cookie check in middleware is enough to get the job done.

This is exactly that.


How the Logic Works

Next.js middleware runs before a request reaches any page. That makes it the perfect place to check if someone is allowed to visit a route.

Here is what we are doing:

  • If someone tries to open /dashboard without being logged in, they get kicked back to /

  • If someone is already logged in and visits /, they get skipped straight to /dashboard

  • The check is based on one cookie: isLoggedIn

That is it. No library. No JWT verification. Just a cookie presence check.


Step 1: Create middleware.ts (or middleware.js)

Put this file in your root directory / — or inside src/ if you use that structure.

TypeScript (middleware.ts):

import { NextRequest, NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  const isLoggedIn = request.cookies.has('isLoggedIn');
  const { pathname } = request.nextUrl;

  const protectedRoutes = ['/dashboard'];
  const isProtectedRoute = protectedRoutes.some(route => pathname.startsWith(route));

  if (isProtectedRoute && !isLoggedIn) {
    return NextResponse.redirect(new URL('/', request.url));
  }

  if (pathname === '/' && isLoggedIn) {
    return NextResponse.redirect(new URL('/dashboard', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/', '/dashboard/:path*'],
};
Enter fullscreen mode Exit fullscreen mode

or if you are using JavaScript (middleware.js) — same logic, no types:

import { NextResponse } from 'next/server';

export function middleware(request) {
  const isLoggedIn = request.cookies.has('isLoggedIn');
  const { pathname } = request.nextUrl;

  const protectedRoutes = ['/dashboard'];
  const isProtectedRoute = protectedRoutes.some(route => pathname.startsWith(route));

  if (isProtectedRoute && !isLoggedIn) {
    return NextResponse.redirect(new URL('/', request.url));
  }

  if (pathname === '/' && isLoggedIn) {
    return NextResponse.redirect(new URL('/dashboard', request.url));
  }

  return NextResponse.next();
}

export const config = {
  matcher: ['/', '/dashboard/:path*'],
};
Enter fullscreen mode Exit fullscreen mode

The matcher at the bottom is important. Without it, the middleware runs on every single request — including _next/static files, images, API routes. That slows things down for no reason. The matcher makes sure it only fires where it actually matters.


Step 2: Wire It Up to Your Forms

You do not need to change your layout or UI. Just drop these lines into the right places.

On Login — inside your login page's submit handler:

Most people have a login form on their landing page or /login. Find the function that runs when the form is submitted — after your validation passes, add this:

document.cookie = "isLoggedIn=true; path=/; max-age=86400";
router.push('/dashboard');
Enter fullscreen mode Exit fullscreen mode

max-age=86400 means the cookie lives for exactly one day — 86400 seconds. After that it disappears on its own.

On Logout — inside your dashboard's logout button:

You do not need a separate logout page or file. Just find your logout button — it is usually sitting in your dashboard's navbar or sidebar — and add this to its onClick handler:

<button onClick={() => {
  document.cookie = "isLoggedIn=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
  router.push('/');
  router.refresh();  // It will Clear Next.js client-side cache and force a fresh server state reload (prevents browser back-button bypass)
}}>
  Logout
</button>
Enter fullscreen mode Exit fullscreen mode

Setting expires to a past date is how you delete a cookie. The browser sees it as already expired and removes it.


What This Does Not Do

This setup only checks if the cookie exists. It does not verify a signature or validate who the user actually is.

That means someone could open DevTools, go to Application → Cookies, manually add isLoggedIn=true, and get past the middleware. The lock is real but the key is easy to duplicate.

For a hackathon or a personal project — totally fine. For something handling real user data or payments — replace the boolean cookie with a signed JWT and verify the signature server-side.

But for getting something working fast? This does the job.


No npm install needed. Copy, paste, ship.


🔗 Connect & Explore More

If you found this guide helpful or built something cool with it, let's stay connected! I regularly share minimal production-grade templates, software engineering blueprints, and modern full-stack development insights.

Feel free to star my repositories, drop a comment on the blog, or reach out for collaboration! 🚀

Top comments (0)