If you're building a modern web app with Next.js App Router, you'll likely need to protect certain pages—like /dashboard
or /account
—from unauthenticated access.
In this post, you'll learn how to protect private routes using Middleware in Next.js 13+ (with the new app/
directory).
🧭 What do we mean by private routes?
Private routes are pages that should only be accessible if the user is authenticated.
Example:
✅ /login → anyone can see it
✅ /register → anyone can register
🔒 /dashboard → only logged-in users
🔒 /settings → must be authenticated
If a user isn't logged in, they should be redirected—usually to /login
.
⚙️ App router vs pages router
In Pages Router (pages/
folder), route protection was usually done via getServerSideProps
or HOCs.
With the App Router (app/
folder), the new recommendation is to use Middleware for global auth checks.
🧪 Step-by-step: protecting routes with middleware
1. Create your middleware
In the root of your project, create a file:
/middleware.ts
Basic structure:
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth_token');
const isAuthenticated = !!token?.value;
const protectedPaths = ['/dashboard', '/settings'];
const pathname = request.nextUrl.pathname;
const isProtected = protectedPaths.some((path) =>
pathname.startsWith(path)
);
if (isProtected && !isAuthenticated) {
const loginUrl = new URL('/login', request.url);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
This checks for a cookie called auth_token
and redirects to /login
if it's missing.
2. Configure your middleware.ts
matcher
At the bottom of your file (or in next.config.js
), you can limit where middleware applies:
export const config = {
matcher: ['/dashboard/:path*', '/settings/:path*'],
};
This means the middleware only runs on the routes you specify.
3. Set the cookie on login
In your login logic (e.g. inside an API route or app/login/actions.ts
), set the auth token in a cookie:
import { cookies } from 'next/headers';
export async function login(email: string, password: string) {
// Your auth logic here
const token = await getTokenFromServer(email, password);
cookies().set('auth_token', token, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
});
}
> You can also use JWTs or session tokens stored in secure HTTP-only cookies.
🛑 What middleware can _not_ do
- ❌ You can’t access
localStorage
orwindow
(it runs on the Edge) - ❌ You can’t fetch server-side session data unless you pass it via cookies or headers
- ✅ You can read cookies and request URLs
🧠 Pro tips
- Use
matcher
to limit the scope of your middleware - Protect both frontend and API routes if needed
- Prefer secure, HTTP-only cookies for auth tokens
- For more complex logic, integrate NextAuth.js or your own session system
✅ Summary checklist
- ✅ Add a
middleware.ts
at the root - ✅ Read the
auth_token
from cookies - ✅ Redirect unauthenticated users to
/login
- ✅ Use
matcher
to target private routes - ✅ Set auth cookie on login, and clear it on logout
🔚 Conclusion
With Next.js App Router and Middleware, protecting private routes is lightweight and powerful. It’s one of the best ways to guard your pages on the edge—before they even render.
Using this pattern, you can create fast, secure apps that feel seamless for users while keeping sensitive content safe.