Next.js
I. Next.js Fundamentals
1. Introduction to Next.js
2. Next.js vs React
3. Project Setup
4. Folder Structure
5. Pages and Routing
6. Link and Navigation
7. Static Assets
8. CSS and Styling
II. Routing, Data & Rendering
9. Dynamic Routing
10. API Routes
11. Data Fetching Basics
12. getStaticProps
13. getServerSideProps
14. Incremental Static Regeneration
15. Rendering Strategies
16. Image Optimization
III. Advanced Next.js & Performance
17. Middleware
18. Authentication Basics
19. Authorization
20. Environment Variables
21. Performance Optimization
22. SEO with Next.js
23. Internationalization
24. Error Handling
IV. Projects, Deployment & Best Practices
25. Next.js Best Practices
26. Folder and Code Organization
27. Testing Next.js Apps
28. Security in Next.js
29. Next.js Interview Questions
30. Mini Project – Blog
31. Mini Project – Dashboard
32. Mini Project – Ecommerce
33. Mini Project – API Integration
34. Next.js Case Study
35. Real-World Use Cases
36. Project Planning
37. Final Project
38. Deployment with Vercel
39. Course Review
40. Career Roadmap
Lesson 17
Middleware
Learn to intercept requests before they hit your pages, handle authentication guards, and add custom logic to your Next.js application flow.
Middleware sits between your users and your pages. Every request passes through it first. Think of middleware like a security guard at a building entrance — they check everyone before letting them through. Unlike regular React components that run in the browser, middleware runs on the edge. The edge means servers close to your users around the world. When someone visits NewsWave from Japan, middleware runs on a server in Japan. From Germany? A server in Germany handles it. This keeps things fast. The power comes from interception. You can redirect users, rewrite URLs, add headers, or block requests entirely. All before Next.js renders any page. Traditional React apps can't do this — they only run after the page loads.What Middleware Does
Middleware handles cross-cutting concerns. These are features that affect multiple pages across your app. Authentication, internationalization, A/B testing, bot detection — all perfect middleware use cases. The NewsWave team needs several middleware features. They want to redirect old article URLs to new ones. Block suspicious requests. Add security headers to every response. Check if users are logged in before they access admin pages. Without middleware, you'd add this logic to every page component. That means duplicated code. Inconsistent behavior. Easy to miss pages. Middleware centralizes everything in one place. Here's what middleware can do:Request Control
Redirect, rewrite, or block requests before pages load
Authentication Guards
Check login status and protect private routes
Header Modification
Add security headers, CORS, or custom metadata
URL Rewriting
Transform URLs without changing the browser address
Creating Your First Middleware
Middleware lives in a special file calledmiddleware.js. You create this file in your project root — the same level as your app folder.
The file must export a function called middleware. Next.js calls this function for every request. The function receives a request object with information about the incoming request.
File structure
📁 newswave/
📄 middleware.js ← Create this file
📁 app/
📄 page.js
📁 articles/
📄 package.json
// middleware.js — runs before every page request
export function middleware(request) {
// Get the URL that the user is trying to visit
console.log('Request URL:', request.nextUrl.pathname)
// Let the request continue to its destination
return
}Terminal
$ npm run dev
Next.js 14.0.0
Local: http://localhost:3000
✓ Ready in 2.1s
Request URL: /
Request URL: /_next/static/chunks/webpack.js
Request URL: /favicon.ico
What just happened?
Your middleware ran for every request — even static files. The console shows each URL that users tried to access. Next.js passes a request object with details about where they want to go. Try this: visit different pages and watch the terminal output change.
Response Objects and Control Flow
Middleware can do three things with requests. Let them through unchanged. Redirect users somewhere else. Or rewrite the URL to serve different content. You control this with response objects.NextResponse creates these response objects. Import it from next/server. Think of NextResponse like a traffic controller — it tells requests where to go.
// Import NextResponse to control request flow
import { NextResponse } from 'next/server'
export function middleware(request) {
// Get the current path user is visiting
const path = request.nextUrl.pathname
// Log each request for debugging
console.log(`Middleware: ${path}`)
}Terminal
$ npm run dev
Middleware: /
Middleware: /articles
Middleware: /search
import { NextResponse } from 'next/server'
export function middleware(request) {
const path = request.nextUrl.pathname
// Redirect old URLs to new ones (browser shows new URL)
if (path === '/old-news') {
return NextResponse.redirect(new URL('/articles', request.url))
}
// Rewrite URLs (browser keeps showing original URL)
if (path === '/breaking') {
return NextResponse.rewrite(new URL('/articles?category=breaking', request.url))
}
}Terminal
Middleware: /old-news
→ Redirecting to /articles
Middleware: /breaking
→ Rewriting to /articles?category=breaking
What just happened?
NextResponse gives you three options: redirect (changes browser URL), rewrite (serves different content with same URL), or return nothing (let request continue). The
new URL() constructor needs the full URL, so you pass the original request.url as the base. Try this: create an /old-news route and see how it redirects.Middleware Configuration
By default, middleware runs on every single request. That includes static files, images, API routes — everything. Most of the time you don't want this. You want middleware to run only on specific paths. Theconfig object controls which paths trigger middleware. Export it from your middleware file. Use the matcher property to specify patterns.
Think of matcher like a filter. Only requests that match your patterns will run your middleware function. Everything else bypasses middleware completely.
import { NextResponse } from 'next/server'
export function middleware(request) {
console.log('Protected route accessed:', request.nextUrl.pathname)
// Your middleware logic here
}
// Only run middleware on these paths
export const config = {
matcher: ['/admin/:path*', '/profile/:path*']
}Terminal
$ npm run dev
Middleware will only run on /admin/* and /profile/* routes
Static files and other routes bypass middleware
✓ Ready in 1.8s
:path* means match any sub-path. /admin/:path* matches /admin, /admin/users, /admin/settings/general — everything under admin.
You can exclude paths too. NewsWave wants middleware on all pages except API routes and static assets:
// Run middleware on all paths except these excluded ones
export const config = {
matcher: [
// Match all routes except API and static files
'/((?!api|_next/static|_next/image|favicon.ico).*)',
]
}Terminal
Middleware runs on all pages
But skips /api, /_next/static, /_next/image, /favicon.ico
✓ More efficient — no middleware on static assets
What just happened?
The config object filters which requests run middleware. Glob patterns like
:path* match multiple routes. Negative lookahead (?!pattern) excludes paths. This keeps middleware fast by avoiding unnecessary runs on static files. Try this: add console logs and visit different pages to see which ones trigger middleware.Authentication Middleware
Authentication middleware is middleware's killer feature. Traditional React apps check authentication in every protected component. That's slow and inconsistent. Users see protected content for a split second before redirecting. Middleware runs before any page renders. Users never see protected content if they shouldn't. The check happens on the server, closer to your database. Much faster and more secure. The pattern is simple. Check if the user has a valid session token in their cookies. If yes, let them through. If no, redirect to login. All authentication logic centralized in one place.import { NextResponse } from 'next/server'
export function middleware(request) {
// Get the user's session token from cookies
const token = request.cookies.get('session-token')
const path = request.nextUrl.pathname
// Check if user is trying to access admin area
if (path.startsWith('/admin')) {
// No token means not logged in - redirect to login
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
}Terminal
User visits /admin without token
→ Redirected to /login
User visits /admin with valid token
→ Allowed through to /admin
✓ No flash of protected content
import { NextResponse } from 'next/server'
export function middleware(request) {
const token = request.cookies.get('session-token')?.value
const path = request.nextUrl.pathname
// Protected routes that require authentication
const protectedPaths = ['/admin', '/profile', '/dashboard']
const isProtectedPath = protectedPaths.some(p => path.startsWith(p))
if (isProtectedPath) {
// No token at all - definitely not authenticated
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Here you would validate the token with your auth service
// For now, we'll assume any token is valid
console.log(`Access granted to ${path}`)
}
}Terminal
Protected routes: /admin, /profile, /dashboard
User visits /profile
→ Checking session-token cookie
✓ Access granted to /profile
What just happened?
Authentication middleware checks cookies before page render. The
cookies.get() method reads browser cookies. Array.some() checks if the current path matches any protected route. In production, you'd validate the token against your authentication service. Try this: set different cookies in your browser dev tools and test the middleware behavior.Headers and Security
Middleware can modify request and response headers. Headers carry metadata about requests and responses. Security headers protect your app from common attacks. CORS headers control which domains can access your API. Response headers get added to every response your app sends. This makes middleware perfect for security headers that should apply site-wide. Content Security Policy, CORS settings, cache control — all handled in one place.import { NextResponse } from 'next/server'
export function middleware(request) {
// Create a response to modify headers
const response = NextResponse.next()
// Add security headers to every response
response.headers.set('X-Frame-Options', 'DENY') // Prevent embedding in iframes
response.headers.set('X-Content-Type-Options', 'nosniff') // Prevent MIME sniffing
response.headers.set('Referrer-Policy', 'origin-when-cross-origin') // Control referrer info
return response
}Terminal
✓ Security headers added to all responses
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: origin-when-cross-origin
import { NextResponse } from 'next/server'
export function middleware(request) {
// Read incoming request headers
const userAgent = request.headers.get('user-agent')
const acceptLanguage = request.headers.get('accept-language')
const authorization = request.headers.get('authorization')
// Block suspicious user agents (simple bot detection)
if (userAgent && userAgent.includes('malicious-bot')) {
return NextResponse.json({ error: 'Access denied' }, { status: 403 })
}
// Log user language for analytics
console.log('User language preference:', acceptLanguage)
return NextResponse.next()
}Terminal
Request from Chrome browser
User language preference: en-US,en;q=0.9
Request from malicious-bot
✗ Access denied (403)
What just happened?
Middleware can read request headers with
request.headers.get() and set response headers with response.headers.set(). NextResponse.json() returns JSON responses with custom status codes. Security headers protect against common attacks by telling browsers how to handle your content. Try this: open browser dev tools and check the Network tab to see these headers on your responses.Real-World Middleware Examples
Professional Next.js apps combine multiple middleware patterns. NewsWave needs authentication, URL redirects, security headers, and request logging. Here's how to structure complex middleware:import { NextResponse } from 'next/server'
export function middleware(request) {
const path = request.nextUrl.pathname
const response = NextResponse.next()
// 1. Security headers (apply to all responses)
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-Content-Type-Options', 'nosniff')
// 2. Legacy URL redirects
if (path === '/news') {
return NextResponse.redirect(new URL('/articles', request.url))
}
return response
}// Complete NewsWave middleware with all features
import { NextResponse } from 'next/server'
export function middleware(request) {
const { pathname } = request.nextUrl
const token = request.cookies.get('auth-token')?.value
// Create response object for header modifications
let response = NextResponse.next()
// Authentication check for protected routes
const protectedRoutes = ['/admin', '/profile', '/dashboard']
if (protectedRoutes.some(route => pathname.startsWith(route))) {
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
}
// URL redirects for SEO
const redirects = {
'/news': '/articles',
'/blog': '/articles',
'/story': '/articles'
}
if (redirects[pathname]) {
return NextResponse.redirect(new URL(redirects[pathname], request.url))
}
// Add security headers to all responses
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('Referrer-Policy', 'origin-when-cross-origin')
return response
}Terminal
Request: /admin (no auth token)
→ Redirected to /login
Request: /news
→ Redirected to /articles
Request: /articles
✓ Security headers added, request allowed
// Only run middleware on page routes, not static assets
export const config = {
matcher: [
// Skip API routes and static files
'/((?!api|_next/static|_next/image|favicon.ico).*)',
]
}Common Middleware Mistakes
Don't run expensive operations in middleware — it slows down every request. Avoid database calls or external API requests. Don't forget the config matcher — without it, middleware runs on static files too. Always return a NextResponse object, even if it's just NextResponse.next().
What just happened?
Professional middleware combines multiple concerns in one function. Early returns handle redirects and authentication. The response object accumulates headers throughout the function. Object lookups make redirect mapping clean and maintainable. The matcher config ensures middleware only runs where needed. Try this: add console.log statements to track which middleware logic runs for different requests.
Quiz
1. The NewsWave team wants to add authentication checks. What's the main advantage of using middleware instead of checking authentication in page components?
2. NewsWave wants to serve different content at /breaking but keep the URL unchanged in the browser. Which NextResponse method should they use?
3. NewsWave's middleware is running on static files and slowing down the app. Which config would fix this by excluding API routes and static assets?
Up Next: Authentication Basics
Build complete user authentication with login, signup, and session management using NextAuth.js and modern authentication patterns.