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.
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.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.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:
PlanetPageParams, który określa strukturę parametrów otrzymywanych przez komponentPlanetPage otrzymuje obiekt params zawierający planetId z URLplanetId do pobrania danych o planecieJeś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.
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.
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 /destinationsW tej strukturze mamy dwa poziomy dynamicznych segmentów:
[planetId] - identyfikator planety[moonId] - identyfikator księżyca danej planety1// 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:
planetId i moonIdW 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.
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-objectsImplementacja 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"].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.tsxImplementacja:
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).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.
1app/
2 ├── api/
3 ├── planets/
4 ├── [id]/
5 │ └── route.ts # matches /api/planets/:id
6 └── route.ts # matches /api/planetsImplementacja 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:
idid jest dostępny przez obiekt params przekazywany do funkcji obsługującychW 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).
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:
SearchPage otrzymuje searchParams jako props, zawierający wszystkie parametry zapytania z URLSearchResults, który pobiera dane na podstawie parametrówSuspense do obsługi stanu ładowania1// 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:
new URL(request.url).searchParams do pobrania parametrów zapytaniaW 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:
generateMetadata otrzymuje te same params co komponent stronyplanetId do pobrania danych o planecieDynamiczne 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:
[param]params[...param]) umożliwiają dopasowanie wielu segmentów ścieżki[[...param]]) działają podobnie, ale dopasowują również ścieżkę bez dodatkowych segmentówsearchParams w komponentach stronyDzię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.