W sercu Metropolii Quantum znajduje się zaawansowany system kontroli dostępu i przekierowań, znany jako Quantum Gateway. Ten system monitoruje każdą próbę dostępu do różnych stref miasta, weryfikuje uprawnienia, przekierowuje do odpowiednich punktów i modyfikuje trasy podróży w czasie rzeczywistym. To niezwykle ważny element infrastruktury, który zapewnia zarówno bezpieczeństwo, jak i optymalizację ruchu w całym mieście.
W świecie Next.js 15, podobną rolę odgrywa Middleware - potężny mechanizm, który pozwala na przechwytywanie i modyfikowanie żądań HTTP zanim zostaną one obsłużone przez stronę lub API, co umożliwia implementację takich funkcji jak autoryzacja, przekierowania, czy modyfikacje odpowiedzi.
Middleware w Next.js to funkcja, która jest wykonywana przed obsługą żądania przez stronę lub API route. Działa ona między żądaniem klienta a odpowiedzią serwera, co daje możliwość:
W Next.js 15, middleware jest definiowane w pliku
middleware.ts (lub middleware.js) w katalogu głównym projektu (obok app lub pages).1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5// Funkcja middleware jest wykonywana przed każdym żądaniem
6export function middleware(request: NextRequest) {
7 // Tutaj możemy zaimplementować naszą logikę
8 console.log('Middleware uruchomiony dla:', request.nextUrl.pathname);
9
10 // Możemy zmodyfikować obiekt odpowiedzi
11 return NextResponse.next();
12}
13
14// Opcjonalnie możemy ograniczyć, dla których tras middleware będzie uruchamiane
15export const config = {
16 matcher: '/api/:path*',
17};Podobnie jak w Metropolii Quantum, gdzie bramy bezpieczeństwa mogą automatycznie przekierować nieautoryzowanych gości do punktów kontrolnych, middleware w Next.js pozwala na przekierowanie żądań:
1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6 // Sprawdzenie, czy użytkownik próbuje dostać się do strefy administracyjnej
7 if (request.nextUrl.pathname.startsWith('/admin')) {
8 // Sprawdzamy, czy użytkownik jest zalogowany (np. sprawdzając cookie)
9 const isLoggedIn = request.cookies.has('auth-token');
10
11 if (!isLoggedIn) {
12 // Przekierowanie niezalogowanych użytkowników do strony logowania
13 const loginUrl = new URL('/login', request.url);
14 // Dodajemy oryginalny URL jako parametr, aby móc wrócić po zalogowaniu
15 loginUrl.searchParams.set('from', request.nextUrl.pathname);
16 return NextResponse.redirect(loginUrl);
17 }
18 }
19
20 // W przeciwnym razie kontynuujemy normalne przetwarzanie żądania
21 return NextResponse.next();
22}Możemy również modyfikować nagłówki, podobnie jak Quantum Gateway może dodawać specjalne identyfikatory do biletów transportowych:
1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6 // Pobieramy oryginalną odpowiedź
7 const response = NextResponse.next();
8
9 // Dodajemy własne nagłówki do odpowiedzi
10 response.headers.set('x-quantum-security', 'enabled');
11 response.headers.set('x-quantum-gateway-version', '15.0.1');
12
13 // Dodanie polityki bezpieczeństwa treści (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}Middleware może również manipulować cookies, co przydaje się przy zarządzaniu sesjami:
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 // Odczyt wartości cookie
9 const theme = request.cookies.get('theme')?.value;
10
11 // Ustawienie nowego cookie
12 response.cookies.set('last-visit', new Date().toISOString());
13
14 // Zmiana wartości istniejącego cookie
15 if (theme === 'dark') {
16 response.cookies.set('theme-version', 'dark-v2');
17 }
18
19 // Usunięcie cookie
20 if (request.nextUrl.pathname === '/logout') {
21 response.cookies.delete('auth-token');
22 }
23
24 return response;
25}Rewrite pozwala na podmianę adresu URL wewnętrznie, bez zmiany adresu widocznego dla użytkownika:
1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6 // Przykład: wewnętrzne przekierowanie wszystkich żądań z /docs do /documentation
7 // Adres w pasku przeglądarki pozostaje /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}Przygotujmy kompleksowe middleware dla naszej aplikacji Quantum Voyages, które będzie obsługiwać:
1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4import { verifyAuthToken } from './lib/auth';
5
6// Lista obsługiwanych języków
7const SUPPORTED_LOCALES = ['pl', 'en', 'de', 'fr'];
8// Domyślny język
9const DEFAULT_LOCALE = 'pl';
10// Ścieżki wymagające autoryzacji
11const PROTECTED_PATHS = ['/dashboard', '/account', '/bookings'];
12// Ścieżki administracyjne wymagające uprawnień admina
13const ADMIN_PATHS = ['/admin'];
14
15export async function middleware(request: NextRequest) {
16 const { pathname } = request.nextUrl;
17
18 // 1. Obsługa lokalizacji i internacjonalizacji
19 const pathnameHasLocale = SUPPORTED_LOCALES.some(
20 locale => pathname.startsWith(`/\${locale}/`) || pathname === `/\${locale}`
21 );
22
23 // Jeśli ścieżka nie zawiera jeszcze kodu języka, przekieruj i dodaj kod
24 if (!pathnameHasLocale) {
25 // Próba wykrycia preferowanego języka użytkownika
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 // Wyodrębnienie kodu języka z ścieżki
33 const locale = pathname.split('/')[1];
34
35 // 2. Autoryzacja dla stref chronionych
36 // Sprawdzenie, czy ścieżka jest chroniona
37 if (isProtectedPath(pathname)) {
38 const authToken = request.cookies.get('auth-token')?.value;
39
40 // Weryfikacja tokenu autoryzacji
41 const authResult = await verifyAuthToken(authToken);
42
43 // Jeśli użytkownik nie jest zalogowany lub token jest nieprawidłowy
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 // Sprawdzenie uprawnień admina dla ścieżek administracyjnych
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. Dodanie nagłówków analityki i bezpieczeństwa
58 const response = NextResponse.next();
59
60 // Dodanie informacji o użytkowniku dla narzędzi analitycznych (nie zawiera danych osobowych)
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 // Nagłówki bezpieczeństwa
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// Funkcje pomocnicze
77
78// Wykrywanie preferowanego języka użytkownika
79function getPreferredLocale(request: NextRequest): string {
80 // Sprawdzenie zapisanego wcześniej preferowanego języka w cookie
81 const savedLocale = request.cookies.get('preferred-locale')?.value;
82 if (savedLocale && SUPPORTED_LOCALES.includes(savedLocale)) {
83 return savedLocale;
84 }
85
86 // Sprawdzenie nagłówka Accept-Language
87 const acceptLanguage = request.headers.get('accept-language');
88 if (acceptLanguage) {
89 // Analiza nagłówka Accept-Language i wyszukanie najlepszego dopasowania
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 // Znajdź pierwszy obsługiwany język
99 for (const { locale } of userLocales) {
100 if (SUPPORTED_LOCALES.includes(locale)) {
101 return locale;
102 }
103 }
104 }
105
106 // Domyślny język, jeśli nie można określić preferencji
107 return DEFAULT_LOCALE;
108}
109
110// Sprawdzenie, czy ścieżka jest chroniona
111function isProtectedPath(pathname: string): boolean {
112 return PROTECTED_PATHS.some(path => {
113 // Usunięcie kodu języka z ścieżki przed porównaniem
114 const pathWithoutLocale = pathname.split('/').slice(2).join('/');
115 return `/\${pathWithoutLocale}`.startsWith(path);
116 });
117}
118
119// Sprawdzenie, czy ścieżka jest administracyjna
120function isAdminPath(pathname: string): boolean {
121 return ADMIN_PATHS.some(path => {
122 // Usunięcie kodu języka z ścieżki przed porównaniem
123 const pathWithoutLocale = pathname.split('/').slice(2).join('/');
124 return `/\${pathWithoutLocale}`.startsWith(path);
125 });
126}
127
128// Generowanie unikalnego ID dla odwiedzającego
129function generateVisitorId(): string {
130 return `\${Date.now()}-\${Math.random().toString(36).substring(2, 15)}`;
131}
132
133// Określenie, dla których ścieżek middleware będzie uruchamiane
134export const config = {
135 matcher: [
136 // Dopasowanie wszystkich ścieżek
137 '/((?!api|_next/static|_next/image|favicon.ico).*)',
138 ],
139};Podobnie jak w Metropolii Quantum, gdzie nie każde przemieszczenie wymaga pełnej kontroli bezpieczeństwa, tak w Next.js 15 możemy określić, dla których ścieżek middleware będzie uruchamiane, używając konfiguracji
matcher:1// middleware.ts
2export const config = {
3 matcher: [
4 // Ścieżki, dla których middleware będzie uruchamiane
5 '/dashboard/:path*',
6 '/api/:path*',
7 '/((?!_next/static|_next/image|favicon.ico).*)',
8 ],
9};Dostępne są różne wzorce matcherów:
'/about''/blog/:path''/blog/:path*'/dashboard/:path*|/admin/:path*/((?!api|_next/static|_next/image|favicon.ico).*)W Metropolii Quantum, identyfikatory mieszkańców są weryfikowane przy użyciu zaawansowanej technologii kryptograficznej. Zaimplementujmy podobny system autoryzacji JWT w naszej aplikacji:
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 // Sprawdź, czy ścieżka wymaga autoryzacji
15 if (request.nextUrl.pathname.startsWith('/dashboard') ||
16 request.nextUrl.pathname.startsWith('/api/protected')) {
17
18 // Pobierz token JWT z cookie lub nagłówka Authorization
19 const authToken = request.cookies.get('auth-token')?.value ||
20 request.headers.get('Authorization')?.split(' ')[1];
21
22 if (!authToken) {
23 // Brak tokenu - przekierowanie do logowania
24 return redirectToLogin(request);
25 }
26
27 try {
28 // Weryfikacja tokenu JWT
29 const secretKey = new TextEncoder().encode(process.env.JWT_SECRET!);
30 const { payload } = await jwtVerify<UserPayload>(authToken, secretKey);
31
32 // Sprawdzenie, czy token nie wygasł
33 const currentTime = Math.floor(Date.now() / 1000);
34 if (payload.exp < currentTime) {
35 // Token wygasł - przekierowanie do logowania
36 return redirectToLogin(request);
37 }
38
39 // Sprawdzenie uprawnień admina dla ścieżek administracyjnych
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 // Dodanie informacji o użytkowniku do nagłówków dla kolejnych obsług
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 // Błąd weryfikacji tokenu - przekierowanie do logowania
55 return redirectToLogin(request);
56 }
57 }
58
59 return NextResponse.next();
60}
61
62// Funkcja przekierowująca do logowania
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};Quantum City oferuje swoim mieszkańcom możliwość dostosowania wyglądu interfejsów miejskich. Zaimplementujmy podobny system przełączania między jasnymi i ciemnymi motywami:
1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export function middleware(request: NextRequest) {
6 // Pobierz preferowany motyw
7 const theme = request.cookies.get('theme')?.value || 'light';
8
9 // Utwórz odpowiedź
10 const response = NextResponse.next();
11
12 // Ustaw zmienną preferencji motywu jako cookie
13 response.cookies.set('theme', theme, {
14 maxAge: 60 * 60 * 24 * 365, // ważne przez rok
15 path: '/',
16 });
17
18 // Dodaj niestandardowy nagłówek, który może być odczytany przez klienta
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};W Metropolii Quantum, różne dzielnice mogą mieć swoje własne systemy dostępu. Podobnie w Next.js możemy obsługiwać różne domeny i subdomeny:
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 // Obsługa różnych subdomen
9 if (hostname === 'admin.quantumvoyages.com') {
10 // Przekierowanie wszystkich żądań do panelu administracyjnego
11 const url = new URL('/admin' + pathname, request.url);
12 return NextResponse.rewrite(url);
13 }
14
15 if (hostname === 'api.quantumvoyages.com') {
16 // Przekierowanie żądań do API
17 const url = new URL('/api' + pathname, request.url);
18 return NextResponse.rewrite(url);
19 }
20
21 if (hostname.startsWith('user-')) {
22 // Obsługa subdomen użytkowników (np. 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};W Metropolii Quantum, system Gateway optymalizuje przepływ danych przez swoje systemy. Podobnie możemy zoptymalizować naszą aplikację:
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 // Ustawienie nagłówków cachowania dla zasobów statycznych
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 // Ustawienie nagłówków cachowania dla stron HTML
14 else if (!request.nextUrl.pathname.startsWith('/api/')) {
15 response.headers.set('Cache-Control', 'public, max-age=300, s-maxage=3600');
16 }
17
18 // Dodanie nagłówków bezpieczeństwa
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};Metropolia Quantum dostosowuje dostępne usługi w zależności od położenia mieszkańców. Zaimplementujmy podobny system w naszej aplikacji, używając middleware do określenia lokalizacji użytkownika:
1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5// Tabela regionów i ich dostępnych destynacji
6const REGION_DESTINATIONS = {
7 'EU': ['mars', 'europa', 'titan'],
8 'NA': ['mars', 'luna', 'titan', 'ganymede'],
9 'ASIA': ['mars', 'venus-orbiter', 'luna'],
10 'default': ['mars'] // domyślne destynacje dostępne globalnie
11};
12
13export function middleware(request: NextRequest) {
14 // Pobierz region użytkownika (w rzeczywistości można użyć zewnętrznego API do geolokalizacji)
15 const countryCode = request.headers.get('x-country') || 'default';
16 const region = getRegionFromCountry(countryCode);
17
18 // Utwórz odpowiedź
19 const response = NextResponse.next();
20
21 // Dodanie informacji o regionie i dostępnych destynacjach jako nagłówki
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 // Ustawienie cookie z regionem (będzie dostępne w przeglądarce)
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// Funkcja mapująca kody krajów na regiony
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};W Metropolii Quantum, system Gateway kontroluje ilość zapytań do krytycznych zasobów, aby zapobiec przeciążeniu. Zaimplementujmy podobny mechanizm rate limitingu:
1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5// Prosty system rate limitingu oparty na pamięci (w produkcji lepiej użyć Redis lub podobnej usługi)
6const RATE_LIMIT_DURATION = 60 * 1000; // 1 minuta
7const MAX_REQUESTS_PER_MINUTE = 60; // Maksymalna liczba żądań na minutę
8
9// Przechowywanie liczników żądań dla różnych IP (w rzeczywistej aplikacji lepiej użyć Redis)
10const ipRequestCounts = new Map<string, { count: number, resetAt: number }>();
11
12export function middleware(request: NextRequest) {
13 // Ograniczamy tylko żądania API
14 if (request.nextUrl.pathname.startsWith('/api/')) {
15 const ip = request.ip || 'unknown';
16 const now = Date.now();
17
18 // Pobierz lub utwórz licznik dla tego IP
19 if (!ipRequestCounts.has(ip) || ipRequestCounts.get(ip)!.resetAt < now) {
20 // Zresetuj licznik, jeśli upłynął czas
21 ipRequestCounts.set(ip, { count: 1, resetAt: now + RATE_LIMIT_DURATION });
22 } else {
23 // Zwiększ licznik
24 const record = ipRequestCounts.get(ip)!;
25 record.count++;
26
27 // Sprawdź, czy przekroczono limit
28 if (record.count > MAX_REQUESTS_PER_MINUTE) {
29 // Oblicz czas, po którym można ponownie wysyłać żądania
30 const resetTime = Math.ceil((record.resetAt - now) / 1000);
31
32 // Zwróć odpowiedź z kodem 429 Too Many Requests
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};W Metropolii Quantum, niektóre nowe funkcje są testowane na wybranych grupach mieszkańców. Zaimplementujmy podobny system A/B testów:
1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5// Definicje testów A/B
6const AB_TESTS = {
7 'new-landing-page': {
8 variants: ['control', 'variant-a', 'variant-b'],
9 weights: [0.5, 0.25, 0.25], // 50% użytkowników zobaczy control, 25% variant-a, 25% variant-b
10 },
11 'checkout-flow': {
12 variants: ['standard', 'simplified'],
13 weights: [0.5, 0.5], // 50% użytkowników zobaczy każdy wariant
14 },
15};
16
17export function middleware(request: NextRequest) {
18 const response = NextResponse.next();
19
20 // Przypisz użytkownika do grup testów
21 Object.entries(AB_TESTS).forEach(([testName, test]) => {
22 // Pobierz istniejący wariant z cookie lub przypisz nowy
23 let variant = request.cookies.get(`ab-\${testName}`)?.value;
24
25 if (!variant || !test.variants.includes(variant)) {
26 // Przypisz wariant na podstawie wag
27 variant = assignVariant(test.variants, test.weights);
28
29 // Zapisz wariant w cookie
30 response.cookies.set(`ab-\${testName}`, variant, {
31 maxAge: 60 * 60 * 24 * 30, // 30 dni
32 path: '/',
33 });
34 }
35
36 // Dodaj informację o wariancie jako nagłówek dla klienta
37 response.headers.set(`x-ab-\${testName}`, variant);
38 });
39
40 return response;
41}
42
43// Funkcja przypisująca wariant na podstawie wag
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 // Domyślnie zwróć pierwszy wariant
56 return variants[0];
57}
58
59export const config = {
60 matcher: '/((?!api|_next/static|_next/image|favicon.ico).*)',
61};W zastosowaniach produkcyjnych, ważne jest monitorowanie działania middleware. Możemy zaimplementować prosty system logowania:
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 // Utwórz identyfikator żądania
9 const requestId = generateRequestId();
10
11 // Utwórz odpowiedź
12 const response = NextResponse.next();
13
14 // Dodaj identyfikator żądania do nagłówków
15 response.headers.set('x-request-id', requestId);
16
17 // Oblicz czas wykonania
18 const duration = Date.now() - startTime;
19 response.headers.set('x-response-time', `\${duration}ms`);
20
21 // W środowisku produkcyjnym można wysłać te dane do systemu monitorowania
22 // logRequest(requestId, request.method, request.url, response.status, duration);
23
24 return response;
25}
26
27// Generowanie unikalnego identyfikatora żądania
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};Middleware może również integrować się z zewnętrznymi systemami, takimi jak systemy uwierzytelniania lub analityki:
1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5export async function middleware(request: NextRequest) {
6 // Pobierz informacje o sesji z zewnętrznego serwisu
7 const sessionId = request.cookies.get('session-id')?.value;
8 let userInfo = null;
9
10 if (sessionId) {
11 try {
12 // W rzeczywistej aplikacji, to mogłoby być wywołanie API
13 userInfo = await fetchUserInfo(sessionId);
14 } catch (error) {
15 console.error('Error fetching user info:', error);
16 }
17 }
18
19 // Utwórz odpowiedź
20 const response = NextResponse.next();
21
22 // Jeśli mamy informacje o użytkowniku, dodaj je do nagłówków
23 if (userInfo) {
24 response.headers.set('x-user-id', userInfo.id);
25 response.headers.set('x-user-plan', userInfo.plan);
26
27 // Wysyłanie danych do systemu analityki
28 trackUserActivity(userInfo.id, request.url, request.method);
29 }
30
31 return response;
32}
33
34// Funkcja pobierająca informacje o użytkowniku (symulacja)
35async function fetchUserInfo(sessionId: string) {
36 // W rzeczywistej aplikacji, mogłoby to być wywołanie API
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// Funkcja śledząca aktywność użytkownika (symulacja)
49function trackUserActivity(userId: string, url: string, method: string) {
50 // W rzeczywistej aplikacji, mogłoby to wysyłać dane do Google Analytics, Mixpanel, itp.
51 console.log(`User \${userId} accessed \${url} with method \${method}`);
52}
53
54export const config = {
55 matcher: '/((?!_next/static|_next/image|favicon.ico).*)',
56};Middleware w Next.js 15 jest potężnym narzędziem, które, podobnie jak Quantum Gateway w Metropolii Quantum, umożliwia kontrolę i modyfikację ruchu w aplikacji na samym początku cyklu żądanie-odpowiedź. Dzięki temu możemy:
Implementować zaawansowane systemy autoryzacji - weryfikować tokeny JWT, przekierowywać niezalogowanych użytkowników, kontrolować dostęp do chronionych zasobów.
Dodawać funkcje internacjonalizacji - wykrywać preferowany język użytkownika, przekierowywać do odpowiednich wersji językowych.
Personalizować doświadczenie użytkownika - dostosowywać treści na podstawie lokalizacji, preferencji i historii użytkownika.
Optymalizować wydajność - kontrolować nagłówki cachowania, modyfikować odpowiedzi, implementować rate limiting.
Integrować się z zewnętrznymi systemami - łączyć się z systemami analityki, uwierzytelniania i innymi usługami.
Przeprowadzać testy A/B - przydzielać użytkowników do grup testowych i personalizować doświadczenie.
Monitorować i debugować - śledzić żądania, mierzyć wydajność, zapisywać metryki.
Wykorzystując middleware, możemy znacząco rozszerzyć możliwości naszej aplikacji Next.js 15, dodając zaawansowane funkcje, które są wykonywane na wczesnym etapie cyklu życia żądania, co pozwala na większą kontrolę i lepszą wydajność.
W następnym rozdziale, zgłębimy techniki obsługi formularz i walidacji danych w Next.js 15, co pozwoli nam tworzyć interaktywne i przyjazne dla użytkownika interfejsy zbierania danych.