Polski

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:

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:

Obiekt Response

Funkcje middleware zwracają obiekt Response, aby kontrolować wynik żądania. Możesz użyć następujących odpowiedzi:

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

Rozwiązywanie typowych problemów

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.