Next.js Auth Middleware: A Clean Pattern Without Bugs
By Arash Latifi
Practical guide to auth with Next.js Middleware — session checks, protected routes, smart redirects and secure token handling without leaking to the client.
TL;DR: Centralize auth in
middleware.ts— oneisAuthenticatedhelper and a protected-routes list handle redirects and token refresh cleanly without repeating checks in every page.
Why middleware for auth
Checking session in every page.tsx is repetitive and bug-prone. middleware.ts runs on the Edge before rendering, so it can block or redirect early, read cookies, and even trigger a token refresh. One place for all rules, no logic leaking into components.
If client/server boundaries matter, see RSC poisoning and use-client boundaries and caching and PPR in Next.js 15 to decide what stays in middleware vs server actions.
Clean middleware skeleton
Create middleware.ts at the project root and keep routes declarative:
Use matcher to skip static assets so Edge does not waste cycles.
Token refresh with secure cookies
Short-lived token plus refresh token is the safest combo. Keep middleware light; delegate heavy refresh to a route handler:
Always set cookies httpOnly and secure, never store tokens in localStorage. For asset caching see Next.js image optimization guide so redirects don't clash with image cache.
Redirect decision table
| User state | Requested path | Middleware action | Code |
|---|---|---|---|
| Guest | /dashboard | Redirect to /login?next=/dashboard | 307 |
| Guest | /login | Allow | 200 |
| Authed | /login | Redirect to /dashboard | 307 |
| Authed | /dashboard | Allow + x-user-id header | 200 |
Handle the reverse redirect for authed users hitting login:
Pre-deploy security checklist
- [x]
AUTH_SECRETset in env, not in git - [x] Cookie is
httpOnly,secure,sameSite=lax - [ ]
publicandprotectedlists are exact - [ ]
nextparam validated as internal path only (prevent open redirect) - [ ] Error logs don't leak tokens
Mini-case: an order panel for a Tehran shop duplicated login checks in every page. Moving to middleware removed 120 lines, fixed the "authed user loops to login" bug, and cut Edge response by ~12ms.
FAQ
Q: Should we put heavy logic in middleware?
No. Middleware runs on Edge and must stay light — just cookie checks and redirects. Put heavy validation, DB calls and refresh in route handlers or server actions.
Q: How to prevent open redirect?
Accept next only if it starts with / and contains no // or http; otherwise fall back to /dashboard. Never redirect to raw user input.
Q: Does it clash with next/image?
Not if matcher is correct. Exclude /_next/static and /_next/image in config.matcher so image requests bypass middleware and caching stays intact.