W sercu Metropolii Quantum, system zarządzania przestrzenią miejską opiera się na koncepcji dzielnic tematycznych i stref funkcjonalnych. Miasto podzielone jest na rozpoznawalne obszary - dzielnicę naukową, strefę mieszkalną, dzielnicę rozrywkową i wiele innych. Te strefy są od siebie oddzielone, ale współdziałają jako elementy jednego, spójnego organizmu miejskiego.
W podobny sposób, w Next.js 15, możemy organizować i grupować trasy naszej aplikacji, aby zwiększyć przejrzystość kodu, usprawnić zarządzanie i umożliwić bardziej elastyczne strategie ładowania i izolacji komponentów.
W App Router, możemy tworzyć grupy tras, które nie mają wpływu na faktyczną strukturę URL, umieszczając katalog w nawiasach, na przykład:
(nazwaGrupy). To potężna funkcja, która pozwala na logiczne organizowanie kodu bez zmiany jego zachowania czy publicznego API.Grupy tras są definiowane przez umieszczenie nazwy katalogu w nawiasach:
1app/
2 ├── (marketing)/ # Grupa dla stron marketingowych
3 │ ├── about/
4 │ │ └── page.tsx # /about
5 │ ├── blog/
6 │ │ └── page.tsx # /blog
7 │ └── layout.tsx # Współdzielony układ dla stron marketingowych
8 │
9 ├── (shop)/ # Grupa dla stron sklepowych
10 │ ├── products/
11 │ │ └── page.tsx # /products
12 │ ├── cart/
13 │ │ └── page.tsx # /cart
14 │ └── layout.tsx # Współdzielony układ dla stron sklepowych
15 │
16 └── layout.tsx # Główny układ aplikacjiW tym przykładzie, trasy
/about i /blog są częścią grupy (marketing), podczas gdy /products i /cart są częścią grupy (shop). Zauważ, że grupy tras nie wpływają na faktyczne URL-e - katalog (marketing) nie jest częścią ścieżki URL /about.Najprostszym zastosowaniem grup tras jest logiczna organizacja plików, zwłaszcza w większych aplikacjach:
1app/
2 ├── (main)/ # Główne strony publiczne
3 │ ├── page.tsx # Strona główna (/)
4 │ ├── about/ # /about
5 │ └── contact/ # /contact
6 │
7 ├── (auth)/ # Strony związane z uwierzytelnianiem
8 │ ├── login/ # /login
9 │ ├── register/ # /register
10 │ └── forgot-password/ # /forgot-password
11 │
12 ├── (dashboard)/ # Strony dashboardu (chronione)
13 │ ├── account/ # /account
14 │ ├── settings/ # /settings
15 │ └── billing/ # /billing
16 │
17 └── layout.tsx # Główny układJednym z najpotężniejszych zastosowań grup tras jest możliwość definiowania wspólnych układów dla określonych grup stron bez wpływu na ścieżki URL:
1// app/(marketing)/layout.tsx - Układ dla stron marketingowych
2export default function MarketingLayout({
3 children,
4}: {
5 children: React.ReactNode;
6}) {
7 return (
8 <div className="marketing-layout">
9 <nav className="marketing-nav">
10 <a href="/about">O nas</a>
11 <a href="/blog">Blog</a>
12 <a href="/careers">Kariera</a>
13 </nav>
14 <main>{children}</main>
15 <footer className="marketing-footer">
16 <p>© 2150 Quantum Voyages. Wszystkie prawa zastrzeżone.</p>
17 </footer>
18 </div>
19 );
20}1// app/(shop)/layout.tsx - Układ dla stron sklepowych
2export default function ShopLayout({
3 children,
4}: {
5 children: React.ReactNode;
6}) {
7 return (
8 <div className="shop-layout">
9 <header className="shop-header">
10 <h1>Quantum Voyages Store</h1>
11 <nav className="shop-nav">
12 <a href="/products">Produkty</a>
13 <a href="/categories">Kategorie</a>
14 <a href="/cart">Koszyk</a>
15 </nav>
16 </header>
17 <main>{children}</main>
18 <aside className="shop-sidebar">
19 <div className="popular-products">
20 <h3>Popularne produkty</h3>
21 {/* Lista popularnych produktów */}
22 </div>
23 </aside>
24 </div>
25 );
26}Grupy tras są szczególnie przydatne w aplikacjach wielosekcyjnych, takich jak aplikacja z publiczną stroną główną, panelem administracyjnym i dashboardem użytkownika:
1app/
2 ├── (public)/ # Publiczna część witryny
3 │ ├── page.tsx # Strona główna (/)
4 │ ├── about/ # /about
5 │ ├── blog/ # /blog
6 │ │ └── [slug]/ # /blog/:slug
7 │ └── layout.tsx # Układ publiczny
8 │
9 ├── (admin)/ # Panel administracyjny
10 │ ├── admin/ # /admin (uwaga: potrzebny katalog admin/ dla zachowania URL)
11 │ │ ├── page.tsx # /admin
12 │ │ ├── users/ # /admin/users
13 │ │ └── settings/ # /admin/settings
14 │ └── layout.tsx # Układ admin
15 │
16 ├── (dashboard)/ # Dashboard użytkownika
17 │ ├── dashboard/ # /dashboard
18 │ │ ├── page.tsx # /dashboard
19 │ │ ├── profile/ # /dashboard/profile
20 │ │ └── settings/ # /dashboard/settings
21 │ └── layout.tsx # Układ dashboard
22 │
23 └── layout.tsx # Główny układ (wspólny dla wszystkich sekcji)W tym przykładzie, aplikacja jest podzielona na trzy główne sekcje, każda z własnym układem, ale wszystkie dzielą wspólny układ główny.
Zaimplementujmy system grup tras dla naszej kosmicznej aplikacji Quantum Voyages:
1app/
2 ├── (marketing)/ # Strony marketingowe
3 │ ├── page.tsx # Strona główna (/)
4 │ ├── about/ # /about
5 │ ├── destinations/ # /destinations
6 │ │ └── [id]/ # /destinations/:id
7 │ └── layout.tsx # Układ marketingowy
8 │
9 ├── (customer)/ # Strony dla zalogowanych klientów
10 │ ├── dashboard/ # /dashboard
11 │ ├── bookings/ # /bookings
12 │ │ └── [id]/ # /bookings/:id
13 │ ├── account/ # /account
14 │ └── layout.tsx # Układ dla klientów
15 │
16 ├── (admin)/ # Panel administracyjny
17 │ ├── admin/ # /admin
18 │ │ ├── voyages/ # /admin/voyages
19 │ │ ├── customers/ # /admin/customers
20 │ │ └── analytics/ # /admin/analytics
21 │ └── layout.tsx # Układ administracyjny
22 │
23 └── layout.tsx # Główny układKod dla układów:
1// app/layout.tsx - Główny układ
2import { Inter } from 'next/font/google';
3import './globals.css';
4
5const inter = Inter({ subsets: ['latin'] });
6
7export const metadata = {
8 title: 'Quantum Voyages - Podróże kosmiczne z Metropolii Quantum',
9 description: 'Odkryj kosmos z przewodnikiem po Metropolii Quantum',
10};
11
12export default function RootLayout({
13 children,
14}: {
15 children: React.ReactNode;
16}) {
17 return (
18 <html lang="pl">
19 <body className={inter.className}>
20 {children}
21 </body>
22 </html>
23 );
24}1// app/(marketing)/layout.tsx - Układ marketingowy
2import Header from '@/components/marketing/Header';
3import Footer from '@/components/marketing/Footer';
4
5export default function MarketingLayout({
6 children,
7}: {
8 children: React.ReactNode;
9}) {
10 return (
11 <>
12 <Header />
13 <main className="min-h-screen pt-16 pb-20">
14 {children}
15 </main>
16 <Footer />
17 </>
18 );
19}1// app/(customer)/layout.tsx - Układ dla zalogowanych klientów
2import CustomerHeader from '@/components/customer/Header';
3import CustomerSidebar from '@/components/customer/Sidebar';
4import { checkAuth } from '@/lib/auth';
5import { redirect } from 'next/navigation';
6
7export default async function CustomerLayout({
8 children,
9}: {
10 children: React.ReactNode;
11}) {
12 // Sprawdzenie, czy użytkownik jest zalogowany
13 const user = await checkAuth();
14 if (!user) {
15 redirect('/login?returnTo=' + encodeURIComponent(window.location.pathname));
16 }
17
18 return (
19 <>
20 <CustomerHeader user={user} />
21 <div className="flex min-h-screen pt-16">
22 <CustomerSidebar />
23 <main className="flex-1 p-6 bg-gray-50">
24 {children}
25 </main>
26 </div>
27 </>
28 );
29}1// app/(admin)/layout.tsx - Układ administracyjny
2import AdminHeader from '@/components/admin/Header';
3import AdminSidebar from '@/components/admin/Sidebar';
4import { checkAdminAuth } from '@/lib/auth';
5import { redirect } from 'next/navigation';
6
7export default async function AdminLayout({
8 children,
9}: {
10 children: React.ReactNode;
11}) {
12 // Sprawdzenie, czy użytkownik jest administratorem
13 const isAdmin = await checkAdminAuth();
14 if (!isAdmin) {
15 redirect('/admin-login');
16 }
17
18 return (
19 <div className="admin-layout">
20 <AdminHeader />
21 <div className="flex min-h-screen pt-16">
22 <AdminSidebar />
23 <main className="flex-1 p-6 bg-gray-100">
24 {children}
25 </main>
26 </div>
27 </div>
28 );
29}W Next.js 15, możemy także tworzyć grupy opcji, które służą wyłącznie do organizacji kodu, bez żadnego wpływu na logikę routingu czy ładowania:
1app/
2 ├── (onboarding)/welcome/
3 │ └── page.tsx # /welcome
4 └── (payments)/cart/
5 └── page.tsx # /cartGrupy tras są szczególnie przydatne, gdy chcemy współdzielić układ między specyficznymi trasami, które niekoniecznie są blisko siebie w hierarchii URL:
1app/
2 ├── (auth)/
3 │ ├── login/ # /login
4 │ │ └── page.tsx
5 │ ├── register/ # /register
6 │ │ └── page.tsx
7 │ └── layout.tsx # Układ współdzielony tylko dla /login i /register
8 │
9 ├── account/ # /account (nie używa układu (auth))
10 │ └── page.tsx
11 │
12 └── layout.tsx # Główny układ dla wszystkich trasNext.js 15 wprowadza również konwencję prywatnych folderów, które są oznaczone podkreślnikiem (_) na początku nazwy. Takie foldery nie są uwzględniane w ścieżkach URL, ale mogą zawierać kod współdzielony przez wiele tras.
1app/
2 ├── _components/ # Prywatny folder dla komponentów współdzielonych
3 │ ├── Button.tsx # Komponent przycisku
4 │ ├── Card.tsx # Komponent karty
5 │ └── SearchForm.tsx # Komponent formularza wyszukiwania
6 │
7 ├── _lib/ # Prywatny folder dla współdzielonych funkcji
8 │ ├── api.ts # Funkcje API
9 │ └── utils.ts # Funkcje narzędziowe
10 │
11 ├── destinations/ # Publiczna trasa
12 │ └── page.tsx # /destinations
13 │
14 └── page.tsx # Strona główna (/)W tym przykładzie, katalogi
_components i _lib są prywatne i nie generują żadnych ścieżek URL. Są one używane do przechowywania współdzielonych komponentów i funkcji, które mogą być importowane przez inne komponenty.Prywatne foldery i grupy tras wyglądają podobnie, ale mają różne zastosowania:
_folderName) są używane do organizowania kodu, który nie powinien być bezpośrednio dostępny przez URL.(folderName)) są używane do grupowania tras bez wpływu na ścieżki URL, ale mogą wpływać na zachowanie układów.1app/
2 ├── _components/ # Współdzielone komponenty UI
3 │ ├── ui/ # Podstawowe komponenty UI
4 │ │ ├── Button.tsx
5 │ │ ├── Card.tsx
6 │ │ └── Input.tsx
7 │ │
8 │ ├── marketing/ # Komponenty dla stron marketingowych
9 │ │ ├── HeroSection.tsx
10 │ │ └── TestimonialCard.tsx
11 │ │
12 │ └── bookings/ # Komponenty dla systemu rezerwacji
13 │ ├── BookingForm.tsx
14 │ └── PlanetSelector.tsx
15 │
16 ├── _lib/ # Współdzielone funkcje i utilities
17 │ ├── api/ # Funkcje do komunikacji z API
18 │ │ ├── destinations.ts
19 │ │ └── bookings.ts
20 │ │
21 │ ├── hooks/ # Własne hooki React
22 │ │ ├── useBooking.ts
23 │ │ └── useAuth.ts
24 │ │
25 │ └── utils/ # Funkcje pomocnicze
26 │ ├── dates.ts
27 │ └── validation.ts
28 │
29 ├── _data/ # Dane statyczne (można też użyć /public)
30 │ ├── destinations.json
31 │ └── testimonials.json
32 │
33 ├── (marketing)/ # Grupa dla stron marketingowych
34 │ ├── page.tsx # Strona główna (/)
35 │ ├── about/ # /about
36 │ └── destinations/ # /destinations
37 │
38 └── (customer)/ # Grupa dla stron klienta
39 ├── dashboard/ # /dashboard
40 └── bookings/ # /bookingsPrzykład kodu z wykorzystaniem prywatnych folderów:
1// app/_components/ui/Button.tsx
2import { cva, type VariantProps } from 'class-variance-authority';
3import { cn } from '@/app/_lib/utils/cn';
4
5const buttonVariants = cva(
6 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
7 {
8 variants: {
9 variant: {
10 default: 'bg-primary text-primary-foreground hover:bg-primary/90',
11 destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
12 outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
13 secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
14 ghost: 'hover:bg-accent hover:text-accent-foreground',
15 link: 'text-primary underline-offset-4 hover:underline',
16 },
17 size: {
18 default: 'h-10 px-4 py-2',
19 sm: 'h-9 rounded-md px-3',
20 lg: 'h-11 rounded-md px-8',
21 icon: 'h-10 w-10',
22 },
23 },
24 defaultVariants: {
25 variant: 'default',
26 size: 'default',
27 },
28 }
29);
30
31export interface ButtonProps
32 extends React.ButtonHTMLAttributes<HTMLButtonElement>,
33 VariantProps<typeof buttonVariants> {
34 asChild?: boolean;
35}
36
37export function Button({
38 className,
39 variant,
40 size,
41 asChild = false,
42 ...props
43}: ButtonProps) {
44 return (
45 <button
46 className={cn(buttonVariants({ variant, size, className }))}
47 {...props}
48 />
49 );
50}1// app/(marketing)/destinations/page.tsx
2import { getDestinations } from '@/app/_lib/api/destinations';
3import DestinationCard from '@/app/_components/marketing/DestinationCard';
4import { HeroSection } from '@/app/_components/marketing/HeroSection';
5
6export default async function DestinationsPage() {
7 const destinations = await getDestinations();
8
9 return (
10 <div className="container mx-auto px-4 py-12">
11 <HeroSection
12 title="Odkryj kosmos z Quantum Voyages"
13 subtitle="Przeglądaj nasze kosmiczne destynacje i zaplanuj swoją przygodę"
14 image="/images/space-banner.jpg"
15 />
16
17 <section className="my-12">
18 <h2 className="text-3xl font-bold mb-8">Popularne destynacje</h2>
19
20 <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
21 {destinations.map((destination) => (
22 <DestinationCard
23 key={destination.id}
24 destination={destination}
25 />
26 ))}
27 </div>
28 </section>
29 </div>
30 );
31}Next.js 15 umożliwia tworzenie API endpoints przy użyciu plików
route.js (lub route.ts). Również dla tych plików możemy stosować techniki organizacyjne, takie jak grupy tras i prywatne foldery.1app/
2 ├── api/ # Główna sekcja API
3 │ ├── (public)/ # Publiczne endpointy
4 │ │ ├── destinations/
5 │ │ │ └── route.ts # GET /api/destinations
6 │ │ └── newsletter/
7 │ │ └── route.ts # POST /api/newsletter
8 │ │
9 │ ├── (protected)/ # Chronione endpointy (wymagają autoryzacji)
10 │ │ ├── bookings/
11 │ │ │ ├── route.ts # GET, POST /api/bookings
12 │ │ │ └── [id]/
13 │ │ │ └── route.ts # GET, PUT, DELETE /api/bookings/:id
14 │ │ └── user/
15 │ │ └── route.ts # GET /api/user
16 │ │
17 │ └── _middleware/ # Middleware dla API (prywatny folder)
18 │ ├── auth.ts # Middleware uwierzytelniania
19 │ └── rateLimit.ts # Middleware ograniczenia liczby zapytań
20 │
21 └── page.tsx # Strona główna (/)Przykład implementacji chronionego endpointu API:
1// app/api/(protected)/bookings/route.ts
2import { NextResponse } from 'next/server';
3import { verifyAuth } from '@/app/api/_middleware/auth';
4import { rateLimit } from '@/app/api/_middleware/rateLimit';
5
6export async function GET(request: Request) {
7 // Weryfikacja uwierzytelnienia
8 const authResult = await verifyAuth(request);
9 if (!authResult.isAuthenticated) {
10 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
11 }
12
13 // Ograniczenie liczby zapytań
14 const rateLimitResult = await rateLimit(request);
15 if (!rateLimitResult.success) {
16 return NextResponse.json({ error: 'Too many requests' }, { status: 429 });
17 }
18
19 // Pobranie rezerwacji dla zalogowanego użytkownika
20 const userId = authResult.user.id;
21 const bookings = await getBookingsForUser(userId);
22
23 return NextResponse.json({ bookings });
24}
25
26export async function POST(request: Request) {
27 // Weryfikacja uwierzytelnienia
28 const authResult = await verifyAuth(request);
29 if (!authResult.isAuthenticated) {
30 return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
31 }
32
33 // Parsowanie danych z żądania
34 const data = await request.json();
35
36 // Walidacja danych
37 const validationResult = validateBookingData(data);
38 if (!validationResult.success) {
39 return NextResponse.json({ error: validationResult.errors }, { status: 400 });
40 }
41
42 // Tworzenie rezerwacji
43 const userId = authResult.user.id;
44 const booking = await createBooking(userId, data);
45
46 return NextResponse.json({ booking }, { status: 201 });
47}W Metropolii Quantum, szczegółowe planowanie jest kluczowe dla efektywnego funkcjonowania miasta. Podobnie, w większych aplikacjach Next.js 15, ważne jest przestrzeganie określonych zasad i wzorców organizacji tras:
Utrzymuj konsekwentną strukturę katalogów i nazewnictwo w całej aplikacji:
1app/
2 ├── (sections)/ # Grupy dla głównych sekcji
3 │ ├── (marketing)/ # Publiczna część
4 │ ├── (app)/ # Główna aplikacja
5 │ └── (admin)/ # Panel admina
6 │
7 ├── _lib/ # Współdzielony kod
8 │ ├── components/ # Współdzielone komponenty
9 │ ├── hooks/ # Hooki React
10 │ └── utils/ # Funkcje narzędziowe
11 │
12 ├── api/ # Endpointy API
13 │ ├── (public)/
14 │ └── (protected)/
15 │
16 └── layout.tsx # Główny układUżywaj prywatnych folderów do przechowywania współdzielonych komponentów i umieszczaj je na odpowiednim poziomie hierarchii:
app/_components/app/(section)/_components/page.js - Renderowanie strony i pobieranie danychlayout.js - Układ UI i logika nawigacjiloading.js - Stany ładowaniaerror.js - Obsługa błędównot-found.js - Obsługa zasobów nie znalezionychSkonfiguruj aliasy importów dla uproszczenia ścieżek:
1{
2 "compilerOptions": {
3 "baseUrl": ".",
4 "paths": {
5 "@/*": ["./*"],
6 "@components/*": ["./app/_components/*"],
7 "@lib/*": ["./app/_lib/*"],
8 "@hooks/*": ["./app/_lib/hooks/*"],
9 "@utils/*": ["./app/_lib/utils/*"]
10 }
11 }
12}Dzięki temu możesz importować komponenty w bardziej czytelny sposób:
1// Zamiast:
2import Button from '../../../_components/ui/Button';
3
4// Możesz użyć:
5import Button from '@components/ui/Button';Przykład kompleksowej struktury dla dużej aplikacji Quantum Voyages:
1app/
2 ├── (public)/ # Publiczna część witryny
3 │ ├── _components/ # Komponenty specyficzne dla części publicznej
4 │ ├── page.tsx # Strona główna (/)
5 │ ├── about/ # /about
6 │ ├── destinations/ # /destinations
7 │ │ ├── page.tsx # Lista destynacji
8 │ │ └── [id]/ # /destinations/:id
9 │ │ └── page.tsx # Szczegóły destynacji
10 │ ├── contact/ # /contact
11 │ └── layout.tsx # Układ publiczny
12 │
13 ├── (auth)/ # Sekcja uwierzytelniania
14 │ ├── _components/ # Komponenty uwierzytelniania
15 │ ├── _lib/ # Logika uwierzytelniania
16 │ ├── login/ # /login
17 │ ├── register/ # /register
18 │ ├── forgot-password/ # /forgot-password
19 │ └── layout.tsx # Układ uwierzytelniania
20 │
21 ├── (app)/ # Główna aplikacja (dla zalogowanych)
22 │ ├── _components/ # Komponenty aplikacji
23 │ ├── dashboard/ # /dashboard
24 │ ├── bookings/ # /bookings
25 │ │ ├── page.tsx # Lista rezerwacji
26 │ │ ├── new/ # /bookings/new
27 │ │ └── [id]/ # /bookings/:id
28 │ │ ├── page.tsx # Szczegóły rezerwacji
29 │ │ └── edit/ # /bookings/:id/edit
30 │ ├── account/ # /account
31 │ │ ├── page.tsx # Przegląd konta
32 │ │ ├── profile/ # /account/profile
33 │ │ └── preferences/ # /account/preferences
34 │ └── layout.tsx # Układ aplikacji
35 │
36 ├── (admin)/ # Panel administracyjny
37 │ ├── _components/ # Komponenty admina
38 │ ├── admin/ # /admin (potrzebny folder dla URL)
39 │ │ ├── page.tsx # Dashboard admina
40 │ │ ├── users/ # /admin/users
41 │ │ ├── destinations/ # /admin/destinations
42 │ │ └── bookings/ # /admin/bookings
43 │ └── layout.tsx # Układ admina
44 │
45 ├── _components/ # Współdzielone komponenty
46 │ ├── ui/ # Podstawowe komponenty UI
47 │ └── forms/ # Komponenty formularzy
48 │
49 ├── _lib/ # Współdzielone funkcje
50 │ ├── api/ # Funkcje API
51 │ ├── hooks/ # Własne hooki
52 │ └── utils/ # Funkcje narzędziowe
53 │
54 ├── api/ # Endpointy API
55 │ ├── (public)/ # Publiczne API
56 │ └── (protected)/ # Chronione API
57 │
58 └── layout.tsx # Główny układGrupowanie i organizacja tras w Next.js 15 oferuje wiele możliwości strukturyzowania aplikacji w logiczny i przejrzysty sposób, podobnie jak zoning urbanistryczny w Metropolii Quantum organizuje przestrzeń miejską dla maksymalnej wydajności i funkcjonalności.
Kluczowe koncepcje:
Grupy tras (
(groupName)) - pozwalają na logiczne grupowanie tras bez wpływu na ścieżki URL, co jest szczególnie przydatne do dzielenia różnych sekcji aplikacji i implementacji specyficznych układów.Prywatne foldery (
_folderName) - umożliwiają organizację kodu, współdzielonych komponentów i funkcji, które nie są bezpośrednio dostępne przez URL.Hierarchiczne układy - możliwość definiowania specyficznych układów dla różnych grup tras, zapewniając spójne doświadczenie użytkownika w ramach każdej sekcji.
Organizacja API - stosowanie tych samych zasad organizacji dla endpointów API.
Dzięki tym technikom można tworzyć aplikacje, które są:
W następnym rozdziale, odkryjemy jak obsługiwać bardziej złożone scenariusze nawigacji, takie jak trasy zagnieżdżone i parametry catch-all, które umożliwiają tworzenie zaawansowanych i elastycznych systemów nawigacji w aplikacjach Next.js 15.