Kurs Next.js · Moduł 2: Routing i layouty
Loading UI i layout.tsx
W tej lekcji6
W Metropolii Quantum każdy dystrykt posiada stałą infrastrukturę - oświetlenie, systemy nawigacyjne, panele informacyjne - która pozostaje aktywna niezależnie od tego, kto aktualnie przebywa w danej strefie. Kiedy mieszkaniec przemieszcza się między sektorami, holograficzne ekrany wyświetlają animację ładowania, podczas gdy otaczająca infrastruktura pozostaje niezmieniona.
W Next.js 16 App Router te koncepcje mają swoje odpowiedniki: layout.tsx to stała infrastruktura, która otacza strony i nie jest ponownie renderowana przy nawigacji, a loading.tsx to automatyczny wskaźnik ładowania wyświetlany podczas przejść między stronami.
Czym jest layout.tsx?
Plik layout.tsx to jeden z najważniejszych plików specjalnych w App Routerze. Definiuje on współdzielony układ (shared layout), który otacza strony w danym segmencie i wszystkich jego podsegmentach. Najważniejsza cecha layoutu: nie jest ponownie renderowany przy nawigacji między stronami, które współdzielą ten sam layout. To jak infrastruktura dystryktu Quantum - pozostaje na miejscu, zmienia się tylko zawartość wewnątrz.
Root Layout - obowiązkowy layout główny
Każda aplikacja Next.js musi posiadać Root Layout w pliku app/layout.tsx. Jest to jedyny layout, który musi zawierać tagi <html> i <body>:
1// app/layout.tsx - Root Layout
2import Link from 'next/link';
3
4export default function RootLayout({
5 children,
6}: {
7 children: React.ReactNode
8}) {
9 return (
10 <html lang="pl">
11 <body>
12 <nav className="quantum-nav">
13 <Link href="/">Strona główna</Link>
14 <Link href="/dashboard">Dashboard</Link>
15 <Link href="/settings">Ustawienia</Link>
16 </nav>
17 <main>{children}</main>
18 <footer>Metropolis Quantum © 2150</footer>
19 </body>
20 </html>
21 );
22}Linki w layoutach to komponenty Link z next/link. Zwykły <a href> przeładowałby całą stronę: layout zamontowałby się od nowa i straciłby stan.
Root Layout jest renderowany raz i pozostaje aktywny przez cały czas życia aplikacji. Nawigacja między /dashboard a /settings zmieni tylko zawartość {children} - nawigacja i stopka pozostaną na miejscu bez ponownego renderowania.
Zagnieżdżone layouty (Nested Layouts)
Layouty można zagnieżdżać na dowolnym poziomie struktury folderów. Każdy segment ścieżki może mieć swój własny layout.tsx:
1app/
2 ├── layout.tsx # Root Layout (html + body + nawigacja)
3 ├── page.tsx # Strona główna /
4 └── dashboard/
5 ├── layout.tsx # Dashboard Layout (sidebar + panel)
6 ├── page.tsx # /dashboard
7 ├── analytics/
8 │ └── page.tsx # /dashboard/analytics
9 └── settings/
10 └── page.tsx # /dashboard/settingsDashboard Layout zagnieżdża się wewnątrz Root Layout:
1// app/dashboard/layout.tsx - Zagnieżdżony layout
2import Link from 'next/link';
3
4export default function DashboardLayout({
5 children,
6}: {
7 children: React.ReactNode
8}) {
9 return (
10 <div className="dashboard-container">
11 <aside className="sidebar">
12 <h2>Panel Quantum</h2>
13 <ul>
14 <li><Link href="/dashboard">Przegląd</Link></li>
15 <li><Link href="/dashboard/analytics">Analityka</Link></li>
16 <li><Link href="/dashboard/settings">Ustawienia</Link></li>
17 </ul>
18 </aside>
19 <section className="content">
20 {children}
21 </section>
22 </div>
23 );
24}Kiedy użytkownik nawiguje z /dashboard do /dashboard/analytics, Root Layout i Dashboard Layout nie są ponownie renderowane. Zmienia się tylko {children} w Dashboard Layout. To kluczowa optymalizacja wydajności - stan komponentów layoutu (np. otwarty sidebar, pozycja scrolla) jest zachowany.
Zachowanie stanu layoutu przy nawigacji
Jedną z największych zalet layoutów jest zachowanie stanu przy nawigacji. Wyobraź sobie panel kontrolny w Metropolii Quantum - kiedy przełączasz się między sekcjami, nie chcesz, żeby cały interfejs się przeładował:
1// app/dashboard/layout.tsx
2'use client';
3
4import Link from 'next/link';
5import { useState } from 'react';
6
7export default function DashboardLayout({
8 children,
9}: {
10 children: React.ReactNode
11}) {
12 // Ten stan PRZETRWA nawigację między /dashboard/* stronami!
13 const [sidebarOpen, setSidebarOpen] = useState(true);
14
15 return (
16 <div className="flex">
17 {sidebarOpen && (
18 <aside className="w-64 bg-gray-900 p-4">
19 <h2>Panel Quantum</h2>
20 <nav>
21 <Link href="/dashboard">Przegląd</Link>
22 <Link href="/dashboard/analytics">Analityka</Link>
23 </nav>
24 </aside>
25 )}
26 <div className="flex-1">
27 <button onClick={() => setSidebarOpen(!sidebarOpen)}>
28 {sidebarOpen ? 'Ukryj' : 'Pokaż'} sidebar
29 </button>
30 {children}
31 </div>
32 </div>
33 );
34}Jeśli użytkownik zamknie sidebar na stronie /dashboard i przejdzie do /dashboard/analytics, sidebar pozostanie zamknięty - stan sidebarOpen nie jest resetowany.
loading.tsx - automatyczne stany ładowania
Plik loading.tsx to specjalny plik, który Next.js automatycznie wyświetla jako fallback podczas ładowania zawartości strony. To jak holograficzny ekran ładowania w Metropolii Quantum - pojawia się automatycznie, gdy system przygotowuje dane.
Jak działa loading.tsx?
1// app/dashboard/loading.tsx
2export default function DashboardLoading() {
3 return (
4 <div className="loading-screen">
5 <div className="spinner"></div>
6 <p>Ładowanie danych panelu Quantum...</p>
7 </div>
8 );
9}Next.js automatycznie opakowuje page.tsx w granicę <Suspense>, używając loading.tsx jako fallback:
1// To jest to, co Next.js robi "pod maską":
2<Layout>
3 <Suspense fallback={<Loading />}>
4 <Page />
5 </Suspense>
6</Layout>Dzięki temu:
- Layout jest wyświetlany natychmiast (nie czeka na dane strony)
- Komponent loading pojawia się automatycznie podczas ładowania
- Strona zastępuje loading, gdy dane są gotowe
- Użytkownik widzi responsywny interfejs zamiast pustego ekranu
loading.tsx na różnych poziomach
Każdy segment może mieć własny loading.tsx:
1app/
2 ├── loading.tsx # Loading dla strony głównej
3 ├── dashboard/
4 │ ├── loading.tsx # Loading dla /dashboard/*
5 │ ├── analytics/
6 │ │ └── loading.tsx # Loading specyficzny dla analityki
7 │ └── settings/
8 │ └── page.tsx # Użyje loading z /dashboard/template.tsx vs layout.tsx
Next.js oferuje jeszcze jeden specjalny plik - template.tsx. Wygląda identycznie jak layout.tsx, ale ma jedną kluczową różnicę: template jest ponownie montowany (tworzona jest nowa instancja) przy każdej nawigacji.
1// app/dashboard/template.tsx
2// Nowa instancja przy KAŻDEJ nawigacji!
3export default function DashboardTemplate({
4 children,
5}: {
6 children: React.ReactNode
7}) {
8 return (
9 <div className="animate-fadeIn">
10 {children}
11 </div>
12 );
13}Kiedy używać template.tsx zamiast layout.tsx?
| Cecha | layout.tsx | template.tsx |
|---|---|---|
| Ponowne renderowanie | NIE - zachowuje stan | TAK - nowa instancja |
| Stan komponentu | Zachowany między nawigacjami | Resetowany przy nawigacji |
| useEffect | Nie uruchamia się ponownie | Uruchamia się przy każdej nawigacji |
| Animacje wejścia | Nie powtarzają się | Powtarzają się przy każdym wejściu |
Przykłady użycia template.tsx:
- Animacje wejścia/wyjścia na stronach (fade-in przy każdej nawigacji)
- Logowanie wyświetleń strony (useEffect z analytics przy każdym wejściu)
- Formularze, które powinny się resetować przy nawigacji
Praktyczne zastosowania
Nawigacja z aktywnym stanem
1// app/dashboard/layout.tsx
2'use client';
3
4import Link from 'next/link';
5import { usePathname } from 'next/navigation';
6
7export default function DashboardLayout({
8 children,
9}: {
10 children: React.ReactNode
11}) {
12 const pathname = usePathname();
13
14 const links = [
15 { href: '/dashboard', label: 'Przegląd' },
16 { href: '/dashboard/analytics', label: 'Analityka' },
17 { href: '/dashboard/settings', label: 'Ustawienia' },
18 ];
19
20 return (
21 <div className="dashboard">
22 <nav className="dashboard-nav">
23 {links.map((link) => (
24 <Link
25 key={link.href}
26 href={link.href}
27 className={pathname === link.href ? 'active' : ''}
28 >
29 {link.label}
30 </Link>
31 ))}
32 </nav>
33 <main>{children}</main>
34 </div>
35 );
36}Podsumowanie
Layouty i loading UI to fundamentalne koncepcje App Router w Next.js 16:
- layout.tsx - współdzielony układ, który otacza strony i nie jest ponownie renderowany przy nawigacji. Zachowuje stan komponentów.
- Root Layout - obowiązkowy główny layout z tagami
<html>i<body>. - Zagnieżdżone layouty - każdy segment może mieć swój layout, tworząc hierarchię układów.
- loading.tsx - automatyczny wskaźnik ładowania. Next.js opakowuje stronę w
<Suspense>z loading jako fallback. - template.tsx - jak layout, ale tworzy nową instancję przy każdej nawigacji. Idealne do animacji i logowania.
Te mechanizmy, inspirowane stałą infrastrukturą dystryktów Metropolii Quantum, pozwalają budować wydajne i responsywne interfejsy, gdzie użytkownik nigdy nie widzi pustego ekranu, a nawigacja jest płynna i natychmiastowa.
Kod do tej lekcji: app/layout.tsx
1// Root Layout - Metropolis Quantum
2import { ReactNode } from 'react';
3import Link from 'next/link';
4
5interface RootLayoutProps {
6 children: ReactNode;
7}
8
9export const metadata = {
10 title: 'Metropolis Quantum',
11 description: 'Loading UI i layout.tsx',
12};
13
14export default function RootLayout({ children }: RootLayoutProps) {
15 return (
16 <html lang="pl">
17 <body style={{ margin: 0, fontFamily: 'monospace', background: '#0f0f23', color: '#e0e0e0' }}>
18 <nav style={{
19 display: 'flex',
20 gap: '16px',
21 padding: '12px 20px',
22 background: '#1a1a2e',
23 borderBottom: '1px solid #64ffda',
24 }}>
25 <Link href="/" style={{ color: '#64ffda', textDecoration: 'none' }}>Strona główna</Link>
26 <Link href="/dashboard" style={{ color: '#bd93f9', textDecoration: 'none' }}>Dashboard</Link>
27 <Link href="/settings" style={{ color: '#ff79c6', textDecoration: 'none' }}>Ustawienia</Link>
28 </nav>
29 <main style={{ padding: '20px' }}>
30 {children}
31 </main>
32 <footer style={{
33 textAlign: 'center',
34 padding: '12px',
35 borderTop: '1px solid #16213e',
36 color: '#8892b0',
37 fontSize: '12px',
38 }}>
39 Metropolis Quantum © 2150
40 </footer>
41 </body>
42 </html>
43 );
44}Widzisz błąd w tej lekcji?