Falytom logoFALYTOM
Games
ToolsUI ComponentsServicesProductsAbout
menu
  1. Home
  2. /Web Development
  3. /Nextjs
  4. /Middleware Intro
Your Progress0%
0 of 70 completed

NextJS Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication

Introduction to Middleware

Request interception and global logic

Middleware runs before every request, giving you complete control to intercept, modify, redirect, or rewrite requests. Use middleware for authentication, logging, redirects,header modification, and any global logic needed across your app. Middleware runs on the Edge runtime for incredible performance. Master middleware and unlock powerful request-level control!

What is Middleware?

Request Flow with Middleware

  1. User requests a page (e.g., /dashboard)
  2. Middleware runs first (before anything else)
  3. Middleware can: redirect, rewrite, modify headers, or continue
  4. If middleware continues, request proceeds to page/API route
  5. Page renders and response sent to user

Common Middleware Use Cases

  • Authentication: Check if user is logged in before showing protected pages
  • Redirects: Redirect users based on conditions (locale, device, etc.)
  • Rewrites: Serve different content without changing the URL
  • Headers: Add security headers, CORS headers, custom headers
  • Logging: Track requests, analytics, debugging
  • A/B Testing: Route users to different versions
  • Rate Limiting: Prevent abuse
  • Bot Detection: Handle bots differently

Basic Middleware Setup

Creating Middleware File

middleware.ts (project root)
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

// This function can be marked 'async' if needed
export function middleware(request: NextRequest) {
  console.log('Middleware running for:', request.nextUrl.pathname);
  
  // Continue to the requested page
  return NextResponse.next();
}

// Optionally configure which routes middleware runs on
export const config = {
  matcher: '/:path*', // Run on all routes
};

// File location: middleware.ts in project root
// OR: src/middleware.ts if using src directory

// ✅ Export middleware function
// ✅ Receives NextRequest
// ✅ Returns NextResponse
// ✅ Optional matcher configuration

Middleware Function Signature

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

export function middleware(request: NextRequest) {
  // Request properties
  const pathname = request.nextUrl.pathname;  // /dashboard
  const url = request.url;                    // Full URL
  const method = request.method;              // GET, POST, etc.
  const headers = request.headers;            // Request headers
  const cookies = request.cookies;            // Request cookies
  
  // Must return NextResponse
  return NextResponse.next();                 // Continue
  // OR
  return NextResponse.redirect(new URL('/login', request.url));
  // OR
  return NextResponse.rewrite(new URL('/other', request.url));
  // OR
  return new Response('Blocked', { status: 403 });
}

// ✅ NextRequest has cookies, geo, IP, nextUrl
// ✅ Must return a Response
// ✅ NextResponse has helpers (next, redirect, rewrite)

Matcher Configuration

Matcher Patterns

middleware.ts
// Match all routes
export const config = {
  matcher: '/:path*',
};

// Match specific routes
export const config = {
  matcher: '/dashboard/:path*',
};

// Match multiple routes
export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*'],
};

// Match with conditions (exclude static files)
export const config = {
  matcher: [
    /*
     * Match all request paths except:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     */
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

// Exclude specific paths
export const config = {
  matcher: [
    // Match everything except static files
    '/((?!_next/static|_next/image|favicon.ico).*)',
  ],
};

// ✅ matcher is an array or string
// ✅ Use regex patterns
// ✅ Exclude static assets for performance

Advanced Matcher Examples

TYPESCRIPT
// Only authenticated routes
export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*', '/settings/:path*'],
};

// Only specific file types
export const config = {
  matcher: '/api/:path*.json', // Only JSON API routes
};

// Everything except public routes
export const config = {
  matcher: [
    '/((?!login|register|forgot-password|_next/static|_next/image).*)',
  ],
};

// Multiple specific paths
export const config = {
  matcher: [
    '/dashboard',
    '/admin/:path*',
    '/api/protected/:path*',
  ],
};

// ✅ Matcher supports path patterns
// ✅ :path* matches nested routes
// ✅ Negative lookahead (?!...) excludes paths

Redirects with Middleware

Basic Redirect

middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Redirect /old-page to /new-page
  if (request.nextUrl.pathname === '/old-page') {
    return NextResponse.redirect(new URL('/new-page', request.url));
  }
  
  return NextResponse.next();
}

// Visit /old-page → Redirects to /new-page
// ✅ User sees /new-page in address bar
// ✅ Browser receives 307/308 redirect
// ✅ Must use absolute URL

Conditional Redirects

middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  // Check if user is authenticated (simplified)
  const isAuthenticated = request.cookies.has('session');
  
  // Redirect unauthenticated users from protected routes
  if (pathname.startsWith('/dashboard') && !isAuthenticated) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  
  // Redirect authenticated users away from login
  if (pathname === '/login' && isAuthenticated) {
    return NextResponse.redirect(new URL('/dashboard', request.url));
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/login'],
};

// ✅ Authentication-based redirects
// ✅ Prevent access to protected routes
// ✅ Redirect logged-in users away from login

Redirect with Query Parameters

middleware.ts
export function middleware(request: NextRequest) {
  const isAuthenticated = request.cookies.has('session');
  
  if (request.nextUrl.pathname.startsWith('/dashboard') && !isAuthenticated) {
    // Create login URL with return path
    const loginUrl = new URL('/login', request.url);
    loginUrl.searchParams.set('returnTo', request.nextUrl.pathname);
    
    return NextResponse.redirect(loginUrl);
  }
  
  return NextResponse.next();
}

// Visit /dashboard/settings (not logged in)
// → Redirects to /login?returnTo=/dashboard/settings

// ✅ Preserve intended destination
// ✅ Redirect back after login
// ✅ Better user experience

Rewrites with Middleware

Basic Rewrite

middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  // Rewrite /blog to /posts
  if (request.nextUrl.pathname === '/blog') {
    return NextResponse.rewrite(new URL('/posts', request.url));
  }
  
  return NextResponse.next();
}

// Visit /blog → Shows content from /posts
// ✅ URL stays /blog in address bar
// ✅ Content served from /posts
// ✅ Invisible to user

A/B Testing with Rewrites

middleware.ts
export function middleware(request: NextRequest) {
  // A/B test - 50% of users see variant
  const bucket = request.cookies.get('bucket')?.value;
  
  if (!bucket) {
    // Randomly assign bucket
    const newBucket = Math.random() < 0.5 ? 'a' : 'b';
    const response = NextResponse.next();
    response.cookies.set('bucket', newBucket, {
      maxAge: 60 * 60 * 24 * 30, // 30 days
    });
    return response;
  }
  
  // Rewrite based on bucket
  if (request.nextUrl.pathname === '/') {
    if (bucket === 'b') {
      return NextResponse.rewrite(new URL('/variant-b', request.url));
    }
  }
  
  return NextResponse.next();
}

// ✅ 50% see original
// ✅ 50% see variant
// ✅ Consistent per user (cookie)
// ✅ URL unchanged

Locale-Based Rewrites

middleware.ts
export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  // Get locale from cookie or header
  const locale = request.cookies.get('locale')?.value || 
                 request.headers.get('accept-language')?.split(',')[0] || 
                 'en';
  
  // Rewrite to locale-specific version
  if (pathname === '/') {
    return NextResponse.rewrite(new URL(`/${locale}`, request.url));
  }
  
  // Add locale header for pages to use
  const response = NextResponse.next();
  response.headers.set('x-locale', locale);
  
  return response;
}

// Visit / → Serves content from /en (or user's locale)
// ✅ Automatic locale routing
// ✅ URL stays simple
// ✅ Locale in header for pages

Modifying Headers

Adding Request Headers

middleware.ts
export function middleware(request: NextRequest) {
  // Clone request headers
  const requestHeaders = new Headers(request.headers);
  
  // Add custom headers
  requestHeaders.set('x-user-id', 'user-123');
  requestHeaders.set('x-request-time', new Date().toISOString());
  
  // Continue with modified headers
  const response = NextResponse.next({
    request: {
      headers: requestHeaders,
    },
  });
  
  return response;
}

// Pages/API routes can read these headers:
// const userId = headers().get('x-user-id');

// ✅ Add headers to request
// ✅ Available in pages/routes
// ✅ Pass data to downstream handlers

Adding Response Headers

middleware.ts
export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  
  // Security headers
  response.headers.set('X-Frame-Options', 'DENY');
  response.headers.set('X-Content-Type-Options', 'nosniff');
  response.headers.set('Referrer-Policy', 'origin-when-cross-origin');
  response.headers.set(
    'Permissions-Policy',
    'camera=(), microphone=(), geolocation=()'
  );
  
  // CORS headers
  response.headers.set('Access-Control-Allow-Origin', '*');
  response.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
  
  // Custom headers
  response.headers.set('X-Custom-Header', 'value');
  
  return response;
}

// ✅ Security headers
// ✅ CORS configuration
// ✅ Custom headers
// ✅ Applied to all responses

Content Security Policy

middleware.ts
export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  
  // Content Security Policy
  const csp = [
    "default-src 'self'",
    "script-src 'self' 'unsafe-inline' 'unsafe-eval'",
    "style-src 'self' 'unsafe-inline'",
    "img-src 'self' data: https:",
    "font-src 'self' data:",
    "connect-src 'self' https://api.example.com",
  ].join('; ');
  
  response.headers.set('Content-Security-Policy', csp);
  
  return response;
}

// ✅ CSP for security
// ✅ Prevent XSS attacks
// ✅ Control resource loading

Working with Cookies

Reading Cookies

middleware.ts
export function middleware(request: NextRequest) {
  // Get single cookie
  const session = request.cookies.get('session');
  console.log('Session:', session?.value);
  
  // Get all cookies
  const allCookies = request.cookies.getAll();
  console.log('All cookies:', allCookies);
  
  // Check if cookie exists
  const hasSession = request.cookies.has('session');
  
  if (!hasSession) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  
  return NextResponse.next();
}

// ✅ request.cookies.get(name)
// ✅ request.cookies.getAll()
// ✅ request.cookies.has(name)

Setting Cookies

middleware.ts
export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  
  // Set cookie
  response.cookies.set('visited', 'true', {
    maxAge: 60 * 60 * 24 * 365, // 1 year
    httpOnly: true,
    secure: process.env.NODE_ENV === 'production',
    sameSite: 'lax',
    path: '/',
  });
  
  // Set multiple cookies
  response.cookies.set('theme', 'dark');
  response.cookies.set('locale', 'en');
  
  return response;
}

// ✅ response.cookies.set(name, value, options)
// ✅ Cookie options for security
// ✅ Multiple cookies supported

Deleting Cookies

middleware.ts
export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  
  // Delete cookie
  response.cookies.delete('session');
  
  // OR set to expire immediately
  response.cookies.set('session', '', {
    maxAge: 0,
    path: '/',
  });
  
  return response;
}

// ✅ response.cookies.delete(name)
// ✅ Or set maxAge: 0

Middleware File Location

Where to place middleware.ts

project-rootImportant
middleware.tsImportant
app
src

Select a file or folder to see details

Practical Middleware Examples

Example 1: Simple Authentication Check

middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;
  
  // Protected routes
  const protectedRoutes = ['/dashboard', '/profile', '/settings'];
  const isProtectedRoute = protectedRoutes.some(route => 
    pathname.startsWith(route)
  );
  
  if (isProtectedRoute) {
    const session = request.cookies.get('session');
    
    if (!session) {
      // Not authenticated - redirect to login
      const loginUrl = new URL('/login', request.url);
      loginUrl.searchParams.set('returnTo', pathname);
      return NextResponse.redirect(loginUrl);
    }
  }
  
  return NextResponse.next();
}

export const config = {
  matcher: ['/dashboard/:path*', '/profile/:path*', '/settings/:path*'],
};

// ✅ Check session cookie
// ✅ Redirect if not authenticated
// ✅ Preserve return URL

Example 2: Logging and Analytics

middleware.ts
export function middleware(request: NextRequest) {
  const start = Date.now();
  
  // Log request
  console.log('Request:', {
    method: request.method,
    url: request.url,
    userAgent: request.headers.get('user-agent'),
    timestamp: new Date().toISOString(),
  });
  
  const response = NextResponse.next();
  
  // Log response time
  const duration = Date.now() - start;
  response.headers.set('X-Response-Time', `${duration}ms`);
  
  console.log('Response:', {
    url: request.url,
    duration: `${duration}ms`,
  });
  
  return response;
}

// ✅ Track all requests
// ✅ Measure response times
// ✅ Analytics data

Example 3: Mobile Redirect

middleware.ts
export function middleware(request: NextRequest) {
  const userAgent = request.headers.get('user-agent') || '';
  const isMobile = /Mobile|Android|iPhone/i.test(userAgent);
  
  const { pathname } = request.nextUrl;
  
  // Don't redirect if already on mobile subdomain
  if (request.nextUrl.hostname.startsWith('m.')) {
    return NextResponse.next();
  }
  
  // Redirect mobile users to mobile site
  if (isMobile && pathname === '/') {
    const mobileUrl = new URL(request.url);
    mobileUrl.hostname = `m.${mobileUrl.hostname}`;
    return NextResponse.redirect(mobileUrl);
  }
  
  return NextResponse.next();
}

// ✅ Detect mobile devices
// ✅ Redirect to mobile subdomain
// ✅ Prevent redirect loops

Middleware Best Practices

1. Keep Middleware Fast

TYPESCRIPT
// ✅ GOOD: Fast operations only
export function middleware(request: NextRequest) {
  const hasSession = request.cookies.has('session');
  if (!hasSession) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  return NextResponse.next();
}

// ❌ BAD: Slow operations
export function middleware(request: NextRequest) {
  // Don't make API calls!
  await fetch('https://api.example.com/verify');
  
  // Don't query databases!
  await db.users.findUnique({ ... });
  
  // Middleware runs on EVERY request
  // Keep it fast!
}

// Middleware runs on every matched request
// Keep it lightweight and fast

2. Use Matcher to Limit Scope

TYPESCRIPT
// ✅ GOOD: Specific matcher
export const config = {
  matcher: ['/dashboard/:path*', '/admin/:path*'],
};

// ❌ BAD: Too broad (includes static files)
export const config = {
  matcher: '/:path*',
};

// Exclude static assets:
export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico).*)',
  ],
};

// Only run middleware where needed

3. Return Responses Properly

TYPESCRIPT
// ✅ GOOD: Always return NextResponse
export function middleware(request: NextRequest) {
  if (condition) {
    return NextResponse.redirect(url);
  }
  return NextResponse.next(); // Don't forget this!
}

// ❌ BAD: Missing return
export function middleware(request: NextRequest) {
  if (condition) {
    NextResponse.redirect(url); // Missing return!
  }
  // Middleware must return a Response
}

4. Use Absolute URLs

TYPESCRIPT
// ✅ GOOD: Absolute URL
return NextResponse.redirect(new URL('/login', request.url));

// ❌ BAD: Relative path
return NextResponse.redirect('/login'); // Error!

// Always use new URL() with request.url

5. Handle Edge Cases

TYPESCRIPT
// ✅ GOOD: Handle edge cases
export function middleware(request: NextRequest) {
  const session = request.cookies.get('session');
  const { pathname } = request.nextUrl;
  
  // Avoid redirect loops
  if (pathname === '/login') {
    return NextResponse.next();
  }
  
  // Protected routes
  if (pathname.startsWith('/dashboard') && !session) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
  
  return NextResponse.next();
}

// Prevent redirect loops
// Handle all code paths

Key Takeaways

  • middleware.ts in root - runs before every request
  • matcher config - specify which routes to run on
  • NextResponse.next() - continue to page/route
  • NextResponse.redirect() - redirect with absolute URL
  • NextResponse.rewrite() - serve different content
  • Modify headers - request and response headers
  • Read/set cookies - request.cookies and response.cookies
  • Keep it fast - runs on every matched request

What's Next?

You've mastered middleware basics! Next, we'll explore Authentication with Middleware—building complete authentication systems, protecting routes, handling sessions and JWT tokens, implementing role-based access control, and creating production-ready auth flows. You'll secure your entire application!

We'll cover session management, JWT verification, protected routes, role-based permissions, and complete authentication patterns.

⚡ Edge Runtime

Middleware runs on the Edge runtime for incredible performance and global distribution. However, this means you have access only to Web APIs—no Node.js APIs like fs, crypto (use Web Crypto), or database drivers. Keep middleware logic simple and fast!

Test Your Understanding

Question 1 of 4

Where do you create middleware in Next.js?

Master middleware in Next.js! Learn request interception, redirects, and global logic.

Previous
API Error Handling and Status Codes
Next
Authentication with Middleware

Master Next.js Middleware

Join 2,000+ developers building secure Next.js apps. Get the next lesson on authentication with middleware - 100% FREE.

You might also like

UI Components

Ready-made components for your projects

Developer Tools

Boost productivity with handy generators

HTML Game

Learn HTML concepts through interactive gameplay

Join Our Web Development Training Program

Master frontend & backend development with hands-on projects.

NextJS Tutorials

0 of 70 completed

Your Progress0%

Topics

Getting Started

  • What is Next.js?
  • Next.js vs React - Key Differences
  • Creating Your First Next.js 15 Project
  • Next.js Project Structure Explained
  • App Router vs Pages Router

Routing Fundamentals

  • File-Based Routing Basics
  • Creating Pages with page.tsx
  • Dynamic Routes and Route Parameters
  • Catch-All and Optional Catch-All Routes
  • Route Groups for Organization
  • Parallel Routes
  • Intercepting Routes

Layouts and Pages

  • Understanding Layouts
  • Root Layout and Global Configuration
  • Nested Layouts
  • Templates vs Layouts
  • Loading States with loading.tsx
  • Error Handling with error.tsx

Server and Client Components

  • Understanding Server Components
  • Client Components with 'use client'
  • When to Use Server vs Client Components
  • Component Composition Patterns
  • Passing Props Between Server and Client
  • Server Component Patterns and Best Practices

Data Fetching

  • Fetching Data in Server Components
  • Parallel and Sequential Data Fetching
  • Data Fetching Patterns and Strategies
  • Caching and Revalidation
  • Revalidate and Cache Tags
  • Handling Loading and Error States in Data Fetching

Navigation and Links

  • Link Component Basics
  • useRouter Hook for Programmatic Navigation
  • usePathname and useSearchParams Hooks
  • Active Links and Navigation States
  • Redirects and Navigation Guards

Styling in Next.js

  • CSS Modules in Next.js
  • Tailwind CSS Setup and Configuration
  • Global Styles and CSS Variables
  • Next.js Font Optimization

Images and Media

  • Next.js Image Component Basics
  • Image Optimization and Best Practices
  • Working with Static Assets

Forms and Data Mutations

  • Understanding Server Actions
  • Form Handling with Server Actions
  • Form Validation and Error Handling
  • useFormStatus and useFormState Hooks
  • Revalidating Data After Mutations
  • Optimistic Updates

Metadata and SEO

  • Static Metadata Configuration
  • Dynamic Metadata Generation
  • Open Graph and Social Media Cards
  • Sitemap and Robots.txt

API Routes and Route Handlers

  • Route Handlers Introduction
  • GET and POST Request Handlers
  • Dynamic API Routes
  • Request and Response Objects
  • API Error Handling and Status Codes

Middleware and Advanced Features

  • Introduction to Middleware
  • Authentication with Middleware
  • Environment Variables and Configuration
  • Streaming and Suspense
  • Not Found and Global Error Pages

Deployment and Optimization

  • Understanding Static and Dynamic Rendering
  • generateStaticParams for Static Generation
  • Build and Production Optimization
  • Deploying to Vercel
  • Alternative Deployment Options
  • Performance Monitoring and Analytics

Real-World Projects

  • Project 1: E-commerce Product Catalog
  • Project 2: Dashboard with Authentication
Falytom logoFALYTOM

Learn technologies like HTML, CSS, JavaScript, React, NodeJS and more through interactive games, tutorials, components and free tools. Transform your businesses with AI chatbots, SEO-optimized websites, and professional development services.

© 2025 Tomilola Group
  • Facebook logo
  • Twitter logo
  • Instagram logo
  • LinkedIn logo
  • GitHub logo
  • YouTube logo