Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

System routingu oparty na plikach

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.

Architektura 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ą.

Podstawowa struktura katalogów

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.

Konwencje plików

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) |

Prosty przykład aplikacji

Stwórzmy prosty przykład aplikacji Quantum Voyages dla Metropolii Quantum, która umożliwia eksplorację kosmosu:

1. Strona główna (
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}

2. Strona z destynacjami (
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}

3. Dodanie wspólnego układu (
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}

Jak działa system routingu w Next.js 15?

Wyrażenie ścieżek poprzez strukturę folderów

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.js

Zagnieżdżanie układów (layouts)

Ukł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.

Routing dla API

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.

Server Components w App Router

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.

Domyślne renderowanie po stronie serwera

W Next.js 15, wszystkie komponenty w App Router są domyślnie Server Components. Oznacza to, że:

  1. Są renderowane na serwerze
  2. Nie wysyłają JavaScript do klienta
  3. Mogą bezpośrednio korzystać z zasobów serwerowych (np. baz danych)
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.

Używanie "use client" dla interaktywnych komponentów

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.

Generowanie statyczne i dynamiczne

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ść:

Statyczne generowanie stron (Static Generation)

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.

Dynamiczne generowanie stron (Dynamic Generation)

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...

Podsumowanie

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:

  1. Intuicyjna struktura - struktura aplikacji jest bezpośrednio widoczna w organizacji plików
  2. Zero konfiguracji - nie musisz definiować routingu w osobnym pliku
  3. Hierarchiczne layouty - współdzielone układy UI dla różnych sekcji aplikacji
  4. Domyślne Server Components - wydajniejsze renderowanie i optymalne przesyłanie danych
  5. Elastyczne generowanie - możliwość wyboru między statycznym a dynamicznym generowaniem

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.

Przejdź do CodeWorlds