Poznaj middleware w Next.js – potężną funkcję do przechwytywania żądań. Naucz się implementować uwierzytelnianie, autoryzację, przekierowania i testy A/B.
Middleware w Next.js: Opanowanie przechwytywania żądań w dynamicznych aplikacjach
Middleware w Next.js zapewnia elastyczny i potężny sposób na przechwytywanie i modyfikowanie przychodzących żądań, zanim dotrą one do Twoich tras (routes). Ta możliwość pozwala na implementację szerokiego zakresu funkcji, od uwierzytelniania i autoryzacji po przekierowania i testy A/B, a wszystko to przy jednoczesnej optymalizacji wydajności. Ten kompleksowy przewodnik przeprowadzi Cię przez podstawowe koncepcje middleware w Next.js i pokaże, jak efektywnie z niego korzystać.
Czym jest middleware w Next.js?
Middleware w Next.js to funkcja, która jest uruchamiana przed zakończeniem obsługi żądania. Pozwala na:
- Przechwytywanie żądań: Analizowanie nagłówków, plików cookie i adresu URL przychodzącego żądania.
- Modyfikowanie żądań: Przepisywanie adresów URL, ustawianie nagłówków lub przekierowywanie użytkowników na podstawie określonych kryteriów.
- Wykonywanie kodu: Uruchamianie logiki po stronie serwera przed wyrenderowaniem strony.
Funkcje middleware definiuje się w pliku middleware.ts
(lub middleware.js
) w głównym katalogu projektu. Są one wykonywane dla każdej trasy w Twojej aplikacji lub dla określonych tras na podstawie konfigurowalnych matcherów.
Kluczowe koncepcje i korzyści
Obiekt Request
Obiekt request
zapewnia dostęp do informacji o przychodzącym żądaniu, w tym:
request.url
: Pełny adres URL żądania.request.method
: Metoda HTTP (np. GET, POST).request.headers
: Obiekt zawierający nagłówki żądania.request.cookies
: Obiekt reprezentujący pliki cookie żądania.request.geo
: Dostarcza dane geolokalizacyjne powiązane z żądaniem, jeśli są dostępne.
Obiekt Response
Funkcje middleware zwracają obiekt Response
, aby kontrolować wynik żądania. Możesz użyć następujących odpowiedzi:
NextResponse.next()
: Kontynuuje normalne przetwarzanie żądania, pozwalając mu dotrzeć do docelowej trasy.NextResponse.redirect(url)
: Przekierowuje użytkownika na inny adres URL.NextResponse.rewrite(url)
: Przepisuje adres URL żądania, skutecznie serwując inną stronę bez przekierowania. Adres URL w przeglądarce pozostaje bez zmian.- Zwrócenie niestandardowego obiektu
Response
: Pozwala na serwowanie niestandardowej zawartości, takiej jak strona błędu lub określona odpowiedź JSON.
Matchery
Matchery pozwalają określić, do których tras ma być stosowane Twoje middleware. Możesz definiować matchery za pomocą wyrażeń regularnych lub wzorców ścieżek. Zapewnia to, że Twoje middleware uruchamia się tylko wtedy, gdy jest to konieczne, poprawiając wydajność i zmniejszając obciążenie.
Edge Runtime
Middleware w Next.js działa w środowisku Edge Runtime, które jest lekkim środowiskiem uruchomieniowym JavaScript, mogącym być wdrożonym blisko Twoich użytkowników. Ta bliskość minimalizuje opóźnienia i poprawia ogólną wydajność aplikacji, zwłaszcza dla użytkowników rozproszonych globalnie. Edge Runtime jest dostępne w sieci Edge Vercel i na innych kompatybilnych platformach. Edge Runtime ma pewne ograniczenia, w szczególności dotyczące użycia API Node.js.
Praktyczne przykłady: Implementacja funkcji middleware
1. Uwierzytelnianie
Middleware do uwierzytelniania może być używane do ochrony tras, które wymagają zalogowania użytkowników. Oto przykład, jak zaimplementować uwierzytelnianie za pomocą plików cookie:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const token = request.cookies.get('auth_token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/dashboard/:path*'],
}
To middleware sprawdza obecność pliku cookie auth_token
. Jeśli plik cookie nie zostanie znaleziony, użytkownik jest przekierowywany na stronę /login
. config.matcher
określa, że to middleware powinno działać tylko dla tras w obrębie /dashboard
.
Perspektywa globalna: Dostosuj logikę uwierzytelniania, aby wspierać różne metody (np. OAuth, JWT) i integrować się z różnymi dostawcami tożsamości (np. Google, Facebook, Azure AD), aby zaspokoić potrzeby użytkowników z różnych regionów.
2. Autoryzacja
Middleware do autoryzacji może być używane do kontrolowania dostępu do zasobów na podstawie ról lub uprawnień użytkownika. Na przykład możesz mieć panel administracyjny, do którego dostęp mają tylko określeni użytkownicy.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
const token = request.cookies.get('auth_token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Example: Fetch user roles from an API (replace with your actual logic)
const userResponse = await fetch('https://api.example.com/userinfo', {
headers: {
Authorization: `Bearer ${token}`,
},
});
const userData = await userResponse.json();
if (userData.role !== 'admin') {
return NextResponse.redirect(new URL('/unauthorized', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/admin/:path*'],
}
To middleware pobiera rolę użytkownika i sprawdza, czy ma on rolę admin
. Jeśli nie, jest przekierowywany na stronę /unauthorized
. Ten przykład używa zastępczego punktu końcowego API. Zastąp `https://api.example.com/userinfo` rzeczywistym punktem końcowym serwera uwierzytelniania.
Perspektywa globalna: Bądź świadomy przepisów o ochronie danych (np. RODO, CCPA) podczas przetwarzania danych użytkowników. Wdrażaj odpowiednie środki bezpieczeństwa, aby chronić wrażliwe informacje i zapewnić zgodność z lokalnymi przepisami.
3. Przekierowania
Middleware do przekierowań może być używane do przekierowywania użytkowników na podstawie ich lokalizacji, języka lub innych kryteriów. Na przykład możesz przekierować użytkowników do zlokalizowanej wersji swojej strony internetowej na podstawie ich adresu IP.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const country = request.geo?.country || 'US'; // Default to US if geo-location fails
if (country === 'DE') {
return NextResponse.redirect(new URL('/de', request.url))
}
if (country === 'FR') {
return NextResponse.redirect(new URL('/fr', request.url))
}
return NextResponse.next()
}
export const config = {
matcher: ['/'],
}
To middleware sprawdza kraj użytkownika na podstawie jego adresu IP i przekierowuje go do odpowiedniej zlokalizowanej wersji strony internetowej (/de
dla Niemiec, /fr
dla Francji). Jeśli geolokalizacja zawiedzie, domyślnie przyjmuje wersję amerykańską. Zauważ, że zależy to od dostępności właściwości geo
(np. podczas wdrożenia na Vercel).
Perspektywa globalna: Upewnij się, że Twoja strona internetowa obsługuje wiele języków i walut. Daj użytkownikom możliwość ręcznego wyboru preferowanego języka lub regionu. Używaj odpowiednich formatów daty i czasu dla każdej lokalizacji.
4. Testy A/B
Middleware może być używane do implementacji testów A/B poprzez losowe przypisywanie użytkowników do różnych wariantów strony i śledzenie ich zachowania. Oto uproszczony przykład:
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
function getRandomVariant() {
return Math.random() < 0.5 ? 'A' : 'B';
}
export function middleware(request: NextRequest) {
let variant = request.cookies.get('variant')?.value;
if (!variant) {
variant = getRandomVariant();
const response = NextResponse.next();
response.cookies.set('variant', variant);
return response;
}
if (variant === 'B') {
return NextResponse.rewrite(new URL('/variant-b', request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ['/'],
}
To middleware przypisuje użytkowników do wariantu 'A' lub 'B'. Jeśli użytkownik nie ma jeszcze pliku cookie variant
, jest on losowo przypisywany i ustawiany. Użytkownicy przypisani do wariantu 'B' są przepisywani na stronę /variant-b
. Następnie śledziłbyś wydajność każdego wariantu, aby określić, który jest bardziej skuteczny.
Perspektywa globalna: Weź pod uwagę różnice kulturowe podczas projektowania testów A/B. To, co dobrze działa w jednym regionie, może nie rezonować z użytkownikami w innym. Upewnij się, że Twoja platforma do testów A/B jest zgodna z przepisami o ochronie prywatności w różnych regionach.
5. Flagi funkcji (Feature Flags)
Flagi funkcji pozwalają włączać lub wyłączać funkcje w aplikacji bez wdrażania nowego kodu. Middleware może być używane do określenia, czy użytkownik powinien mieć dostęp do określonej funkcji na podstawie jego identyfikatora, lokalizacji lub innych kryteriów.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
// Example: Fetch feature flags from an API
const featureFlagsResponse = await fetch('https://api.example.com/featureflags', {
headers: {
'X-User-Id': 'user123',
},
});
const featureFlags = await featureFlagsResponse.json();
if (featureFlags.new_feature_enabled) {
// Enable the new feature
return NextResponse.next();
} else {
// Disable the new feature (e.g., redirect to an alternative page)
return NextResponse.redirect(new URL('/alternative-page', request.url));
}
}
export const config = {
matcher: ['/new-feature'],
}
To middleware pobiera flagi funkcji z API i sprawdza, czy flaga new_feature_enabled
jest ustawiona. Jeśli tak, użytkownik może uzyskać dostęp do strony /new-feature
. W przeciwnym razie jest przekierowywany na stronę /alternative-page
.
Perspektywa globalna: Używaj flag funkcji do stopniowego wdrażania nowych funkcji dla użytkowników w różnych regionach. Pozwala to na monitorowanie wydajności i rozwiązywanie wszelkich problemów przed udostępnieniem funkcji szerszej publiczności. Upewnij się również, że Twój system flagowania funkcji skaluje się globalnie i zapewnia spójne wyniki niezależnie od lokalizacji użytkownika. Weź pod uwagę regionalne ograniczenia regulacyjne dotyczące wdrażania funkcji.
Techniki zaawansowane
Łączenie middleware w łańcuchy (Chaining)
Możesz łączyć wiele funkcji middleware w łańcuch, aby wykonać serię operacji na żądaniu. Może to być przydatne do rozbijania złożonej logiki na mniejsze, łatwiejsze do zarządzania moduły.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// First middleware function
const token = request.cookies.get('auth_token');
if (!token) {
return NextResponse.redirect(new URL('/login', request.url))
}
// Second middleware function
response.headers.set('x-middleware-custom', 'value');
return response;
}
export const config = {
matcher: ['/dashboard/:path*'],
}
Ten przykład pokazuje dwa middleware w jednym. Pierwsze wykonuje uwierzytelnianie, a drugie ustawia niestandardowy nagłówek.
Używanie zmiennych środowiskowych
Przechowuj wrażliwe informacje, takie jak klucze API i dane uwierzytelniające do bazy danych, w zmiennych środowiskowych, zamiast umieszczać je na stałe w kodzie funkcji middleware. Poprawia to bezpieczeństwo i ułatwia zarządzanie konfiguracją aplikacji.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
const API_KEY = process.env.API_KEY;
export async function middleware(request: NextRequest) {
const response = await fetch('https://api.example.com/data', {
headers: {
'X-API-Key': API_KEY,
},
});
// ...
}
export const config = {
matcher: ['/data'],
}
W tym przykładzie API_KEY
jest pobierany ze zmiennej środowiskowej.
Obsługa błędów
Zaimplementuj solidną obsługę błędów w swoich funkcjach middleware, aby zapobiec awariom aplikacji spowodowanym przez nieoczekiwane błędy. Używaj bloków try...catch
do przechwytywania wyjątków i odpowiedniego logowania błędów.
// middleware.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
export async function middleware(request: NextRequest) {
try {
const response = await fetch('https://api.example.com/data');
// ...
} catch (error) {
console.error('Error fetching data:', error);
return NextResponse.error(); // Or redirect to an error page
}
}
export const config = {
matcher: ['/data'],
}
Najlepsze praktyki
- Utrzymuj lekkość funkcji middleware: Unikaj wykonywania operacji o dużej złożoności obliczeniowej w middleware, ponieważ może to wpłynąć na wydajność. Przenoś złożone przetwarzanie do zadań w tle lub dedykowanych usług.
- Efektywnie używaj matcherów: Stosuj middleware tylko do tras, które tego wymagają.
- Dokładnie testuj swoje middleware: Pisz testy jednostkowe, aby upewnić się, że Twoje funkcje middleware działają poprawnie.
- Monitoruj wydajność middleware: Używaj narzędzi do monitorowania, aby śledzić wydajność swoich funkcji middleware i identyfikować ewentualne wąskie gardła.
- Dokumentuj swoje middleware: Jasno dokumentuj cel i funkcjonalność każdej funkcji middleware.
- Weź pod uwagę ograniczenia Edge Runtime: Bądź świadomy ograniczeń Edge Runtime, takich jak brak API Node.js. Dostosuj swój kod odpowiednio.
Rozwiązywanie typowych problemów
- Middleware nie uruchamia się: Sprawdź dokładnie konfigurację matchera, aby upewnić się, że middleware jest stosowane do właściwych tras.
- Problemy z wydajnością: Zidentyfikuj i zoptymalizuj powolne funkcje middleware. Użyj narzędzi do profilowania, aby zlokalizować wąskie gardła wydajności.
- Zgodność z Edge Runtime: Upewnij się, że Twój kod jest kompatybilny z Edge Runtime. Unikaj używania API Node.js, które nie są obsługiwane.
- Problemy z plikami cookie: Sprawdź, czy pliki cookie są poprawnie ustawiane i pobierane. Zwróć uwagę na atrybuty plików cookie, takie jak
domain
,path
isecure
. - Konflikty nagłówków: Bądź świadomy potencjalnych konfliktów nagłówków podczas ustawiania niestandardowych nagłówków w middleware. Upewnij się, że Twoje nagłówki nie nadpisują istniejących nagłówków nieumyślnie.
Podsumowanie
Middleware w Next.js to potężne narzędzie do budowania dynamicznych i spersonalizowanych aplikacji internetowych. Opanowując przechwytywanie żądań, możesz zaimplementować szeroki zakres funkcji, od uwierzytelniania i autoryzacji po przekierowania i testy A/B. Stosując się do najlepszych praktyk przedstawionych w tym przewodniku, możesz wykorzystać middleware w Next.js do tworzenia wydajnych, bezpiecznych i skalowalnych aplikacji, które spełniają potrzeby Twojej globalnej bazy użytkowników. Wykorzystaj moc middleware, aby odblokować nowe możliwości w swoich projektach Next.js i dostarczać wyjątkowe doświadczenia użytkownikom.