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 15 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.
Plik
layout.tsx to jeden z najważniejszych plików specjalnych w App Router. 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.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
2export default function RootLayout({
3 children,
4}: {
5 children: React.ReactNode
6}) {
7 return (
8 <html lang="pl">
9 <body>
10 <nav className="quantum-nav">
11 <a href="/">Strona główna</a>
12 <a href="/dashboard">Dashboard</a>
13 <a href="/settings">Ustawienia</a>
14 </nav>
15 <main>{children}</main>
16 <footer>Metropolis Quantum © 2150</footer>
17 </body>
18 </html>
19 );
20}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.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
2export default function DashboardLayout({
3 children,
4}: {
5 children: React.ReactNode
6}) {
7 return (
8 <div className="dashboard-container">
9 <aside className="sidebar">
10 <h2>Panel Quantum</h2>
11 <ul>
12 <li><a href="/dashboard">Przegląd</a></li>
13 <li><a href="/dashboard/analytics">Analityka</a></li>
14 <li><a href="/dashboard/settings">Ustawienia</a></li>
15 </ul>
16 </aside>
17 <section className="content">
18 {children}
19 </section>
20 </div>
21 );
22}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.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 { useState } from 'react';
5
6export default function DashboardLayout({
7 children,
8}: {
9 children: React.ReactNode
10}) {
11 // Ten stan PRZETRWA nawigację między /dashboard/* stronami!
12 const [sidebarOpen, setSidebarOpen] = useState(true);
13
14 return (
15 <div className="flex">
16 {sidebarOpen && (
17 <aside className="w-64 bg-gray-900 p-4">
18 <h2>Panel Quantum</h2>
19 <nav>
20 <a href="/dashboard">Przegląd</a>
21 <a href="/dashboard/analytics">Analityka</a>
22 </nav>
23 </aside>
24 )}
25 <div className="flex-1">
26 <button onClick={() => setSidebarOpen(!sidebarOpen)}>
27 {sidebarOpen ? 'Ukryj' : 'Pokaż'} sidebar
28 </button>
29 {children}
30 </div>
31 </div>
32 );
33}Jeśli użytkownik zamknie sidebar na stronie
/dashboard i przejdzie do /dashboard/analytics, sidebar pozostanie zamknięty — stan sidebarOpen nie jest resetowany.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.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:
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/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: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}Layouty i loading UI to fundamentalne koncepcje App Router w Next.js 15:
<html> i <body>.<Suspense> z loading jako fallback.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.