Rute Paralele și Rute de Interceptare în Next.js 16: Modaluri, Dashboarduri și Pattern-uri Avansate
Rute paralele și de interceptare în Next.js 16: ghid practic pentru modaluri cu URL partajabil, dashboarduri cu streaming independent și pattern-uri avansate App Router cu exemple de cod complete.
Rutele paralele și rutele de interceptare în Next.js 16 sunt două convenții ale App Router care îți permit să afișezi mai multe pagini simultan în aceeași machetă și să suprascrii o rută într-un context specific (de exemplu, deschizând o fotografie ca modal peste feed în loc să navighezi la o pagină nouă). Folosind dosare prefixate cu @slot pentru rute paralele și convențiile (.), (..), (...) pentru interceptare, obții dashboard-uri cu stări de încărcare independente și pattern-uri de tip "modal cu URL partajabil" fără biblioteci externe.
Rutele paralele folosesc dosare @slot care devin props în layout.tsx, fiecare cu propriul loading.tsx și error.tsx.
Fiecare slot are nevoie de un fișier default.tsx ca fallback când URL-ul nu se potrivește, altfel apare eroare 404 la refresh.
Interceptarea folosește prefixele (.) (același nivel), (..) (un nivel mai sus), (..)(..) (două niveluri) și (...) (de la root).
Pattern-ul "modal cu URL partajabil" combină o rută paralelă @modal cu o rută de interceptare (..)photo/[id] pentru a deschide conținut în context.
Hook-ul useSelectedLayoutSegment(slotName) permite randare condițională a slot-urilor în layout-ul părinte.
În Next.js 16, rutele paralele beneficiază de Partial Prerendering și streaming nativ, livrând shell-ul static înainte de slot-urilor dinamice.
Ce sunt rutele paralele în Next.js 16?
Pe scurt, rutele paralele permit randarea simultană a mai multor pagini independente în aceeași machetă, fiecare cu propriul flux de date, stare de încărcare și gestionare a erorilor. În App Router, un slot e pur și simplu un dosar al cărui nume începe cu simbolul @, de exemplu @team sau @analytics. Aceste slot-uri nu apar în URL; ele devin props ale fișierului layout.tsx aflat la același nivel, alături de prop-ul standard children.
Diferența esențială față de simpla compunere a componentelor e că fiecare slot menține propriul său sub-arbore de rutare. Asta înseamnă că poți avea două secțiuni ale aceleiași pagini care răspund independent la navigare, suspendare și erori. Pentru un dashboard cu mai mulți widget-i, vine cu un câștig clar: un grafic lent nu blochează afișarea listei de utilizatori, fiecare se transmite prin streaming când datele sale sunt gata.
Conform documentației oficiale Next.js, rutele paralele se combină foarte bine cu Suspense și streaming, ducând la îmbunătățiri vizibile ale metricii LCP atunci când o pagină agregează date din mai multe surse cu latențe diferite. Dacă vrei un refresher pe streaming, citește mai întâi articolul despre streaming și Suspense în Next.js 16.
Configurarea slot-urilor și layout-ului părinte
Să presupunem că vrei un dashboard cu două panouri: unul cu echipa curentă și unul cu analytics. Structura dosarelor arată cam așa:
Slot-urile sunt randate de Next.js exact ca children: poți să le treci prin <Suspense>, să le condiționezi pe rol sau să le ascunzi în funcție de starea utilizatorului. Important: TypeScript nu generează automat tipurile pentru slot-uri, așa că trebuie să le declari manual în signature-ul componentei layout. (Da, e enervant. Sper că se va schimba într-o versiune viitoare.)
De ce ai nevoie de default.tsx în fiecare slot
Cea mai frecventă greșeală la rutele paralele este omiterea fișierului default.tsx. Mi s-a întâmplat și mie în primul proiect pe care l-am livrat cu App Router și am pierdut vreo două ore încercând să înțeleg de ce un refresh strica totul. Când utilizatorul navighează soft (client-side) între pagini, Next.js păstrează starea slot-urilor care nu se potrivesc cu URL-ul curent. Dar la un refresh full-page sau la navigare directă la un URL, Next.js trebuie să decidă ce să randeze pentru slot-urile care nu au o rută explicită pentru acel URL.
Fără default.tsx, randarea se sparge cu eroarea 404 sau cu un mesaj despre "missing default". Soluția: creezi un fișier de fallback minimalist în fiecare slot.
// app/dashboard/@team/default.tsx
export default function Default() {
return null; // sau o stare goală
}
Pentru pattern-uri de tip modal e recomandat să returnezi null, astfel încât slot-ul să fie complet invizibil când URL-ul nu îl activează. Pentru dashboard-uri, returnează o stare goală cu schelet (skeleton) sau un mesaj informativ. Issue-urile din repository-ul Next.js sunt pline de bug-uri raportate care, în 80% din cazuri, se rezolvă adăugând default.tsx.
Cum funcționează rutele de interceptare?
Rutele de interceptare îți permit să "prinzi" o navigare către o anumită rută și să o randezi într-un context diferit, fără a schimba URL-ul. Convenția folosește prefixele paranteză plasate înainte de numele dosarului destinație:
(.) interceptează rute de la același nivel
(..) interceptează rute de la un nivel mai sus
(..)(..) interceptează rute de la două niveluri mai sus
(...) interceptează rute de la rădăcina app
Notează că aceste niveluri se referă la segmentele rutei, nu la sistemul de fișiere. Cu alte cuvinte, un grup de rute precum (marketing) nu se numără ca segment. Greșeala asta produce erorile cele mai obscure: dezvoltatorii cred că (..) înseamnă "dosarul părinte" și se întreabă de ce nu funcționează interceptarea când părintele este un route group.
Combinate cu rute paralele, rutele de interceptare permit construirea modalelor cu URL-uri partajabile. Când userul dă click pe o miniatură, se deschide un modal cu detalii. Dacă reîncarcă pagina, vede pagina full a fotografiei. Acesta e pattern-ul Instagram clasic și se poate construi nativ în Next.js 16 fără useState sau biblioteci.
Acum, când userul navighează de la / către /photo/42 printr-un <Link>, Next.js interceptează ruta și randează @modal/(..)photo/[id]/page.tsx peste pagina principală. URL-ul devine /photo/42. Dacă userul reîncarcă, interceptarea nu se mai aplică (nu există context de navigare client-side), așa că randează pagina completă app/photo/[id]/page.tsx. Frumos, nu?
În app/@modal/default.tsx returnează null ca să nu apară nimic pe alte URL-uri. Pentru o tratare detaliată a Server Components asincrone care primesc params: Promise, vezi ghidul pentru Server Actions și formulare.
Dashboard cu loading și error states independente
Marele câștig al rutelor paralele într-un dashboard real e izolarea defectelor. Dacă API-ul de analytics răspunde în 2 secunde, iar API-ul de echipă în 200ms, vrei ca lista de echipă să apară imediat și analytics-ul să se transmită prin streaming când e gata.
În Next.js 16, fiecare slot beneficiază automat de Partial Prerendering: shell-ul static (cardurile cu titluri) este prerederat la build time, iar conținutul dinamic se transmite prin streaming. Conform anunțului oficial Next.js 16 de pe Vercel, această combinație reduce TTFB cu până la 70% pentru pagini cu mai multe surse de date.
Notă importantă: dacă slot-urile partajează date (de exemplu, ambele au nevoie de userul curent), folosește cache() din React sau noile cache APIs din Next.js 16 ca să nu duplici cererea. Pentru detalii, vezi ghidul pentru Cache Components.
Randare condițională cu useSelectedLayoutSegment
Uneori vrei să ascunzi un slot complet în funcție de starea aplicației. De exemplu, să nu randezi @analytics dacă userul n-are permisiune. Hook-ul useSelectedLayoutSegment primește numele slot-ului și returnează segmentul activ sau null:
'use client';
import { useSelectedLayoutSegment } from 'next/navigation';
export function ModalShell({ children }: { children: React.ReactNode }) {
const segment = useSelectedLayoutSegment('modal');
if (segment === null) return null;
return <dialog open>{children}</dialog>;
}
Acest pattern e esențial pentru a evita randări duble când modalul este închis. Notează că hook-ul funcționează doar în componente Client; dacă layout-ul este Server Component, trebuie să-l împachetezi într-un componente client dedicat.
Capcane frecvente și cum să le eviți
După ce am rulat această configurație în zeci de proiecte, iată cele mai comune capcane pe care le-am întâlnit:
Lipsa default.tsx, cauza #1 a erorilor 404 la refresh. Adaugă unul în fiecare slot, chiar dacă returnează null.
Numărarea greșită a nivelurilor de interceptare: (..) se referă la segmente de rută, NU la dosare. Grupurile (marketing) și slot-urile @modal nu se numără.
Folosirea unui Link fără prefetch: interceptarea funcționează doar pe navigare client-side. Asigură-te că folosești <Link> din next/link și nu <a> simplu.
Server Actions într-un slot interceptat: dacă modalul conține un formular, pe redirect folosește revalidatePath pentru ambele rute (interceptată și completă).
Suprapunerea stărilor de eroare: dacă atât slot-ul cât și layout-ul părinte au error.tsx, eroarea slot-ului este capturată local și nu propagă în părinte. E bine pentru izolare, dar surprinzător dacă te bazezi pe error boundaries globale.
O greșeală subtilă în Next.js 16: params și searchParams sunt acum Promises și în slot-uri. Trebuie să le aștepți cu await înainte de a accesa proprietățile. (Da, e o schimbare breaking care a prins multă lume nepregătită.)
Întrebări frecvente
Care este diferența între rute paralele și grupuri de rute?
Grupurile de rute (dosare în paranteze rotunde, ex. (marketing)) sunt doar o convenție de organizare a fișierelor: nu apar în URL și nu schimbă comportamentul rutării. Rutele paralele (dosare @slot) creează arbori de rutare independenți care sunt randați simultan în același layout, cu loading și error states separate.
Cum închid un modal creat cu rute de interceptare?
Folosește router.back() dintr-un Client Component pentru a închide modalul revenind la pagina anterioară din istoric. Alternativ, folosește un <Link> care navighează către URL-ul de bază. Evită useState local pentru vizibilitate, deoarece URL-ul este sursa de adevăr.
Pot folosi rute paralele cu Server Components?
Da, slot-urile pot fi atât Server Components (default) cât și Client Components. Fiecare slot are propriul boundary de Suspense, deci poți combina async Server Components cu loading.tsx pentru streaming. Pentru hook-uri precum useSelectedLayoutSegment ai nevoie de un Client Component dedicat.
De ce nu se păstrează slot-ul după refresh?
Pentru că starea slot-urilor este client-side. La refresh full-page, Next.js folosește URL-ul curent pentru a determina ce să randeze și se bazează pe default.tsx pentru slot-urile fără match. Pattern-ul recomandat este să folosești ruta completă (non-interceptată) ca fallback la refresh.
Funcționează rutele de interceptare cu Partial Prerendering?
Da, dar slot-urile interceptate sunt tratate ca dinamice implicit deoarece depind de contextul navigării. Shell-ul static al paginii părinte este prerederat, iar slot-ul interceptat se transmite prin streaming. Asigură-te că folosești <Suspense> și un loading.tsx în slot pentru a evita blocarea inițială.
Marcus picked up React in 2017 and has been writing it professionally ever since. Five years at Stripe on the Dashboard team, where he led the migration of the billing UI off a legacy webpack 4 setup onto Next.js with module federation for the embedded merchant components. He shipped the first internal RSC prototype there in late 2023, which mostly worked.
Now he's a staff engineer at a logistics startup in Austin, owning the customer-facing Next.js app and the design system that backs it. He spends a disproportionate amount of time thinking about route group conventions, parallel routes for modal UX, and why next/image keeps surprising people on Cloudflare's image resizing.
His writing favors annotated code over prose. Seven years of React, two of those deep in App Router land.
Ghid practic pentru Turbopack în Next.js 16: cum activezi build-urile de producție, migrezi de la Webpack, configurezi loadere SVG/GraphQL, filesystem caching în CI și Subresource Integrity nativ. Cu benchmark-uri reale și capcane din producție.
Ghid complet pentru gestionarea erorilor în Next.js 16: error.tsx, not-found.tsx, global-error.tsx, Server Actions și integrare Sentry, cu exemple practice.
Construiește o API REST completă cu Route Handlers în Next.js 16: GET/POST/PATCH/DELETE, validare Zod, autentificare prin cookies, caching opt-in, Edge Runtime și streaming SSE.