W Metropolii Quantum, centralny system transportowy działa na niezwykle intuicyjnej zasadzie: układ miasta odzwierciedla jego mapy. Każda dzielnica, każda stacja i każdy węzeł transportowy jest dokładnie tam, gdzie pokazuje to plan miasta. To prostota, która rewolucjonizuje sposób poruszania się po mieście.
Next.js 15 wprowadził podobną rewolucję w świecie aplikacji webowych, implementując system routingu oparty na plikach (file-based routing), który jest sercem Architektury App Router.
W Next.js 15, struktura katalogów i plików w Twoim projekcie bezpośrednio określa ścieżki dostępne w Twojej aplikacji. To tak, jakby plan Twojej aplikacji był jednocześnie jej mapą nawigacyjną.
Rdzeń Twojej aplikacji Next.js 15 rozpoczyna się od katalogu
app:1app/
2 ├── layout.js # Główny układ (shell) aplikacji
3 ├── page.js # Strona główna aplikacji (/)
4 ├── about/
5 │ └── page.js # Strona /about
6 ├── dashboard/
7 │ ├── layout.js # Układ dla sekcji dashboard
8 │ ├── page.js # Strona /dashboard
9 │ └── settings/
10 │ └── page.js # Strona /dashboard/settings
11 └── blog/
12 ├── page.js # Strona /blog
13 └── [slug]/
14 └── page.js # Strona /blog/:slug (dynamiczna)W tej strukturze, każdy katalog reprezentuje segment URL, a plik
page.js (lub page.tsx w przypadku TypeScript) w tym katalogu definiuje zawartość odpowiadającą temu URL.App Router wykorzystuje specjalne konwencje nazewnictwa plików, aby określić ich przeznaczenie:
| Plik | Zastosowanie | |--------------------|-------------------------------------------------------------| |
page.js | Strona (UI i dostęp do danych) |
| layout.js | Współdzielony układ dla segmentu i jego podścieżek |
| loading.js | Stan ładowania dla segmentu i jego podścieżek |
| error.js | Obsługa błędów dla segmentu i jego podścieżek |
| not-found.js | Obsługa przypadku, gdy zasób nie został znaleziony |
| route.js | Końcówka API serwera (nie renderuje UI) |Stwórzmy prosty przykład aplikacji Quantum Voyages dla Metropolii Quantum, która umożliwia eksplorację kosmosu:
app/page.tsx)1// app/page.tsx
2import Link from 'next/link';
3import { ArrowRight } from 'lucide-react'; // przykładowa importowana ikona
4
5export default function HomePage() {
6 return (
7 <div className="container mx-auto px-4 py-12">
8 <div className="text-center mb-12">
9 <h1 className="text-5xl font-bold text-gradient mb-4">
10 Quantum Voyages
11 </h1>
12 <p className="text-xl text-gray-600 mb-8">
13 Odkryj kosmos z przewodnikiem po Metropolii Quantum
14 </p>
15 <Link
16 href="/destinations"
17 className="px-6 py-3 bg-indigo-600 text-white rounded-full inline-flex items-center hover:bg-indigo-700 transition-colors"
18 >
19 Rozpocznij podróż
20 <ArrowRight className="ml-2 h-5 w-5" />
21 </Link>
22 </div>
23
24 <div className="grid grid-cols-1 md:grid-cols-3 gap-8">
25 <FeatureCard
26 title="Eksploracja planet"
27 description="Odkryj odległe światy i niezbadane terytoria."
28 />
29 <FeatureCard
30 title="Kosmiczne przewodniki"
31 description="Nasi doświadczeni przewodnicy zabiorą Cię w niesamowitą podróż."
32 />
33 <FeatureCard
34 title="Quantum Travel"
35 description="Innowacyjna technologia transportu międzygwiezdnego."
36 />
37 </div>
38 </div>
39 );
40}
41
42function FeatureCard({ title, description }) {
43 return (
44 <div className="p-6 border border-gray-200 rounded-lg shadow-sm hover:shadow-md transition-shadow">
45 <h3 className="text-xl font-semibold mb-2">{title}</h3>
46 <p className="text-gray-600">{description}</p>
47 </div>
48 );
49}app/destinations/page.tsx)1// app/destinations/page.tsx
2import Image from 'next/image';
3import Link from 'next/link';
4
5const destinations = [
6 {
7 id: 'mars',
8 name: 'Mars',
9 description: 'Czerwona planeta z potencjałem dla przyszłych kolonii.',
10 imageUrl: '/images/mars.jpg'
11 },
12 {
13 id: 'europa',
14 name: 'Europa',
15 description: 'Księżyc Jowisza z oceanem pod lodową skorupą.',
16 imageUrl: '/images/europa.jpg'
17 },
18 {
19 id: 'titan',
20 name: 'Tytan',
21 description: 'Największy księżyc Saturna z gęstą atmosferą.',
22 imageUrl: '/images/titan.jpg'
23 }
24];
25
26export default function DestinationsPage() {
27 return (
28 <div className="container mx-auto px-4 py-12">
29 <h1 className="text-4xl font-bold mb-8">Kosmiczne destynacje</h1>
30
31 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
32 {destinations.map((destination) => (
33 <Link
34 key={destination.id}
35 href={`/destinations/\${destination.id}\`}
36 className="block group"
37 >
38 <div className="rounded-lg overflow-hidden shadow-md hover:shadow-lg transition-shadow">
39 <div className="relative h-64">
40 <Image
41 src={destination.imageUrl}
42 alt={destination.name}
43 fill
44 className="object-cover transition-transform group-hover:scale-105"
45 />
46 </div>
47 <div className="p-4">
48 <h2 className="text-2xl font-semibold mb-2">{destination.name}</h2>
49 <p className="text-gray-600">{destination.description}</p>
50 </div>
51 </div>
52 </Link>
53 ))}
54 </div>
55 </div>
56 );
57}app/layout.tsx)1// app/layout.tsx
2import './globals.css';
3import { Inter } from 'next/font/google';
4import Header from '@/components/Header';
5import Footer from '@/components/Footer';
6
7const inter = Inter({ subsets: ['latin'] });
8
9export const metadata = {
10 title: 'Quantum Voyages - Podróże kosmiczne z Metropolii Quantum',
11 description: 'Odkryj kosmos z przewodnikiem po Metropolii Quantum',
12};
13
14export default function RootLayout({
15 children,
16}: {
17 children: React.ReactNode;
18}) {
19 return (
20 <html lang="pl">
21 <body className={inter.className}>
22 <Header />
23 <main className="min-h-screen pt-16">
24 {children}
25 </main>
26 <Footer />
27 </body>
28 </html>
29 );
30}W Metropolii Quantum, aby dotrzeć do Dystryktu Naukowego, a następnie do Obserwatorium Kosmicznego, musisz fizycznie przemieścić się przez odpowiednie sektory miasta. Podobnie w Next.js 15, ścieżka URL jest bezpośrednio mapowana do struktury katalogów:
/ → app/page.js/destinations → app/destinations/page.js/destinations/mars → app/destinations/mars/page.jsUkład miasta Metropolii Quantum jest hierarchiczny - główne aleje prowadzą do dzielnic, które zawierają mniejsze ulice i place. W podobny sposób, Next.js 15 umożliwia zagnieżdżanie układów (layouts):
1// app/destinations/layout.tsx
2export default function DestinationsLayout({
3 children,
4}: {
5 children: React.ReactNode;
6}) {
7 return (
8 <div className="destinations-layout">
9 <aside className="w-64 bg-gray-100 p-4 fixed h-full left-0 top-16">
10 <h3 className="font-bold text-lg mb-4">Kategorie</h3>
11 <ul className="space-y-2">
12 <li><a href="/destinations/planets">Planety</a></li>
13 <li><a href="/destinations/moons">Księżyce</a></li>
14 <li><a href="/destinations/space-stations">Stacje kosmiczne</a></li>
15 </ul>
16 </aside>
17 <div className="ml-64 p-6">
18 {children}
19 </div>
20 </div>
21 );
22}Ten układ będzie wspólny dla wszystkich stron w katalogu
destinations i jego podkatalogach, zapewniając spójne doświadczenie użytkownika w ramach tej sekcji.W Metropolii Quantum, obok dróg dla obywateli, istnieje również sieć połączeń dla systemów automatycznych i dronów. W Next.js 15, podobną funkcję pełnią trasy API:
1// app/api/destinations/route.ts
2import { NextResponse } from 'next/server';
3
4export async function GET() {
5 // Tu normalnie pobierałbyś dane z bazy danych
6 const destinations = [
7 { id: 'mars', name: 'Mars' },
8 { id: 'europa', name: 'Europa' },
9 { id: 'titan', name: 'Tytan' },
10 ];
11
12 return NextResponse.json(destinations);
13}
14
15export async function POST(request: Request) {
16 const data = await request.json();
17
18 // Walidacja i zapis do bazy danych...
19
20 return NextResponse.json({ success: true, id: 'new-destination-id' }, { status: 201 });
21}Powyższy kod tworzy endpoint API dostępny pod adresem
/api/destinations, który obsługuje metody GET i POST.W tradycyjnym mieście, większość infrastruktury jest statyczna i kontrolowana centralnie. W Metropolii Quantum, budynki i infrastruktura są inteligentne i interaktywne. App Router w Next.js 15 wprowadza podobną rewolucję z domyślnymi Server Components.
W Next.js 15, wszystkie komponenty w App Router są domyślnie Server Components. Oznacza to, że:
1// app/destinations/[id]/page.tsx
2import Image from 'next/image';
3import { notFound } from 'next/navigation';
4
5// Ta funkcja jest wykonywana na serwerze
6async function getDestinationData(id: string) {
7 // W rzeczywistej aplikacji, pobierałbyś dane z bazy lub API
8 const destinations = {
9 'mars': {
10 name: 'Mars',
11 description: 'Czerwona planeta z potencjałem dla przyszłych kolonii. Mars jest czwartą planetą od Słońca i drugą najmniejszą planetą w Układzie Słonecznym. Często nazywany "Czerwoną Planetą" ze względu na swój charakterystyczny kolor, Mars przyciąga uwagę jako potencjalne miejsce dla przyszłej kolonizacji ludzkiej.',
12 gravity: '3.721 m/s²',
13 atmosphere: 'Rzadka, głównie CO2',
14 imageUrl: '/images/mars.jpg'
15 },
16 'europa': {
17 name: 'Europa',
18 description: 'Księżyc Jowisza z oceanem pod lodową skorupą. Europa to szósty naturalny satelita Jowisza i jest jednym z największych księżyców w Układzie Słonecznym. Znana jest ze swojej gładkiej powierzchni pokrytej lodową skorupą i przypuszczeń, że pod nią znajduje się ogromny ocean wody.',
19 gravity: '1.315 m/s²',
20 atmosphere: 'Praktycznie brak, śladowa ilość tlenu',
21 imageUrl: '/images/europa.jpg'
22 },
23 'titan': {
24 name: 'Tytan',
25 description: 'Największy księżyc Saturna z gęstą atmosferą. Tytan to największy księżyc Saturna i drugi co do wielkości księżyc w Układzie Słonecznym. Jest jedynym księżycem ze znaczącą atmosferą, głównie azotu, oraz jedynym ciałem niebieskim poza Ziemią z ciekłymi jeziorami na powierzchni.',
26 gravity: '1.352 m/s²',
27 atmosphere: 'Gęsta, głównie azot',
28 imageUrl: '/images/titan.jpg'
29 }
30 };
31
32 return destinations[id];
33}
34
35export default async function DestinationPage({ params }: { params: Promise<{ id: string }> }) {
36 // Pobieranie danych na serwerze
37 const destination = await getDestinationData((await params).id);
38
39 // Obsługa przypadku, gdy destynacja nie istnieje
40 if (!destination) {
41 notFound();
42 }
43
44 return (
45 <div className="container mx-auto px-4 py-12">
46 <div className="grid grid-cols-1 md:grid-cols-2 gap-12">
47 <div className="relative h-96 md:h-full rounded-lg overflow-hidden">
48 <Image
49 src={destination.imageUrl}
50 alt={destination.name}
51 fill
52 className="object-cover"
53 />
54 </div>
55
56 <div>
57 <h1 className="text-4xl font-bold mb-4">{destination.name}</h1>
58 <p className="text-gray-700 mb-6">{destination.description}</p>
59
60 <div className="bg-gray-100 p-4 rounded-lg">
61 <h2 className="text-xl font-semibold mb-3">Dane planetarne</h2>
62 <ul className="space-y-2">
63 <li><strong>Grawitacja:</strong> {destination.gravity}</li>
64 <li><strong>Atmosfera:</strong> {destination.atmosphere}</li>
65 </ul>
66 </div>
67 </div>
68 </div>
69 </div>
70 );
71}W powyższym przykładzie, cała logika pobierania danych odbywa się na serwerze, a do klienta trafia tylko wygenerowany HTML. Jest to znacznie wydajniejsze niż podejście client-side.
Choć większość komponentów w Metropolii Quantum jest zarządzana centralnie, niektóre wymagają lokalnej interakcji z obywatelami. Podobnie w Next.js 15, gdy potrzebujesz interaktywnego komponentu po stronie klienta, używasz dyrektywy
"use client":1// components/DestinationSearch.tsx
2"use client";
3
4import { useState, useEffect } from 'react';
5import { useRouter } from 'next/navigation';
6
7type Destination = {
8 id: string;
9 name: string;
10};
11
12export default function DestinationSearch() {
13 const [query, setQuery] = useState('');
14 const [results, setResults] = useState<Destination[]>([]);
15 const router = useRouter();
16
17 useEffect(() => {
18 if (query.length > 2) {
19 // Symulacja zapytania do API
20 fetch(`/api/search?q=\${query}`)
21 .then(res => res.json())
22 .then(data => setResults(data));
23 } else {
24 setResults([]);
25 }
26 }, [query]);
27
28 function handleSelect(id: string) {
29 router.push(\`/destinations/\${id}`);
30 }
31
32 return (
33 <div className="relative">
34 <input
35 type="text"
36 value={query}
37 onChange={(e) => setQuery(e.target.value)}
38 className="w-full px-4 py-2 border rounded-lg"
39 />
40
41 {results.length > 0 && (
42 <div className="absolute top-full left-0 right-0 bg-white shadow-lg rounded-lg mt-1 z-10">
43 <ul>
44 {results.map((dest) => (
45 <li
46 key={dest.id}
47 onClick={() => handleSelect(dest.id)}
48 className="px-4 py-2 hover:bg-gray-100 cursor-pointer"
49 >
50 {dest.name}
51 </li>
52 ))}
53 </ul>
54 </div>
55 )}
56 </div>
57 );
58}Ten komponent ma dyrektywę
"use client" na samej górze, co oznacza, że będzie wykonywany po stronie klienta i może używać hooków Reacta oraz obsługiwać interakcje użytkownika.W Metropolii Quantum, pewne informacje, jak mapy stałych dzielnic miasta, mogą być przygotowane z wyprzedzeniem, podczas gdy inne, np. dane o aktualnym ruchu transportowym, muszą być pobierane w czasie rzeczywistym. App Router w Next.js 15 oferuje podobną elastyczność:
1// app/destinations/[id]/page.tsx
2
3// Ta funkcja określa, które ścieżki powinny być generowane statycznie
4export async function generateStaticParams() {
5 // W rzeczywistej aplikacji, pobierałbyś listę ID z bazy danych lub API
6 return [
7 { id: 'mars' },
8 { id: 'europa' },
9 { id: 'titan' }
10 ];
11}Dodając tę funkcję do naszego wcześniejszego przykładu, mówimy Next.js, aby wygenerował statyczne strony dla Mars, Europy i Tytana podczas budowania aplikacji.
Jeśli chcemy, aby strona była zawsze generowana przy każdym żądaniu, możemy użyć opcji
dynamic:1// app/destinations/[id]/page.tsx
2export const dynamic = 'force-dynamic';
3
4// Reszta kodu strony...System routingu oparty na plikach w Next.js 15 to rewolucyjne podejście, które sprawia, że tworzenie złożonych aplikacji staje się intuicyjne i wydajne. Podobnie jak w Metropolii Quantum, gdzie układ miasta jest odzwierciedlony w jego mapach, struktura katalogów w Next.js bezpośrednio określa dostępne ścieżki w aplikacji.
Kluczowe zalety tego podejścia to:
W kolejnym rozdziale, odkryjemy, jak wykorzystać dynamiczne segmenty ścieżki i parametry, aby tworzyć jeszcze bardziej zaawansowane i elastyczne trasy w naszej aplikacji Next.js 15.