Utilizziamo i cookie per migliorare la tua esperienza sul sito
CodeWorlds

Dynamiczne segmenty ścieżki i parametry

W Metropolii Quantum, najnowocześniejszy system transportowy posiada niezwykłą zdolność dostosowywania się do potrzeb obywateli. Trasy transportowe nie są sztywno określone - mogą dynamicznie reorganizować się, aby doprowadzić podróżnych do dowolnego miejsca w mieście. Ta elastyczność jest kluczowa dla efektywnego funkcjonowania tak złożonego organizmu miejskiego.

W świecie Next.js 15, analogiczną funkcję pełnią dynamiczne segmenty ścieżki, które pozwalają tworzyć trasy, które adaptują się do różnych parametrów i kontekstów. To one sprawiają, że nawigacja w aplikacji staje się prawdziwie interaktywna i dostosowana do potrzeb użytkownika.

Dynamiczne segmenty w App Router

W Next.js 15, dynamiczne segmenty ścieżki tworzy się poprzez umieszczenie nazwy parametru w nawiasach kwadratowych

[param]
. Taki folder będzie dopasowywał się do różnych wartości w tym segmencie URL, a wartość parametru będzie dostępna wewnątrz komponentów strony.

Podstawowe dynamiczne segmenty

Oto jak wygląda struktura plików dla aplikacji z dynamicznymi segmentami:

1app/
2  ├── destinations/
3  │   ├── page.tsx               # matches /destinations
4  │   └── [planetId]/            # matches /destinations/:planetId (dynamiczny segment)
5  │       └── page.tsx           # matches /destinations/mars, /destinations/venus, etc.

W tym przykładzie

[planetId]
to dynamiczny segment, który może przyjąć dowolną wartość, np. "mars", "venus", czy "jupiter". Ta wartość jest następnie dostępna w komponencie strony jako
params.planetId
.

Implementacja strony z dynamicznym segmentem

Spójrzmy na implementację strony, która wykorzystuje dynamiczny segment

[planetId]
:

1// app/destinations/[planetId]/page.tsx
2import Image from 'next/image';
3import { notFound } from 'next/navigation';
4
5// Ten typ definiuje strukturę parametrów dla tej strony
6type PlanetPageParams = {
7  params: Promise<{
8    planetId: string;
9  }>;
10};
11
12async function getPlanetData(planetId: string) {
13  // Tutaj normalnie pobieralibyśmy dane z API lub bazy danych
14  // Na potrzeby przykładu używamy statycznych danych
15  const planets = {
16    'mars': {
17      name: 'Mars',
18      description: 'Czerwona planeta znana z trudnych warunków i fascynujących zjawisk geologicznych.',
19      gravity: '3.721 m/s²',
20      diameter: '6,779 km',
21      dayLength: '24h 37m',
22      yearLength: '687 dni ziemskich',
23      temperature: '-63°C (średnia)',
24      imageUrl: '/images/mars.jpg'
25    },
26    'venus': {
27      name: 'Wenus',
28      description: 'Druga planeta od Słońca, znana z ekstremalnie gorącego klimatu i gęstej atmosfery.',
29      gravity: '8.87 m/s²',
30      diameter: '12,104 km',
31      dayLength: '243 dni ziemskie',
32      yearLength: '225 dni ziemskich',
33      temperature: '462°C (średnia)',
34      imageUrl: '/images/venus.jpg'
35    },
36    'jupiter': {
37      name: 'Jowisz',
38      description: 'Największa planeta Układu Słonecznego, znana z Wielkiej Czerwonej Plamy i licznych księżyców.',
39      gravity: '24.79 m/s²',
40      diameter: '139,820 km',
41      dayLength: '9h 56m',
42      yearLength: '11.86 lat ziemskich',
43      temperature: '-108°C (średnia)',
44      imageUrl: '/images/jupiter.jpg'
45    }
46  };
47
48  return planets[planetId];
49}
50
51// Komponent otrzymuje params jako props
52export default async function PlanetPage({ params }: PlanetPageParams) {
53  // Pobranie danych planety na podstawie planetId
54  const planet = await getPlanetData((await params).planetId);
55  
56  // Jeśli planeta nie istnieje, wyświetl stronę 404
57  if (!planet) {
58    notFound();
59  }
60  
61  return (
62    <div className="container mx-auto px-4 py-12">
63      <h1 className="text-4xl font-bold mb-8">{planet.name}</h1>
64      
65      <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
66        <div className="relative h-96 lg:h-auto rounded-lg overflow-hidden">
67          <Image
68            src={planet.imageUrl}
69            alt={planet.name}
70            fill
71            className="object-cover"
72            priority
73          />
74        </div>
75        
76        <div className="space-y-6">
77          <p className="text-lg text-gray-700">{planet.description}</p>
78          
79          <div className="bg-gray-50 p-6 rounded-lg shadow-sm">
80            <h2 className="text-2xl font-semibold mb-4">Dane planetarne</h2>
81            <table className="w-full">
82              <tbody>
83                <tr className="border-b">
84                  <th className="text-left py-2">Grawitacja</th>
85                  <td className="text-right py-2">{planet.gravity}</td>
86                </tr>
87                <tr className="border-b">
88                  <th className="text-left py-2">Średnica</th>
89                  <td className="text-right py-2">{planet.diameter}</td>
90                </tr>
91                <tr className="border-b">
92                  <th className="text-left py-2">Długość dnia</th>
93                  <td className="text-right py-2">{planet.dayLength}</td>
94                </tr>
95                <tr className="border-b">
96                  <th className="text-left py-2">Długość roku</th>
97                  <td className="text-right py-2">{planet.yearLength}</td>
98                </tr>
99                <tr>
100                  <th className="text-left py-2">Temperatura</th>
101                  <td className="text-right py-2">{planet.temperature}</td>
102                </tr>
103              </tbody>
104            </table>
105          </div>
106          
107          <div className="flex space-x-4">
108            <a 
109              href={`/destinations/\${(await params).planetId}/colonies\`}
110              className="px-6 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
111            >
112              Kolonie
113            </a>
114            <a 
115              href={`/destinations/\${(await params).planetId}/tours\`}
116              className="px-6 py-3 border border-indigo-600 text-indigo-600 rounded-lg hover:bg-indigo-50 transition-colors"
117            >
118              Wycieczki
119            </a>
120          </div>
121        </div>
122      </div>
123    </div>
124  );
125}

W powyższym kodzie:

  1. Definiujemy typ
    PlanetPageParams
    , który określa strukturę parametrów otrzymywanych przez komponent
  2. Komponent
    PlanetPage
    otrzymuje obiekt
    params
    zawierający
    planetId
    z URL
  3. Używamy tego
    planetId
    do pobrania danych o planecie
  4. Wyświetlamy szczegółowe informacje o planecie, a także linki do podstron, które również wykorzystują ten sam parametr

Generowanie statycznych stron dla znanych parametrów

Jeśli znamy z góry wszystkie możliwe wartości dla dynamicznego segmentu, możemy wygenerować statyczne strony podczas budowania aplikacji, co znacznie poprawi wydajność. Służy do tego funkcja

generateStaticParams
:

1// app/destinations/[planetId]/page.tsx
2
3// Ta funkcja definiuje statyczne ścieżki, które zostaną wygenerowane podczas budowania
4export async function generateStaticParams() {
5  // Tutaj normalnie pobieralibyśmy dane z API lub bazy danych
6  // Zwracamy tablicę obiektów z parametrami, które mają być wygenerowane statycznie
7  return [
8    { planetId: 'mars' },
9    { planetId: 'venus' },
10    { planetId: 'jupiter' }
11  ];
12}
13
14// Reszta komponentu...

Dzięki temu Next.js wygeneruje trzy statyczne strony podczas budowania: dla Marsa, Wenus i Jowisza. To znacznie przyspieszy ich ładowanie, ponieważ nie będą musiały być generowane dynamicznie przy każdym żądaniu.

Wielopoziomowe dynamiczne segmenty

W Metropolii Quantum, system transportowy umożliwia podróżowanie do dowolnego celu poprzez serię precyzyjnych przesiadek, z których każda zawęża przestrzeń do wybranego celu. Podobnie w Next.js 15, możemy tworzyć wielopoziomowe dynamiczne segmenty, aby dokładnie określić żądane zasoby.

Struktura z wieloma dynamicznymi segmentami

1app/
2  ├── destinations/
3  │   ├── [planetId]/           # matches /destinations/:planetId
4  │   │   ├── page.tsx          # matches /destinations/mars
5  │   │   └── [moonId]/         # matches /destinations/:planetId/:moonId
6  │   │       └── page.tsx      # matches /destinations/mars/phobos
7  │   └── page.tsx              # matches /destinations

W tej strukturze mamy dwa poziomy dynamicznych segmentów:

  1. [planetId]
    - identyfikator planety
  2. [moonId]
    - identyfikator księżyca danej planety

Implementacja strony z zagnieżdżonymi parametrami

1// app/destinations/[planetId]/[moonId]/page.tsx
2import Image from 'next/image';
3import Link from 'next/link';
4import { notFound } from 'next/navigation';
5
6type MoonPageParams = {
7  params: Promise<{
8    planetId: string;
9    moonId: string;
10  }>;
11};
12
13async function getMoonData(planetId: string, moonId: string) {
14  // System mapowania księżyców do planet
15  const moonData = {
16    'mars': {
17      'phobos': {
18        name: 'Phobos',
19        description: 'Większy i bliższy z dwóch naturalnych satelitów Marsa, krąży wokół planety co 7 godzin i 39 minut.',
20        diameter: '22.2 km',
21        orbitDistance: '9,376 km',
22        discovered: '1877',
23        imageUrl: '/images/phobos.jpg'
24      },
25      'deimos': {
26        name: 'Deimos',
27        description: 'Mniejszy i dalszy z dwóch naturalnych satelitów Marsa, o nieregularnym kształcie.',
28        diameter: '12.6 km',
29        orbitDistance: '23,463 km',
30        discovered: '1877',
31        imageUrl: '/images/deimos.jpg'
32      }
33    },
34    'jupiter': {
35      'europa': {
36        name: 'Europa',
37        description: 'Księżyc Jowisza, który może posiadać podpowierzchniowy ocean wody pod lodową skorupą.',
38        diameter: '3,121.6 km',
39        orbitDistance: '670,900 km',
40        discovered: '1610',
41        imageUrl: '/images/europa.jpg'
42      },
43      'io': {
44        name: 'Io',
45        description: 'Najbardziej aktywny wulkanicznie obiekt w Układzie Słonecznym, z ponad 400 aktywnymi wulkanami.',
46        diameter: '3,643.2 km',
47        orbitDistance: '421,700 km',
48        discovered: '1610',
49        imageUrl: '/images/io.jpg'
50      },
51      'ganymede': {
52        name: 'Ganimedes',
53        description: 'Największy księżyc w Układzie Słonecznym, większy nawet od planety Merkury.',
54        diameter: '5,268.2 km',
55        orbitDistance: '1,070,400 km',
56        discovered: '1610',
57        imageUrl: '/images/ganymede.jpg'
58      }
59    }
60  };
61  
62  // Sprawdzenie, czy planeta i księżyc istnieją
63  if (!moonData[planetId] || !moonData[planetId][moonId]) {
64    return null;
65  }
66  
67  return {
68    ...moonData[planetId][moonId],
69    planet: planetId
70  };
71}
72
73export default async function MoonPage({ params }: MoonPageParams) {
74  const { planetId, moonId } = await params;
75  const moon = await getMoonData(planetId, moonId);
76  
77  if (!moon) {
78    notFound();
79  }
80  
81  return (
82    <div className="container mx-auto px-4 py-12">
83      <div className="flex items-center mb-8">
84        <Link 
85          href={`/destinations/\${planetId}\`}
86          className="text-indigo-600 hover:underline flex items-center"
87        >
88          <svg className="w-4 h-4 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
89            <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
90          </svg>
91          Wróć do {planetId.charAt(0).toUpperCase() + planetId.slice(1)}
92        </Link>
93      </div>
94
95      <h1 className="text-4xl font-bold mb-8">{moon.name}</h1>
96      
97      <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
98        <div className="relative h-96 lg:h-auto rounded-lg overflow-hidden">
99          <Image
100            src={moon.imageUrl}
101            alt={moon.name}
102            fill
103            className="object-cover"
104            priority
105          />
106        </div>
107        
108        <div className="space-y-6">
109          <p className="text-lg text-gray-700">{moon.description}</p>
110          
111          <div className="bg-gray-50 p-6 rounded-lg shadow-sm">
112            <h2 className="text-2xl font-semibold mb-4">Informacje</h2>
113            <table className="w-full">
114              <tbody>
115                <tr className="border-b">
116                  <th className="text-left py-2">Planeta</th>
117                  <td className="text-right py-2">{moon.planet.charAt(0).toUpperCase() + moon.planet.slice(1)}</td>
118                </tr>
119                <tr className="border-b">
120                  <th className="text-left py-2">Średnica</th>
121                  <td className="text-right py-2">{moon.diameter}</td>
122                </tr>
123                <tr className="border-b">
124                  <th className="text-left py-2">Odległość orbity</th>
125                  <td className="text-right py-2">{moon.orbitDistance}</td>
126                </tr>
127                <tr>
128                  <th className="text-left py-2">Odkryty</th>
129                  <td className="text-right py-2">{moon.discovered}</td>
130                </tr>
131              </tbody>
132            </table>
133          </div>
134
135          <div className="flex space-x-4">
136            <a 
137              href={`/destinations/\${planetId}/\${moonId}/research\`}
138              className="px-6 py-3 bg-indigo-600 text-white rounded-lg hover:bg-indigo-700 transition-colors"
139            >
140              Badania naukowe
141            </a>
142            <a 
143              href={`/destinations/\${planetId}/\${moonId}/landing-sites\`}
144              className="px-6 py-3 border border-indigo-600 text-indigo-600 rounded-lg hover:bg-indigo-50 transition-colors"
145            >
146              Miejsca lądowań
147            </a>
148          </div>
149        </div>
150      </div>
151    </div>
152  );
153}
154
155export async function generateStaticParams() {
156  // Generujemy wszystkie możliwe kombinacje planet i ich księżyców
157  return [
158    { planetId: 'mars', moonId: 'phobos' },
159    { planetId: 'mars', moonId: 'deimos' },
160    { planetId: 'jupiter', moonId: 'europa' },
161    { planetId: 'jupiter', moonId: 'io' },
162    { planetId: 'jupiter', moonId: 'ganymede' }
163  ];
164}

W tym przykładzie:

  1. Komponent otrzymuje dwa parametry:
    planetId
    i
    moonId
  2. Używamy obu parametrów do pobrania danych o konkretnym księżycu konkretnej planety
  3. Implementujemy nawigację z powrotem do strony planety
  4. Generujemy statycznie wszystkie znane kombinacje planet i księżyców

Parametry typu catch-all i opcjonalne catch-all

W Metropolii Quantum, niektóre linie transportowe są tak elastyczne, że mogą docierać do dowolnej liczby punktów wzdłuż trasy, pobierając wszystkie istotne dane o trasie jako parametry podróży. W Next.js 15 odpowiednikami są segmenty typu catch-all i opcjonalne catch-all.

Segmenty catch-all ([...param])

Segment catch-all, oznaczony jako

[...param]
, dopasowuje ścieżkę o dowolnej liczbie segmentów. Wszystkie dopasowane segmenty są przekazywane jako tablica.

1app/
2  ├── stellar-objects/
3  │   ├── [...path]/           # matches /stellar-objects/any/number/of/segments
4  │   │   └── page.tsx         
5  │   └── page.tsx              # matches /stellar-objects

Implementacja strony z segmentem catch-all:

1// app/stellar-objects/[...path]/page.tsx
2type CatchAllParams = {
3  params: Promise<{
4    path: string[];
5  }>;
6};
7
8export default async function StellarObjectsPage({ params }: CatchAllParams) {
9  const { path } = await params;
10
11  return (
12    <div className="container mx-auto px-4 py-12">
13      <h1 className="text-4xl font-bold mb-8">Przeglądarka obiektów kosmicznych</h1>
14      
15      <div className="bg-gray-100 p-4 rounded mb-8">
16        <p className="font-mono">Ścieżka: /{path.join('/')}</p>
17        <p>Segmenty: {path.length}</p>
18        <ul className="list-disc pl-6 mt-2">
19          {path.map((segment, index) => (
20            <li key={index}>Segment {index + 1}: {segment}</li>
21          ))}
22        </ul>
23      </div>
24      
25      {/* Tutaj normalna zawartość strony... */}
26    </div>
27  );
28}

Przykładowy URL:

/stellar-objects/stars/main-sequence/g-type
zostanie dopasowany do tej strony, a
params.path
będzie tablicą
["stars", "main-sequence", "g-type"]
.

Opcjonalne segmenty catch-all ([...param])

Opcjonalny segment catch-all, oznaczony jako

[[...param]]
(podwójne nawiasy kwadratowe), działa podobnie do standardowego catch-all, ale dopasowuje również ścieżkę bez dodatkowych segmentów.

1app/
2  ├── knowledge-base/
3      ├── [[...topic]]/         # matches /knowledge-base, /knowledge-base/planets, /knowledge-base/planets/terrestrial, etc.
4          └── page.tsx

Implementacja:

1// app/knowledge-base/[[...topic]]/page.tsx
2type OptionalCatchAllParams = {
3  params: Promise<{
4    topic?: string[];
5  }>;
6};
7
8export default async function KnowledgeBasePage({ params }: OptionalCatchAllParams) {
9  const { topic } = await params;
10  
11  // Jeśli topic nie istnieje, to jesteśmy na głównej stronie bazy wiedzy
12  const isRoot = !topic || topic.length === 0;
13  
14  return (
15    <div className="container mx-auto px-4 py-12">
16      <h1 className="text-4xl font-bold mb-8">
17        {isRoot ? 'Baza wiedzy kosmicznej' : 'Temat: ' + topic.join(' > ')}
18      </h1>
19      
20      {isRoot ? (
21        <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
22          <TopicCard title="Planety" href="/knowledge-base/planets" />
23          <TopicCard title="Gwiazdy" href="/knowledge-base/stars" />
24          <TopicCard title="Galaktyki" href="/knowledge-base/galaxies" />
25          {/* Więcej kart tematów... */}
26        </div>
27      ) : (
28        <div>
29          <div className="bg-gray-100 p-4 rounded mb-8">
30            <p>Przeglądasz temat: <strong>{topic.join(' > ')}</strong></p>
31            
32            <div className="mt-4">
33              <a href="/knowledge-base" className="text-indigo-600 hover:underline">
34                Wróć do głównej strony bazy wiedzy
35              </a>
36            </div>
37          </div>
38          
39          {/* Zawartość konkretnego tematu... */}
40        </div>
41      )}
42    </div>
43  );
44}
45
46function TopicCard({ title, href }) {
47  return (
48    <a href={href} className="block p-6 bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow">
49      <h2 className="text-xl font-semibold mb-2">{title}</h2>
50      <p className="text-gray-600">Odkryj wiedzę o {title.toLowerCase()}.</p>
51    </a>
52  );
53}

Ta strona dopasuje zarówno

/knowledge-base
(główna strona bazy wiedzy), jak i
/knowledge-base/planets/terrestrial
(konkretny temat).

Parametry w Route Handlers

W Metropolii Quantum, nie tylko linie transportowe dla obywateli używają parametrów trasy - również zautomatyzowane systemy komunikują się ze sobą poprzez dynamicznie konfigurowane kanały. W Next.js 15, odpowiednikiem są Route Handlers, które również mogą wykorzystywać dynamiczne segmenty.

Dynamiczne segmenty w API Routes

1app/
2  ├── api/
3      ├── planets/
4          ├── [id]/
5          │   └── route.ts     # matches /api/planets/:id
6          └── route.ts         # matches /api/planets

Implementacja Route Handler z dynamicznym segmentem:

1// app/api/planets/[id]/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4// Typ dla parametrów
5type PlanetRouteContext = {
6  params: Promise<{
7    id: string;
8  }>;
9};
10
11// Przykładowe dane
12const planetData = {
13  'mars': { 
14    id: 'mars',
15    name: 'Mars',
16    type: 'terrestrial',
17    habitable: false
18  },
19  'earth': {
20    id: 'earth',
21    name: 'Ziemia',
22    type: 'terrestrial',
23    habitable: true
24  },
25  'jupiter': {
26    id: 'jupiter',
27    name: 'Jowisz',
28    type: 'gas-giant',
29    habitable: false
30  }
31};
32
33export async function GET(
34  request: NextRequest,
35  { params }: PlanetRouteContext
36) {
37  const { id } = await params;
38  
39  // Sprawdzenie, czy planeta istnieje
40  if (!planetData[id]) {
41    return NextResponse.json(
42      { error: 'Planeta nie znaleziona' },
43      { status: 404 }
44    );
45  }
46  
47  // Zwrócenie danych planety
48  return NextResponse.json(planetData[id]);
49}
50
51export async function PUT(
52  request: NextRequest,
53  { params }: PlanetRouteContext
54) {
55  const { id } = await params;
56  
57  // Sprawdzenie, czy planeta istnieje
58  if (!planetData[id]) {
59    return NextResponse.json(
60      { error: 'Planeta nie znaleziona' },
61      { status: 404 }
62    );
63  }
64  
65  try {
66    // Pobranie danych z żądania
67    const updateData = await request.json();
68    
69    // Aktualizacja danych planety (tu tylko symulacja)
70    // W rzeczywistej aplikacji zapisalibyśmy to do bazy danych
71    const updatedPlanet = {
72      ...planetData[id],
73      ...updateData
74    };
75    
76    // Zwrócenie zaktualizowanych danych
77    return NextResponse.json(updatedPlanet);
78  } catch (error) {
79    return NextResponse.json(
80      { error: 'Nieprawidłowe dane' },
81      { status: 400 }
82    );
83  }
84}
85
86export async function DELETE(
87  request: NextRequest,
88  { params }: PlanetRouteContext
89) {
90  const { id } = await params;
91  
92  // Sprawdzenie, czy planeta istnieje
93  if (!planetData[id]) {
94    return NextResponse.json(
95      { error: 'Planeta nie znaleziona' },
96      { status: 404 }
97    );
98  }
99  
100  // Usunięcie planety (tu tylko symulacja)
101  // W rzeczywistej aplikacji usunęlibyśmy to z bazy danych
102  
103  return NextResponse.json({ success: true });
104}

W tym przykładzie:

  1. Route Handler obsługuje żądania dla konkretnej planety określonej przez
    id
  2. Implementujemy metody GET, PUT i DELETE dla zarządzania danymi planety
  3. Parametr
    id
    jest dostępny przez obiekt
    params
    przekazywany do funkcji obsługujących

Pobieranie parametrów zapytania (query parameters)

W Metropolii Quantum, poza głównymi trasami, system transportowy może być dodatkowo konfigurowany przez opcjonalne parametry, takie jak "expressFlight" czy "comfortLevel". W świecie Next.js, te opcjonalne ustawienia są reprezentowane przez parametry zapytania (query parameters).

Dostęp do parametrów zapytania w Server Components

1// app/search/page.tsx
2import { Suspense } from 'react';
3
4// To jest komponent serwerowy
5export default async function SearchPage({
6  searchParams
7}: {
8  searchParams: Promise<{ [key: string]: string | string[] | undefined }>
9}) {
10  // Pobranie parametrów zapytania
11  const query = (await searchParams).q || '';
12  const category = (await searchParams).category || 'all';
13  const page = Number((await searchParams).page) || 1;
14  
15  return (
16    <div className="container mx-auto px-4 py-12">
17      <h1 className="text-4xl font-bold mb-8">Wyszukiwarka kosmiczna</h1>
18      
19      <div className="mb-8">
20        <p>Wyszukiwanie: <strong>{query}</strong></p>
21        <p>Kategoria: <strong>{category}</strong></p>
22        <p>Strona: <strong>{page}</strong></p>
23      </div>
24      
25      <Suspense fallback={<div>Ładowanie wyników...</div>}>
26        <SearchResults query={query} category={category} page={page} />
27      </Suspense>
28    </div>
29  );
30}
31
32// Ten komponent pobiera i wyświetla wyniki
33async function SearchResults({ 
34  query, 
35  category, 
36  page 
37}: { 
38  query: string | string[], 
39  category: string | string[], 
40  page: number 
41}) {
42  // Tutaj normalnie pobieralibyśmy dane na podstawie parametrów
43  // Symulacja opóźnienia dla demonstracji Suspense
44  await new Promise(resolve => setTimeout(resolve, 1000));
45  
46  // Jeśli nie ma zapytania, wyświetl komunikat
47  if (!query) {
48    return (
49      <div className="bg-gray-100 p-8 rounded-lg text-center">
50        <p className="text-lg">Wpisz frazę, aby rozpocząć wyszukiwanie.</p>
51      </div>
52    );
53  }
54  
55  // Tutaj normalnie wyświetlalibyśmy wyniki wyszukiwania
56  return (
57    <div className="space-y-6">
58      <div className="p-6 bg-white rounded-lg shadow">
59        <h2 className="text-xl font-semibold mb-2">Wynik 1</h2>
60        <p>Znaleziono dla: {query}</p>
61      </div>
62      <div className="p-6 bg-white rounded-lg shadow">
63        <h2 className="text-xl font-semibold mb-2">Wynik 2</h2>
64        <p>Znaleziono dla: {query}</p>
65      </div>
66      <div className="p-6 bg-white rounded-lg shadow">
67        <h2 className="text-xl font-semibold mb-2">Wynik 3</h2>
68        <p>Znaleziono dla: {query}</p>
69      </div>
70      
71      <div className="flex justify-center space-x-2 mt-8">
72        <a 
73          href={`/search?q=\${query}&category=\${category}&page=\${Math.max(1, page - 1)}\`}
74          className={`px-4 py-2 border rounded \${page === 1 ? 'text-gray-400 cursor-not-allowed' : 'text-indigo-600 hover:bg-indigo-50'}\`}
75        >
76          Poprzednia
77        </a>
78        <span className="px-4 py-2 bg-indigo-600 text-white rounded">
79          {page}
80        </span>
81        <a 
82          href={`/search?q=\${query}&category=\${category}&page=\${page + 1}\`}
83          className={`px-4 py-2 border rounded \${page === 1 ? 'text-gray-400 cursor-not-allowed' : 'text-indigo-600 hover:bg-indigo-50'}\`}
84        >
85          Następna
86        </a>
87      </div>
88    </div>
89  );
90}

W tym przykładzie:

  1. Komponent
    SearchPage
    otrzymuje
    searchParams
    jako props, zawierający wszystkie parametry zapytania z URL
  2. Wykorzystujemy te parametry do konfiguracji wyszukiwania
  3. Implementujemy komponent
    SearchResults
    , który pobiera dane na podstawie parametrów
  4. Używamy
    Suspense
    do obsługi stanu ładowania

Dostęp do parametrów zapytania w Route Handlers

1// app/api/search/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3
4export async function GET(request: NextRequest) {
5  // Pobranie URL z żądania
6  const { searchParams } = new URL(request.url);
7  
8  // Pobranie parametrów zapytania
9  const query = searchParams.get('q');
10  const category = searchParams.get('category') || 'all';
11  const page = Number(searchParams.get('page')) || 1;
12  const limit = Number(searchParams.get('limit')) || 10;
13  
14  // Sprawdzenie czy query istnieje
15  if (!query) {
16    return NextResponse.json(
17      { error: 'Parametr q (zapytanie) jest wymagany' },
18      { status: 400 }
19    );
20  }
21  
22  // Tutaj normalnie pobieralibyśmy dane z bazy danych
23  // Na potrzeby przykładu zwracamy symulowane dane
24  const results = Array.from({ length: limit }, (_, i) => ({
25    id: `result-\${(page - 1) * limit + i + 1}`,
26    title: `Wynik \${(page - 1) * limit + i + 1} dla "\${query}"`,
27    category,
28    relevance: Math.round(Math.random() * 100)
29  }));
30  
31  return NextResponse.json({
32    query,
33    category,
34    page,
35    limit,
36    totalResults: 100, // Symulowana całkowita liczba wyników
37    results
38  });
39}

W tym przykładzie:

  1. Używamy
    new URL(request.url).searchParams
    do pobrania parametrów zapytania
  2. Wyciągamy i walidujemy konkretne parametry
  3. Zwracamy dane odpowiednie dla podanych parametrów

Generowanie dynamicznych metadanych

W Metropolii Quantum, każda linia transportowa posiada dynamicznie generowane informacje dotyczące trasy, dostępne poprzez system informacyjny miasta. W Next.js 15, możemy generować dynamiczne metadane dla każdej strony, korzystając z funkcji

generateMetadata
.

1// app/destinations/[planetId]/page.tsx
2import { Metadata } from 'next';
3
4// Typ dla parametrów
5type PlanetParams = {
6  params: Promise<{
7    planetId: string;
8  }>;
9};
10
11// Funkcja generująca metadane na podstawie planetId
12export async function generateMetadata(
13  { params }: PlanetParams
14): Promise<Metadata> {
15  const { planetId } = await params;
16  
17  // Pobieranie danych o planecie
18  const planet = await getPlanetData(planetId);
19  
20  // Jeśli planeta nie istnieje, zwróć domyślne metadane
21  if (!planet) {
22    return {
23      title: 'Planeta nie znaleziona | Quantum Voyages',
24      description: 'Nie znaleziono informacji o żądanej planecie.'
25    };
26  }
27  
28  // Zwróć metadane specyficzne dla planety
29  return {
30    title: `\${planet.name} | Quantum Voyages`,
31    description: planet.description.substring(0, 160), // Ogranicz do 160 znaków dla SEO
32    openGraph: {
33      title: `Odkryj \${planet.name} z Quantum Voyages`,
34      description: planet.description.substring(0, 160),
35      images: [
36        {
37          url: `https://example.com\${planet.imageUrl}`,
38          width: 1200,
39          height: 630,
40          alt: planet.name
41        }
42      ]
43    }
44  };
45}
46
47// Reszta kodu komponentu...

W tym przykładzie:

  1. generateMetadata
    otrzymuje te same
    params
    co komponent strony
  2. Używamy
    planetId
    do pobrania danych o planecie
  3. Generujemy dynamiczne metadane, w tym tytuł, opis i dane dla Open Graph
  4. Metadane te są używane do zoptymalizowania SEO strony

Podsumowanie

Dynamiczne segmenty ścieżki i parametry w Next.js 15 to potężne narzędzia, które umożliwiają tworzenie elastycznych i interaktywnych aplikacji. Podobnie jak w Metropolii Quantum, gdzie system transportowy adaptuje się do potrzeb obywateli, tak dynamiczny routing w Next.js dostosowuje się do potrzeb użytkowników.

Kluczowe punkty do zapamiętania:

  1. Dynamiczne segmenty tworzy się poprzez umieszczenie nazwy parametru w nawiasach kwadratowych
    [param]
  2. Wartości parametrów są dostępne w komponentach strony poprzez prop
    params
  3. Segmenty catch-all (
    [...param]
    ) umożliwiają dopasowanie wielu segmentów ścieżki
  4. Opcjonalne segmenty catch-all (
    [[...param]]
    ) działają podobnie, ale dopasowują również ścieżkę bez dodatkowych segmentów
  5. Parametry zapytania są dostępne przez prop
    searchParams
    w komponentach strony
  6. Dynamiczne metadane mogą być generowane na podstawie parametrów ścieżki

Dzięki tym funkcjom, możesz tworzyć zaawansowane aplikacje z intuicyjną nawigacją i bogatymi funkcjami dopasowanymi do konkretnych potrzeb użytkownika.

W następnym rozdziale odkryjemy, jak efektywnie nawigować pomiędzy różnymi stronami w naszej aplikacji przy użyciu komponentu

Link
i hooka
useRouter
.

Vai a CodeWorlds