We use cookies to enhance your experience on the site
CodeWorlds

Middleware and Request Interception

At the heart of the Quantum Metropolis lies an advanced access control and redirect system known as the Quantum Gateway. This system monitors every access attempt to various city zones, verifies permissions, redirects to appropriate points, and modifies travel routes in real time. It's an extremely important infrastructure element that ensures both security and traffic optimization throughout the city.

In the world of Next.js 15, Middleware plays a similar role - a powerful mechanism that allows intercepting and modifying HTTP requests before they are handled by a page or API, enabling the implementation of features like authorization, redirects, or response modifications.

What is Middleware in Next.js 15?

Middleware in Next.js is a function that is executed before a request is handled by a page or API route. It operates between the client request and server response, providing the ability to:

  • Execute code before loading the page
  • Modify the request object
  • Modify the response object
  • Redirect the user
  • Reject requests
  • Add HTTP headers
  • Modify cookies

Basic Middleware Structure

In Next.js 15, middleware is defined in a

middleware.ts
(or
middleware.js
) file in the project root directory (next to
app
or
pages
).

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5// The middleware function is executed before every request
6export function middleware(request: NextRequest) {
7  // Here we can implement our logic
8  console.log('Middleware executed for:', request.nextUrl.pathname);
9
10  // We can modify the response object
11  return NextResponse.next();
12}
13
14// Optionally, we can limit which routes the middleware runs for
15export const config = {
16  matcher: '/api/:path*',
17};

Available Middleware Operations

1. Redirects and Rewrites

Just as in the Quantum Metropolis, where security gates can automatically redirect unauthorized visitors to checkpoints, middleware in Next.js allows redirecting requests:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6  // Check if the user is trying to access the admin zone
7  if (request.nextUrl.pathname.startsWith('/admin')) {
8    // Check if the user is logged in (e.g., by checking a cookie)
9    const isLoggedIn = request.cookies.has('auth-token');
10    
11    if (!isLoggedIn) {
12      // Redirect unauthenticated users to the login page
13      const loginUrl = new URL('/login', request.url);
14      // Add the original URL as a parameter so we can return after login
15      loginUrl.searchParams.set('from', request.nextUrl.pathname);
16      return NextResponse.redirect(loginUrl);
17    }
18  }
19  
20  // Otherwise, continue with normal request processing
21  return NextResponse.next();
22}

2. Modifying Request or Response Headers

We can also modify headers, just as the Quantum Gateway can add special identifiers to transport tickets:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6  // Get the original response
7  const response = NextResponse.next();
8  
9  // Add custom headers to the response
10  response.headers.set('x-quantum-security', 'enabled');
11  response.headers.set('x-quantum-gateway-version', '15.0.1');
12  
13  // Add Content Security Policy (CSP)
14  response.headers.set(
15    'Content-Security-Policy',
16    "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline' quantum-cdn.example.com;"
17  );
18  
19  return response;
20}

3. Manipulating Cookies

Middleware can also manipulate cookies, which is useful for session management:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6  const response = NextResponse.next();
7  
8  // Read cookie value
9  const theme = request.cookies.get('theme')?.value;
10  
11  // Set a new cookie
12  response.cookies.set('last-visit', new Date().toISOString());
13  
14  // Change existing cookie value
15  if (theme === 'dark') {
16    response.cookies.set('theme-version', 'dark-v2');
17  }
18  
19  // Delete cookie
20  if (request.nextUrl.pathname === '/logout') {
21    response.cookies.delete('auth-token');
22  }
23  
24  return response;
25}

4. Rewrite - URL Substitution Without Changing the Visible Address

Rewrite allows internally substituting the URL without changing the address visible to the user:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6  // Example: internally redirect all requests from /docs to /documentation
7  // The address in the browser bar remains /docs
8  if (request.nextUrl.pathname.startsWith('/docs')) {
9    const url = request.nextUrl.clone();
10    url.pathname = url.pathname.replace(/^/docs/, '/documentation');
11    return NextResponse.rewrite(url);
12  }
13  
14  return NextResponse.next();
15}

Implementing Middleware for the Quantum Voyages Application

Let's prepare comprehensive middleware for our Quantum Voyages application that will handle:

  1. Authorization for protected zones
  2. Internationalization and language detection
  3. Tracking and analytics
1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4import { verifyAuthToken } from './lib/auth';
5
6// List of supported languages
7const SUPPORTED_LOCALES = ['pl', 'en', 'de', 'fr'];
8// Default language
9const DEFAULT_LOCALE = 'pl';
10// Paths requiring authorization
11const PROTECTED_PATHS = ['/dashboard', '/account', '/bookings'];
12// Admin paths requiring admin permissions
13const ADMIN_PATHS = ['/admin'];
14
15export async function middleware(request: NextRequest) {
16  const { pathname } = request.nextUrl;
17  
18  // 1. Handle localization and internationalization
19  const pathnameHasLocale = SUPPORTED_LOCALES.some(
20    locale => pathname.startsWith(`/\${locale}/`) || pathname === `/\${locale}`
21  );
22  
23  // If the path doesn't contain a language code yet, redirect and add the code
24  if (!pathnameHasLocale) {
25    // Attempt to detect the user's preferred language
26    const preferredLocale = getPreferredLocale(request);
27    const url = new URL(request.url);
28    url.pathname = `/\${preferredLocale}\${pathname}`;
29    return NextResponse.redirect(url);
30  }
31  
32  // Extract the language code from the path
33  const locale = pathname.split('/')[1];
34  
35  // 2. Authorization for protected zones
36  // Check if the path is protected
37  if (isProtectedPath(pathname)) {
38    const authToken = request.cookies.get('auth-token')?.value;
39    
40    // Verify the authorization token
41    const authResult = await verifyAuthToken(authToken);
42    
43    // If the user is not logged in or the token is invalid
44    if (!authResult.isValid) {
45      const loginUrl = new URL(`/\${locale}/login`, request.url);
46      loginUrl.searchParams.set('returnTo', request.nextUrl.pathname);
47      return NextResponse.redirect(loginUrl);
48    }
49    
50    // Check admin permissions for administrative paths
51    if (isAdminPath(pathname) && !authResult.isAdmin) {
52      const accessDeniedUrl = new URL(`/\${locale}/access-denied`, request.url);
53      return NextResponse.redirect(accessDeniedUrl);
54    }
55  }
56  
57  // 3. Add analytics and security headers
58  const response = NextResponse.next();
59  
60  // Add user info for analytics tools (no personal data)
61  const visitorId = request.cookies.get('visitor-id')?.value || generateVisitorId();
62  response.cookies.set('visitor-id', visitorId, { 
63    httpOnly: true, 
64    sameSite: 'strict',
65    maxAge: 60 * 60 * 24 * 365 // 1 rok
66  });
67  
68  // Security headers
69  response.headers.set('X-Frame-Options', 'DENY');
70  response.headers.set('X-Content-Type-Options', 'nosniff');
71  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
72  
73  return response;
74}
75
76// Helper functions
77
78// Detecting the user's preferred language
79function getPreferredLocale(request: NextRequest): string {
80  // Check previously saved preferred language in cookie
81  const savedLocale = request.cookies.get('preferred-locale')?.value;
82  if (savedLocale && SUPPORTED_LOCALES.includes(savedLocale)) {
83    return savedLocale;
84  }
85  
86  // Check the Accept-Language header
87  const acceptLanguage = request.headers.get('accept-language');
88  if (acceptLanguage) {
89    // Parse Accept-Language header and find the best match
90    const userLocales = acceptLanguage.split(',')
91      .map(item => {
92        const [locale, priority = 'q=1.0'] = item.trim().split(';');
93        const q = parseFloat(priority.replace('q=', '')) || 1.0;
94        return { locale: locale.split('-')[0], q };
95      })
96      .sort((a, b) => b.q - a.q);
97    
98    // Find the first supported language
99    for (const { locale } of userLocales) {
100      if (SUPPORTED_LOCALES.includes(locale)) {
101        return locale;
102      }
103    }
104  }
105  
106  // Default language if preferences cannot be determined
107  return DEFAULT_LOCALE;
108}
109
110// Check if the path is protected
111function isProtectedPath(pathname: string): boolean {
112  return PROTECTED_PATHS.some(path => {
113    // Remove the language code from the path before comparison
114    const pathWithoutLocale = pathname.split('/').slice(2).join('/');
115    return `/\${pathWithoutLocale}`.startsWith(path);
116  });
117}
118
119// Check if the path is administrative
120function isAdminPath(pathname: string): boolean {
121  return ADMIN_PATHS.some(path => {
122    // Remove the language code from the path before comparison
123    const pathWithoutLocale = pathname.split('/').slice(2).join('/');
124    return `/\${pathWithoutLocale}`.startsWith(path);
125  });
126}
127
128// Generate a unique ID for the visitor
129function generateVisitorId(): string {
130  return `\${Date.now()}-\${Math.random().toString(36).substring(2, 15)}`;
131}
132
133// Determine which paths the middleware will run for
134export const config = {
135  matcher: [
136    // Match all paths
137    '/((?!api|_next/static|_next/image|favicon.ico).*)',
138  ],
139};

Selective Middleware Execution with Matcher

Just as in the Quantum Metropolis, where not every movement requires a full security check, in Next.js 15 we can specify which paths the middleware will run for using the

matcher
configuration:

1// middleware.ts
2export const config = {
3  matcher: [
4    // Paths for which the middleware will run
5    '/dashboard/:path*',
6    '/api/:path*',
7    '/((?!_next/static|_next/image|favicon.ico).*)',
8  ],
9};

Various matcher patterns are available:

  1. Exact matching:
    '/about'
  2. Matching with parameters:
    '/blog/:path'
  3. Matching with catch-all parameters:
    '/blog/:path*'
  4. Group matching:
    /dashboard/:path*|/admin/:path*
  5. Negation matching:
    /((?!api|_next/static|_next/image|favicon.ico).*)

Implementing JWT Authorization with Middleware

In the Quantum Metropolis, resident identifiers are verified using advanced cryptographic technology. Let's implement a similar JWT authorization system in our application:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4import { jwtVerify } from 'jose';
5
6interface UserPayload {
7  id: string;
8  email: string;
9  role: string;
10  exp: number;
11}
12
13export async function middleware(request: NextRequest) {
14  // Check if the path requires authorization
15  if (request.nextUrl.pathname.startsWith('/dashboard') || 
16      request.nextUrl.pathname.startsWith('/api/protected')) {
17    
18    // Get JWT token from cookie or Authorization header
19    const authToken = request.cookies.get('auth-token')?.value || 
20                     request.headers.get('Authorization')?.split(' ')[1];
21    
22    if (!authToken) {
23      // No token - redirect to login
24      return redirectToLogin(request);
25    }
26    
27    try {
28      // Verify JWT token
29      const secretKey = new TextEncoder().encode(process.env.JWT_SECRET!);
30      const { payload } = await jwtVerify<UserPayload>(authToken, secretKey);
31      
32      // Check if the token has expired
33      const currentTime = Math.floor(Date.now() / 1000);
34      if (payload.exp < currentTime) {
35        // Token expired - redirect to login
36        return redirectToLogin(request);
37      }
38      
39      // Check admin permissions for administrative paths
40      if (request.nextUrl.pathname.startsWith('/admin') && payload.role !== 'admin') {
41        return NextResponse.json(
42          { message: 'Insufficient permissions' },
43          { status: 403 }
44        );
45      }
46      
47      // Add user info to headers for subsequent handlers
48      const response = NextResponse.next();
49      response.headers.set('x-user-id', payload.id);
50      response.headers.set('x-user-role', payload.role);
51      
52      return response;
53    } catch (error) {
54      // Token verification error - redirect to login
55      return redirectToLogin(request);
56    }
57  }
58  
59  return NextResponse.next();
60}
61
62// Function that redirects to login
63function redirectToLogin(request: NextRequest) {
64  const loginUrl = new URL('/login', request.url);
65  loginUrl.searchParams.set('returnTo', request.nextUrl.pathname);
66  return NextResponse.redirect(loginUrl);
67}
68
69export const config = {
70  matcher: [
71    '/dashboard/:path*',
72    '/admin/:path*',
73    '/api/protected/:path*',
74  ],
75};

Implementing Theme Switching with Middleware

Quantum City offers its residents the ability to customize the appearance of city interfaces. Let's implement a similar system for switching between light and dark themes:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6  // Get preferred theme
7  const theme = request.cookies.get('theme')?.value || 'light';
8  
9  // Create response
10  const response = NextResponse.next();
11  
12  // Set theme preference variable as a cookie
13  response.cookies.set('theme', theme, {
14    maxAge: 60 * 60 * 24 * 365, // valid for one year
15    path: '/',
16  });
17  
18  // Add a custom header that can be read by the client
19  response.headers.set('x-theme', theme);
20  
21  return response;
22}
23
24export const config = {
25  matcher: '/((?!api|_next/static|_next/image|favicon.ico).*)',
26};

Handling Multiple Domains and Subdomains with Middleware

In the Quantum Metropolis, different districts can have their own access systems. Similarly in Next.js, we can handle different domains and subdomains:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6  const { pathname, hostname } = request.nextUrl;
7  
8  // Handle different subdomains
9  if (hostname === 'admin.quantumvoyages.com') {
10    // Redirect all requests to the admin panel
11    const url = new URL('/admin' + pathname, request.url);
12    return NextResponse.rewrite(url);
13  }
14  
15  if (hostname === 'api.quantumvoyages.com') {
16    // Redirect requests to the API
17    const url = new URL('/api' + pathname, request.url);
18    return NextResponse.rewrite(url);
19  }
20  
21  if (hostname.startsWith('user-')) {
22    // Handle user subdomains (e.g., user-john.quantumvoyages.com)
23    const username = hostname.replace('user-', '').split('.')[0];
24    const url = new URL(`/profile/\${username}\${pathname}`, request.url);
25    return NextResponse.rewrite(url);
26  }
27  
28  return NextResponse.next();
29}
30
31export const config = {
32  matcher: '/((?!_next/static|_next/image|favicon.ico).*)',
33};

Caching and Response Header Modification

In the Quantum Metropolis, the Gateway system optimizes data flow through its systems. Similarly, we can optimize our application:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6  const response = NextResponse.next();
7  
8  // Set caching headers for static assets
9  if (request.nextUrl.pathname.match(/\.(jpg|jpeg|png|webp|svg|gif|ico)$/)) {
10    response.headers.set('Cache-Control', 'public, max-age=31536000, immutable');
11  }
12  
13  // Set caching headers for HTML pages
14  else if (!request.nextUrl.pathname.startsWith('/api/')) {
15    response.headers.set('Cache-Control', 'public, max-age=300, s-maxage=3600');
16  }
17  
18  // Add security headers
19  response.headers.set('Strict-Transport-Security', 'max-age=63072000');
20  response.headers.set('X-Content-Type-Options', 'nosniff');
21  response.headers.set('X-Frame-Options', 'DENY');
22  response.headers.set('X-XSS-Protection', '1; mode=block');
23  
24  return response;
25}
26
27export const config = {
28  matcher: '/((?!_next/static|_next/image|favicon.ico).*)',
29};

Implementing Geolocation and Content Personalization

The Quantum Metropolis adjusts available services based on resident locations. Let's implement a similar system in our application, using middleware to determine user location:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5// Table of regions and their available destinations
6const REGION_DESTINATIONS = {
7  'EU': ['mars', 'europa', 'titan'],
8  'NA': ['mars', 'luna', 'titan', 'ganymede'],
9  'ASIA': ['mars', 'venus-orbiter', 'luna'],
10  'default': ['mars'] // default destinations available globally
11};
12
13export function middleware(request: NextRequest) {
14  // Get user's region (in reality, you could use an external geolocation API)
15  const countryCode = request.headers.get('x-country') || 'default';
16  const region = getRegionFromCountry(countryCode);
17  
18  // Create response
19  const response = NextResponse.next();
20  
21  // Add region info and available destinations as headers
22  response.headers.set('x-user-region', region);
23  response.headers.set('x-available-destinations', JSON.stringify(REGION_DESTINATIONS[region] || REGION_DESTINATIONS.default));
24  
25  // Set region cookie (will be available in the browser)
26  response.cookies.set('user-region', region, {
27    maxAge: 60 * 60 * 24 * 30, // 30 dni
28    path: '/',
29  });
30  
31  return response;
32}
33
34// Function mapping country codes to regions
35function getRegionFromCountry(countryCode: string): keyof typeof REGION_DESTINATIONS {
36  const EU_COUNTRIES = ['DE', 'FR', 'IT', 'ES', 'PL', 'NL', 'BE', 'SE', 'AT', 'DK'];
37  const NA_COUNTRIES = ['US', 'CA', 'MX'];
38  const ASIA_COUNTRIES = ['JP', 'CN', 'KR', 'IN', 'SG', 'TH', 'MY', 'PH', 'VN'];
39  
40  if (EU_COUNTRIES.includes(countryCode)) return 'EU';
41  if (NA_COUNTRIES.includes(countryCode)) return 'NA';
42  if (ASIA_COUNTRIES.includes(countryCode)) return 'ASIA';
43  
44  return 'default';
45}
46
47export const config = {
48  matcher: '/((?!_next/static|_next/image|favicon.ico).*)',
49};

Request Rate Limiting

In the Quantum Metropolis, the Gateway system controls the number of requests to critical resources to prevent overload. Let's implement a similar rate limiting mechanism:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5// Simple memory-based rate limiting system (in production, better to use Redis or a similar service)
6const RATE_LIMIT_DURATION = 60 * 1000; // 1 minuta
7const MAX_REQUESTS_PER_MINUTE = 60; // Maximum number of requests per minute
8
9// Storing request counters for different IPs (in a real application, better to use Redis)
10const ipRequestCounts = new Map<string, { count: number, resetAt: number }>();
11
12export function middleware(request: NextRequest) {
13  // We only limit API requests
14  if (request.nextUrl.pathname.startsWith('/api/')) {
15    const ip = request.ip || 'unknown';
16    const now = Date.now();
17    
18    // Get or create a counter for this IP
19    if (!ipRequestCounts.has(ip) || ipRequestCounts.get(ip)!.resetAt < now) {
20      // Reset the counter if time has elapsed
21      ipRequestCounts.set(ip, { count: 1, resetAt: now + RATE_LIMIT_DURATION });
22    } else {
23      // Increment counter
24      const record = ipRequestCounts.get(ip)!;
25      record.count++;
26      
27      // Check if the limit has been exceeded
28      if (record.count > MAX_REQUESTS_PER_MINUTE) {
29        // Calculate the time after which requests can be sent again
30        const resetTime = Math.ceil((record.resetAt - now) / 1000);
31        
32        // Return a response with 429 Too Many Requests code
33        return new NextResponse('Rate limit exceeded', {
34          status: 429,
35          headers: {
36            'Retry-After': `\${resetTime}`,
37            'X-RateLimit-Limit': `\${MAX_REQUESTS_PER_MINUTE}`,
38            'X-RateLimit-Remaining': '0',
39            'X-RateLimit-Reset': `\${resetTime}`,
40          },
41        });
42      }
43    }
44  }
45  
46  return NextResponse.next();
47}
48
49export const config = {
50  matcher: '/api/:path*',
51};

A/B Testing with Middleware

In the Quantum Metropolis, some new features are tested on selected groups of residents. Let's implement a similar A/B testing system:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5// A/B test definitions
6const AB_TESTS = {
7  'new-landing-page': {
8    variants: ['control', 'variant-a', 'variant-b'],
9    weights: [0.5, 0.25, 0.25], // 50% of users will see control, 25% variant-a, 25% variant-b
10  },
11  'checkout-flow': {
12    variants: ['standard', 'simplified'],
13    weights: [0.5, 0.5], // 50% of users will see each variant
14  },
15};
16
17export function middleware(request: NextRequest) {
18  const response = NextResponse.next();
19  
20  // Assign the user to test groups
21  Object.entries(AB_TESTS).forEach(([testName, test]) => {
22    // Get existing variant from cookie or assign a new one
23    let variant = request.cookies.get(`ab-\${testName}`)?.value;
24    
25    if (!variant || !test.variants.includes(variant)) {
26      // Assign variant based on weights
27      variant = assignVariant(test.variants, test.weights);
28      
29      // Save the variant in a cookie
30      response.cookies.set(`ab-\${testName}`, variant, {
31        maxAge: 60 * 60 * 24 * 30, // 30 dni
32        path: '/',
33      });
34    }
35    
36    // Add variant info as a header for the client
37    response.headers.set(`x-ab-\${testName}`, variant);
38  });
39  
40  return response;
41}
42
43// Function assigning a variant based on weights
44function assignVariant(variants: string[], weights: number[]): string {
45  const random = Math.random();
46  let cumulativeWeight = 0;
47  
48  for (let i = 0; i < variants.length; i++) {
49    cumulativeWeight += weights[i];
50    if (random < cumulativeWeight) {
51      return variants[i];
52    }
53  }
54  
55  // Default to the first variant
56  return variants[0];
57}
58
59export const config = {
60  matcher: '/((?!api|_next/static|_next/image|favicon.ico).*)',
61};

Monitoring and Debugging Middleware

In production applications, it's important to monitor middleware behavior. We can implement a simple logging system:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6  const startTime = Date.now();
7  
8  // Create a request identifier
9  const requestId = generateRequestId();
10  
11  // Create response
12  const response = NextResponse.next();
13  
14  // Add request identifier to headers
15  response.headers.set('x-request-id', requestId);
16  
17  // Calculate execution time
18  const duration = Date.now() - startTime;
19  response.headers.set('x-response-time', `\${duration}ms`);
20  
21  // In production, you could send this data to a monitoring system
22  // logRequest(requestId, request.method, request.url, response.status, duration);
23  
24  return response;
25}
26
27// Generating a unique request identifier
28function generateRequestId(): string {
29  return `\${Date.now()}-\${Math.random().toString(36).substring(2, 15)}`;
30}
31
32export const config = {
33  matcher: '/((?!_next/static|_next/image|favicon.ico).*)',
34};

Integration with External Systems

Middleware can also integrate with external systems, such as authentication or analytics systems:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export async function middleware(request: NextRequest) {
6  // Get session info from an external service
7  const sessionId = request.cookies.get('session-id')?.value;
8  let userInfo = null;
9  
10  if (sessionId) {
11    try {
12      // In a real application, this could be an API call
13      userInfo = await fetchUserInfo(sessionId);
14    } catch (error) {
15      console.error('Error fetching user info:', error);
16    }
17  }
18  
19  // Create response
20  const response = NextResponse.next();
21  
22  // If we have user info, add it to headers
23  if (userInfo) {
24    response.headers.set('x-user-id', userInfo.id);
25    response.headers.set('x-user-plan', userInfo.plan);
26    
27    // Send data to the analytics system
28    trackUserActivity(userInfo.id, request.url, request.method);
29  }
30  
31  return response;
32}
33
34// Function fetching user info (simulation)
35async function fetchUserInfo(sessionId: string) {
36  // In a real application, this could be an API call
37  return new Promise(resolve => {
38    setTimeout(() => {
39      resolve({
40        id: 'user-123',
41        plan: 'premium',
42        name: 'John Doe'
43      });
44    }, 50);
45  });
46}
47
48// Function tracking user activity (simulation)
49function trackUserActivity(userId: string, url: string, method: string) {
50  // In a real application, this could send data to Google Analytics, Mixpanel, etc.
51  console.log(`User \${userId} accessed \${url} with method \${method}`);
52}
53
54export const config = {
55  matcher: '/((?!_next/static|_next/image|favicon.ico).*)',
56};

Summary

Middleware in Next.js 15 is a powerful tool that, like the Quantum Gateway in the Quantum Metropolis, enables controlling and modifying traffic in the application at the very beginning of the request-response cycle. Thanks to this, we can:

  1. Implement advanced authorization systems - verify JWT tokens, redirect unauthenticated users, control access to protected resources.

  2. Add internationalization features - detect the user's preferred language, redirect to appropriate language versions.

  3. Personalize user experience - adjust content based on location, preferences, and user history.

  4. Optimize performance - control caching headers, modify responses, implement rate limiting.

  5. Integrate with external systems - connect with analytics, authentication, and other services.

  6. Conduct A/B tests - assign users to test groups and personalize the experience.

  7. Monitor and debug - track requests, measure performance, record metrics.

By leveraging middleware, we can significantly extend the capabilities of our Next.js 15 application, adding advanced features that are executed early in the request lifecycle, allowing for greater control and better performance.

In the next chapter, we'll explore form handling and data validation techniques in Next.js 15, which will allow us to create interactive and user-friendly data collection interfaces.

Go to CodeWorlds