Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Obsługa 404 i przekierowań (not-found.js i redirects)

W rozległym mieście kwantowym Metropolis Quantum, nawigacja staje się kluczowym elementem codziennego życia. Co jednak, gdy mieszkaniec lub turysta próbuje dotrzeć do nieistniejącej lokacji? System miejski musi elegancko obsłużyć taki przypadek - albo informując, że miejsce nie istnieje, albo przekierowując do alternatywnej lokalizacji.

Podobnie w aplikacjach webowych zbudowanych na Next.js 15, musimy zadbać o eleganckie obsłużenie sytuacji, gdy użytkownik próbuje uzyskać dostęp do nieistniejącej strony lub zasobu. Ten rozdział skupia się na tworzeniu niestandardowych stron 404 oraz implementacji przekierowań w Next.js 15.

Obsługa stron 404 (nieznalezionych)

W Next.js 15 możemy obsługiwać strony 404 na dwa główne sposoby:

  1. Globalna strona 404 dla całej aplikacji
  2. Niestandardowe strony 404 dla konkretnych segmentów lub dynamicznych parametrów

Globalna strona 404

Aby utworzyć globalną stronę 404 w Next.js 15, należy utworzyć plik

not-found.js
lub
not-found.tsx
w katalogu głównym
app
:

1// app/not-found.tsx
2import Link from 'next/link';
3 
4export default function NotFound() {
5  return (
6    <div className="quantum-not-found">
7      <h1>404: Lokalizacja Kwantowa Nieznaleziona</h1>
8      <div className="quantum-hologram">
9        <div className="hologram-effect">404</div>
10      </div>
11      <p>
12        Przepraszamy, ale miejsce, którego szukasz nie istnieje w Metropolis Quantum.
13        Nasze kwantowe czujniki nie mogły zlokalizować żądanego celu w żadnym z dostępnych
14        wymiarów czasoprzestrzennych.
15      </p>
16      <p>
17        Możliwe przyczyny błędu:
18      </p>
19      <ul>
20        <li>Koordynaty kwantowe są nieprawidłowe</li>
21        <li>Lokalizacja istniała, ale została usunięta</li>
22        <li>Quantum fluktuacje tymczasowo zakłóciły dostęp</li>
23      </ul>
24      <div className="action-buttons">
25        <Link href="/" className="quantum-button primary">
26          Powrót do Centrum Kontroli
27        </Link>
28        <Link href="/map" className="quantum-button secondary">
29          Otwórz Mapę Kwantową
30        </Link>
31      </div>
32    </div>
33  );
34}

Ta strona będzie wyświetlana automatycznie, gdy użytkownik spróbuje uzyskać dostęp do nieistniejącej ścieżki URL.

Niestandardowe strony 404 dla konkretnych segmentów

Next.js 15 pozwala również na tworzenie niestandardowych stron 404 dla konkretnych segmentów aplikacji. Możesz utworzyć plik

not-found.js
lub
not-found.tsx
w dowolnym katalogu, a będzie on używany dla tego konkretnego segmentu i jego podścieżek:

1// app/quantum-properties/not-found.tsx
2import Link from 'next/link';
3 
4export default function QuantumPropertiesNotFound() {
5  return (
6    <div className="property-not-found">
7      <h1>Posiadłość Kwantowa Nieznaleziona</h1>
8      <p>
9        Niestety, nie znaleźliśmy poszukiwanej przez Ciebie nieruchomości kwantowej.
10        Nasze sensory nie wykryły sygnatur kwantowych w podanej lokalizacji.
11      </p>
12      <div className="property-suggestions">
13        <h2>Sprawdź nasze najpopularniejsze nieruchomości:</h2>
14        <div className="suggestion-list">
15          {/* Lista popularnych nieruchomości */}
16        </div>
17      </div>
18      <Link href="/quantum-properties" className="quantum-button">
19        Wróć do wyszukiwarki nieruchomości
20      </Link>
21    </div>
22  );
23}

Wywołanie not-found z poziomu komponentu

Możemy również programowo wywołać wyświetlenie strony 404 z dowolnego komponentu lub funkcji ładującej dane, używając funkcji

notFound
z
next/navigation
:

1// app/quantum-properties/[id]/page.tsx
2import { notFound } from 'next/navigation';
3import { getPropertyById } from '@/lib/api';
4 
5export default async function QuantumPropertyPage({ params }: { params: Promise<{ id: string }> }) {
6  const property = await getPropertyById((await params).id);
7 
8  // Jeśli nieruchomość nie istnieje, wyświetl stronę 404
9  if (!property) {
10    notFound();
11  }
12 
13  return (
14    <div className="quantum-property-details">
15      <h1>{property.name}</h1>
16      {/* Szczegóły nieruchomości */}
17    </div>
18  );
19}

Jest to szczególnie przydatne przy dynamicznych stronach, gdzie chcemy sprawdzić, czy dany zasób istnieje, a jeśli nie - wyświetlić stronę 404.

Generowanie metadanych dla strony 404

Możemy również dostosować metadane dla naszej strony 404, aby poprawić SEO i doświadczenie użytkownika:

1// app/not-found.tsx
2import { Metadata } from 'next';
3import Link from 'next/link';
4 
5export const metadata: Metadata = {
6  title: 'Strona nie znaleziona | Metropolis Quantum',
7  description: 'Nie mogliśmy znaleźć strony, której szukasz w Metropolis Quantum.',
8};
9 
10export default function NotFound() {
11  // Zawartość strony 404
12}

Przekierowania w Next.js 15

W Metropolis Quantum, gdy stara lokalizacja zostaje przeniesiona lub zastąpiona, system transportu automatycznie przekierowuje podróżnych do nowej destynacji. W Next.js, podobnie używamy przekierowań, aby skierować użytkowników z nieaktualnych URL-i do nowych lokalizacji.

Next.js 15 oferuje kilka metod implementacji przekierowań:

  1. Statyczne przekierowania w konfiguracji
  2. Dynamiczne przekierowania w kodzie
  3. Tymczasowe vs. permanentne przekierowania

Statyczne przekierowania w next.config.js

Najprostszym sposobem zdefiniowania przekierowań jest użycie pliku konfiguracyjnego

next.config.js
:

1// next.config.js
2module.exports = {
3  async redirects() {
4    return [
5      {
6        // Przekierowanie z dawnej ścieżki do nowej
7        source: '/old-quantum-district',
8        destination: '/quantum-districts/central',
9        permanent: true, // kod statusu 308 (permanent redirect)
10      },
11      {
12        // Przekierowanie z parametrami
13        source: '/quantum-labs/:labId',
14        destination: '/research-facilities/:labId',
15        permanent: false, // kod statusu 307 (temporary redirect)
16      },
17      {
18        // Przekierowanie z zachowaniem parametrów zapytania
19        source: '/search-old',
20        destination: '/search-new',
21        permanent: false,
22      },
23      {
24        // Przekierowanie z wieloma parametrami i regex
25        source: '/quantum-zones/:zoneId(\d{1,})',
26        destination: '/zones/:zoneId',
27        permanent: true,
28      },
29    ];
30  },
31};

Każde przekierowanie zawiera:

  • source
    : ścieżka URL źródłowa
  • destination
    : ścieżka URL docelowa
  • permanent
    : flaga określająca, czy przekierowanie jest trwałe czy tymczasowe

Różnica między trwałym a tymczasowym przekierowaniem:

  • Trwałe (permanent, kod 308): Informuje przeglądarki i boty wyszukiwarek, że strona została na stałe przeniesiona. Przeglądarki zapamiętują to przekierowanie i w przyszłości automatycznie przechodzą do nowego URL.
  • Tymczasowe (temporary, kod 307): Wskazuje, że strona jest tymczasowo dostępna pod innym adresem, ale może wrócić pod oryginalnym URL. Przeglądarki nie zapamiętują tego przekierowania na dłużej.

Programowe przekierowania w kodzie

Next.js 15 umożliwia również dynamiczne przekierowania bezpośrednio w komponentach lub funkcjach danych za pomocą funkcji

redirect
z
next/navigation
:

1// app/quantum-profile/[userId]/page.tsx
2import { redirect } from 'next/navigation';
3import { getUserById } from '@/lib/api';
4 
5export default async function UserProfilePage({ params }: { params: Promise<{ userId: string }> }) {
6  const user = await getUserById((await params).userId);
7 
8  // Jeśli użytkownik nie istnieje, przekieruj na stronę logowania
9  if (!user) {
10    redirect('/login');
11  }
12 
13  // Jeśli użytkownik jest administratorem, przekieruj na panel admina
14  if (user.role === 'admin') {
15    redirect('/admin/dashboard');
16  }
17 
18  return (
19    <div className="user-profile">
20      <h1>Profil: {user.name}</h1>
21      {/* Zawartość profilu */}
22    </div>
23  );
24}

Funkcja

redirect
jest szczególnie przydatna w sytuacjach, gdy decyzja o przekierowaniu zależy od danych dynamicznych, takich jak status użytkownika, uprawnienia, czy dostępność zasobów.

Przekierowania w middleware

Middleware w Next.js pozwala na przechwytywanie żądań przed ich przetworzeniem przez aplikację, co daje możliwość implementacji bardziej złożonych logik przekierowań:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4 
5export function middleware(request: NextRequest) {
6  // Pobieranie ścieżki z URL
7  const pathname = request.nextUrl.pathname;
8 
9  // Przekierowanie warunkowe oparte na domenie
10  if (request.headers.get('host')?.includes('old-domain.com')) {
11    return NextResponse.redirect(
12      new URL(pathname, 'https://new-domain.com')
13    );
14  }
15 
16  // Przekierowanie starych ścieżek do nowych
17  if (pathname.startsWith('/legacy-api')) {
18    return NextResponse.redirect(
19      new URL(pathname.replace('/legacy-api', '/api/v2'), request.url)
20    );
21  }
22 
23  // Przekierowanie na podstawie geolokalizacji
24  const country = request.geo?.country || 'US';
25  if (pathname === '/events' && country === 'PL') {
26    return NextResponse.redirect(new URL('/wydarzenia', request.url));
27  }
28 
29  // Przekierowanie na podstawie preferencji językowych przeglądarki
30  const preferredLocale = request.headers.get('accept-language')?.split(',')[0].split('-')[0];
31  if (pathname === '/' && preferredLocale === 'pl' && !pathname.includes('/pl')) {
32    return NextResponse.redirect(new URL('/pl', request.url));
33  }
34 
35  return NextResponse.next();
36}
37 
38export const config = {
39  matcher: [
40    '/((?!api|_next/static|_next/image|favicon.ico).*)',
41  ],
42};

Middleware daje nam ogromną elastyczność, umożliwiając przekierowania oparte na:

  • Nagłówkach HTTP
  • Cookies
  • Geolokalizacji
  • User-Agent
  • Parametrach zapytania
  • I wielu innych czynnikach

Przekierowania warunkowe w Server Components

W Server Components możemy używać przekierowań warunkowych w oparciu o dane pobierane z API lub bazy danych:

1// app/quantum-experiment/[experimentId]/page.tsx
2import { redirect } from 'next/navigation';
3import { getExperimentById, getUserPermissions } from '@/lib/api';
4 
5export default async function ExperimentPage({ params }: { params: Promise<{ experimentId: string }> }) {
6  // Pobierz dane eksperymentu
7  const experiment = await getExperimentById((await params).experimentId);
8 
9  // Jeśli eksperyment został przeniesiony, przekieruj do nowej lokalizacji
10  if (experiment?.movedTo) {
11    redirect(`/quantum-experiment/\${experiment.movedTo}`);
12  }
13 
14  // Jeśli eksperyment jest prywatny, sprawdź uprawnienia użytkownika
15  if (experiment?.isPrivate) {
16    const userPermissions = await getUserPermissions();
17    
18    if (!userPermissions.canAccessPrivateExperiments) {
19      redirect('/access-denied');
20    }
21  }
22 
23  return (
24    <div className="experiment-details">
25      <h1>{experiment.title}</h1>
26      {/* Szczegóły eksperymentu */}
27    </div>
28  );
29}

Przekierowania w Client Components

W komponentach klienckich możemy również wykonywać przekierowania za pomocą hooków

useRouter
:

1'use client';
2
3import { useRouter } from 'next/navigation';
4import { useEffect } from 'react';
5import { useAuth } from '@/lib/auth';
6
7export default function ProtectedPage() {
8  const router = useRouter();
9  const { user, loading } = useAuth();
10  
11  useEffect(() => {
12    // Przekieruj niezalogowanych użytkowników do strony logowania
13    if (!loading && !user) {
14      router.push('/login');
15    }
16  }, [user, loading, router]);
17  
18  if (loading) {
19    return <div>Ładowanie...</div>;
20  }
21  
22  if (!user) {
23    return null; // Nie renderuj nic podczas przekierowania
24  }
25  
26  return (
27    <div className="protected-content">
28      <h1>Witaj, {user.name}!</h1>
29      {/* Chroniona zawartość strony */}
30    </div>
31  );
32}

Zaawansowane techniki obsługi 404 i przekierowań

Obsługa nieistniejących parametrów dynamicznych

W przypadku tras dynamicznych z parametrami, możemy generować statyczne strony tylko dla określonych parametrów, a dla pozostałych wyświetlać stronę 404:

1// app/quantum-experiments/[experimentId]/page.tsx
2import { notFound } from 'next/navigation';
3import { getExperimentById, getAllExperimentIds } from '@/lib/api';
4
5// Generowanie statycznych stron dla znanych eksperymentów
6export async function generateStaticParams() {
7  const experimentIds = await getAllExperimentIds();
8  
9  return experimentIds.map((id) => ({
10    experimentId: id,
11  }));
12}
13
14export default async function ExperimentPage({ params }: { params: Promise<{ experimentId: string }> }) {
15  const experiment = await getExperimentById((await params).experimentId);
16  
17  // Jeśli eksperyment nie istnieje, wyświetl 404
18  if (!experiment) {
19    notFound();
20  }
21  
22  return (
23    <div className="experiment-details">
24      <h1>{experiment.title}</h1>
25      {/* Szczegóły eksperymentu */}
26    </div>
27  );
28}

Obsługa wygasłych treści

Czasami treści mogą być tymczasowo dostępne lub wygasać po określonym czasie. Możemy obsłużyć taki przypadek, automatycznie przekierowując do alternatywnych stron:

1// app/quantum-events/[eventId]/page.tsx
2import { redirect, notFound } from 'next/navigation';
3import { getEventById } from '@/lib/api';
4
5export default async function EventPage({ params }: { params: Promise<{ eventId: string }> }) {
6  const event = await getEventById((await params).eventId);
7  
8  // Jeśli wydarzenie nie istnieje, wyświetl 404
9  if (!event) {
10    notFound();
11  }
12  
13  // Sprawdź, czy wydarzenie się już zakończyło
14  const now = new Date();
15  const eventEndDate = new Date(event.endDate);
16  
17  if (now > eventEndDate) {
18    // Jeśli są dostępne nagrania, przekieruj do nich
19    if (event.recordings) {
20      redirect(`/quantum-events/recordings/\${(await params).eventId}`);
21    }
22    
23    // Jeśli są zaplanowane przyszłe wydarzenia z tej serii, przekieruj do nich
24    if (event.series && event.nextInSeries) {
25      redirect(`/quantum-events/\${event.nextInSeries}`);
26    }
27    
28    // W przeciwnym razie przekieruj do strony archiwum
29    redirect('/quantum-events/archive');
30  }
31  
32  return (
33    <div className="event-details">
34      <h1>{event.title}</h1>
35      {/* Szczegóły wydarzenia */}
36    </div>
37  );
38}

Niestandardowe przekierowania oparte na A/B testach

Możemy również zaimplementować przekierowania oparte na testach A/B, aby kierować użytkowników do różnych wersji stron:

1// middleware.ts
2import { NextResponse } from 'next/server';
3import type { NextRequest } from 'next/server';
4
5// Funkcja pomocnicza do losowania wariantu testu A/B
6function getABTestVariant(userId: string, testName: string, variants: string[]): string {
7  // Używamy deterministycznego algorytmu na podstawie userId i nazwy testu
8  const hash = hashString(`\${userId}-\${testName}`);
9  const variantIndex = hash % variants.length;
10  return variants[variantIndex];
11}
12
13// Pomocnicza funkcja do generowania hasha ze stringa
14function hashString(str: string): number {
15  let hash = 0;
16  for (let i = 0; i < str.length; i++) {
17    hash = ((hash << 5) - hash) + str.charCodeAt(i);
18    hash |= 0; // Konwersja do 32-bitowej liczby całkowitej
19  }
20  return Math.abs(hash);
21}
22
23export function middleware(request: NextRequest) {
24  const pathname = request.nextUrl.pathname;
25  
26  // A/B test dla strony głównej
27  if (pathname === '/') {
28    // Pobierz lub wygeneruj ID użytkownika
29    let userId = request.cookies.get('user_id')?.value;
30    
31    if (!userId) {
32      userId = crypto.randomUUID();
33      // W prawdziwej implementacji należałoby zapisać to cookie w odpowiedzi
34    }
35    
36    // Określ wariant testu A/B dla tego użytkownika
37    const homePageVariant = getABTestVariant(userId, 'homepage_redesign', ['control', 'variant_a', 'variant_b']);
38    
39    // Przekieruj do odpowiedniego wariantu
40    if (homePageVariant !== 'control') {
41      return NextResponse.redirect(new URL(`/home-\${homePageVariant}`, request.url));
42    }
43  }
44  
45  return NextResponse.next();
46}
47
48export const config = {
49  matcher: ['/'],
50};

Przekierowania bazujące na statusie API

Możemy wykonywać przekierowania na podstawie odpowiedzi z API:

1// app/quantum-dashboard/page.tsx
2import { redirect } from 'next/navigation';
3import { getSystemStatus } from '@/lib/api';
4
5export default async function DashboardPage() {
6  try {
7    const status = await getSystemStatus();
8    
9    // Jeśli system jest w trakcie konserwacji, przekieruj do strony informacyjnej
10    if (status.maintenance) {
11      redirect('/maintenance');
12    }
13    
14    // Jeśli system wymaga aktualizacji klienta, przekieruj do strony aktualizacji
15    if (status.requiresClientUpdate) {
16      redirect('/update-required');
17    }
18    
19    return (
20      <div className="quantum-dashboard">
21        <h1>Panel kontrolny</h1>
22        {/* Zawartość panelu */}
23      </div>
24    );
25  } catch (error) {
26    // Jeśli API jest niedostępne, przekieruj do strony statusu
27    redirect('/system-status');
28  }
29}

Integracja z systemem analitycznym

W przypadku przekierowań i stron 404, warto śledzić, które ścieżki prowadzą do błędów lub przekierowań, aby optymalizować doświadczenie użytkownika. Oto przykład implementacji śledzenia dla stron 404:

1// app/not-found.tsx
2'use client';
3
4import { useEffect } from 'react';
5import Link from 'next/link';
6import { usePathname } from 'next/navigation';
7import { trackEvent } from '@/lib/analytics';
8
9export default function NotFound() {
10  const pathname = usePathname();
11  
12  useEffect(() => {
13    // Śledzenie zdarzenia 404
14    trackEvent('404_error', {
15      path: pathname,
16      timestamp: new Date().toISOString(),
17      referrer: document.referrer
18    });
19  }, [pathname]);
20  
21  return (
22    <div className="quantum-not-found">
23      <h1>404: Lokalizacja Kwantowa Nieznaleziona</h1>
24      {/* Zawartość strony 404 */}
25    </div>
26  );
27}

Testowanie przekierowań i stron 404

Ważne jest, aby dokładnie przetestować przekierowania i strony 404, aby upewnić się, że działają zgodnie z oczekiwaniami:

1// tests/redirects.test.js
2import { createServer } from 'http';
3import { parse } from 'url';
4import fetch from 'node-fetch';
5import { NextRequest } from 'next/server';
6import { middleware } from '../middleware';
7
8describe('Redirects Tests', () => {
9  let server;
10  let port;
11
12  beforeAll(() => {
13    // Uruchom lokalny serwer testowy
14    server = createServer((req, res) => {
15      res.writeHead(200).end('Test server');
16    }).listen(0);
17    port = server.address().port;
18  });
19
20  afterAll(() => {
21    server.close();
22  });
23
24  test('Should redirect old-quantum-district to quantum-districts/central', async () => {
25    const req = new NextRequest(new URL(`http://localhost:\${port}/old-quantum-district`));
26    const res = middleware(req);
27    
28    expect(res.status).toBe(307); // lub 308 dla permanent
29    expect(res.headers.get('Location')).toBe(`http://localhost:\${port}/quantum-districts/central`);
30  });
31
32  test('Should handle country-specific redirects', async () => {
33    const req = new NextRequest(new URL(`http://localhost:\${port}/events`));
34    Object.defineProperty(req, 'geo', {
35      value: { country: 'PL' },
36      writable: true
37    });
38    
39    const res = middleware(req);
40    
41    expect(res.status).toBe(307);
42    expect(res.headers.get('Location')).toBe(`http://localhost:\${port}/wydarzenia`);
43  });
44});

Podsumowanie

Prawidłowa obsługa stron 404 i przekierowań jest kluczowym elementem każdej profesjonalnej aplikacji Next.js. Podobnie jak w Metropolis Quantum, gdzie zaawansowane systemy nawigacji zapewniają, że każdy podróżny dotrze do właściwego miejsca (nawet jeśli pierwotna destynacja nie istnieje), tak i w naszej aplikacji musimy dbać o płynne doświadczenie użytkownika, nawet gdy napotyka on błędy.

Next.js 15 oferuje wszechstronne narzędzia do implementacji:

  • Niestandardowych stron 404 globalnych i specyficznych dla segmentów
  • Statycznych przekierowań w konfiguracji
  • Dynamicznych przekierowań w kodzie
  • Zaawansowanych reguł przekierowań w middleware

Dzięki tym narzędziom możemy tworzyć intuicyjne i przyjazne dla użytkownika aplikacje, które elegancko obsługują nawet nieoczekiwane sytuacje.

W następnym rozdziale, omówimy techniki optymalizacji wydajności w Next.js 15, aby nasze aplikacje nie tylko były przyjazne dla użytkownika, ale również działały z maksymalną prędkością, zapewniając płynne doświadczenie w każdej sytuacji.

Vai a CodeWorlds