interface LayoutProps {
children: React.ReactNode;
modal: React.ReactNode;
}
export default function RootLayout({
children,
modal,
}: LayoutProps) {
return (
<html lang="ro">
<body>
<main className="container">
{children}
</main>
{/* Slotul de modal va fi randat doar când ruta este interceptată */}
{modal}
</body>
</html>
);
}Am implementat recent schema asta pe un portofoliu de arhitectură cu peste 12.000 de randări high-res. Clientul voia neapărat ca utilizatorul să dea click pe o poză, să se deschidă un modal superb, URL-ul să devină /photos/123, iar dacă omul dă share la acel link, destinatarul să vadă direct pagina full, nu modalul peste listă.
În Next.js Pages Router făceam niște hack-uri oribile cu state-ul și history.pushState. În App Router avem Parallel Routes (@modal) și Intercepting Routes ((.)photos), dar implementarea lor vine la pachet cu niște subtilități care îți pot mânca ușor două nopți de debug.
Cum funcționează magia sub capotă?
Ideea e simplă în teorie. Vrem ca Next.js să randeze două lucruri în același timp în același layout.
- Parallel Routes ne permit să pasăm mai multe pagini ca proprietăți în același layout. Pe lângă
{children}, layout-ul tău va primi și o prop numită{modal}. - Intercepting Routes îi spun routerului: „Când userul dă click pe un link către
/photos/123din interiorul aplicației, nu te duce acolo. Interceptează ruta și randează conținutul din folderul special(.)photos/[id]în slotul de modal”.
Dacă userul dă refresh direct pe URL-ul /photos/123, interceptarea nu se mai produce (pentru că nu e o navigare pe client), iar Next.js randează ruta normală /photos/[id]/page.tsx ca pagină de sine stătătoare. UX-ul e genial, dar structura de fișiere devine rapid o varză totală.
Structura de foldere care te va induce în eroare
Ca să meargă din prima, structura ta de fișiere în /app trebuie să arate fix așa:
app/
├── @modal/
│ ├── (.)photos/
│ │ └── [id]/
│ │ └── page.tsx <-- Conținutul modalului (randat pe client)
│ └── default.tsx <-- IMPORTANT: Returnează null
├── photos/
│ └── [id]/
│ └── page.tsx <-- Pagina normală (la refresh sau share)
├── layout.tsx <-- Primeste {children} si {modal}
└── page.tsx <-- Pagina principală cu lista de poze
Cel mai des am văzut oameni care uită de default.tsx. Dacă nu pui default.tsx în rădăcina folderului @modal care să returneze null, Next.js va arunca o eroare 404 la primul refresh pe o pagină care nu folosește modalul. De ce? Pentru că Next încearcă să găsească ce să randeze în slotul @modal când ești pe /despre-noi, de exemplu, și nu găsește nimic.
Trade-off-ul sincer: de ce nu e totul roz
Deși am redus bounce rate-ul cu 18% pe portofoliul respectiv pentru că navigarea era incredibil de fluidă, am dat de câteva probleme mari.
În primul rând, starea modalului devine greu de gestionat dacă ai animații de închidere (cum ar fi cele din Framer Motion). Când dai înapoi în browser, Next.js dezactivează ruta interceptată instantaneu, iar modalul tău dispare brusc, fără să mai apuce să ruleze animația de fade-out.
În al doilea rând, debug-ul este un calvar. Dacă ai layout-uri imbricate adânc, determinarea nivelului corect pentru interceptare — adică dacă folosești (.) pentru același nivel, (..) pentru nivelul superior sau (...) pentru rădăcină — se transformă rapid într-un joc de noroc.
Cum vi se pare abordarea asta? Ați reușit să o implementați în producție fără să vă blestemați zilele cu randarea pe server vs. client?