eduardweb.
App RouterAvansat#architecture#nextjs#react#frontend#app-router

Parallel Routes și Intercepting Routes în Next.js: Modalul Fără Refresh

De Bogdan Răducanu, 28 iul. 2026 · 10 vizualizări · 2 like-uri

Postat 28 iul. 2026
typescript
// app/layout.tsx
import React from 'react';

interface LayoutProps {
  children: React.ReactNode;
  modal: React.ReactNode;
}

export default function RootLayout({ children, modal }: LayoutProps) {
  return (
    <html lang="ro">
      <body className="bg-slate-900 text-white min-h-screen">
        {/* Pagina principală */}
        <main className="container mx-auto p-4">{children}</main>
        
        {/* Slotul paralel unde Next.js va injecta modalul interceptat */}
        {modal}
      </body>
    </html>
  );
}

Dacă ai încercat vreodată să faci un modal stil Instagram sau Pinterest în Next.js App Router — unde URL-ul se schimbă în /photos/123, dar pagina din spate rămâne intactă — știi deja că soluțiile clasice cu useState sunt o cârpeală. Am implementat pattern-ul ăsta la o platformă de e-commerce cu vreo 12k vizite zilnice pe galeria de produse și am scăpat de 200ms de re-render inutil pe layout-ul părinte. Câștigi navigare instantanee, SEO ok și URL-uri pe care le poți da mai departe fără să rupi UX-ul.

Structura de dosare care îți prinde urechile

Chestia asta e super confuză la început din cauza convențiilor de denumire din App Router. Ca să meargă din prima, ai nevoie de două concepte combinate: Slot-uri Paralele (Parallel Routes) și Rute Interceptate (Intercepting Routes).

Să zicem că ai pagina principală în app/page.tsx și pagina dedicată a pozei în app/photos/[id]/page.tsx. Ca să pui modalul peste pagina curentă când utilizatorul dă click pe o poză, faci un slot @modal în interiorul aceluiași layout.

Calea devine: app/@modal/(.)photos/[id]/page.tsx. Paranteza (.) îi spune lui Next.js: "când navighezi din client (cu <Link>) către /photos/[id], nu încărca pagina întreagă, ci interceptează ruta și arunc-o în slotul @modal".

Ce faci cu default.tsx și de ce crapă aplicația fără el

Aici m-am lovit eu prima dată. Dacă nu pui un fișier default.tsx în app/@modal/, Next.js o să-ți arunce un 404 sau un crash urât la hărți când dai hard refresh sau când navighezi pe o rută unde slotul @modal nu are potrivire.

default.tsx este fallback-ul pentru sloturile paralele atunci când routerul nu găsește nicio potrivire activă pe slotul respectiv. Când nu e niciun modal deschis, default.tsx trebuie să returneze pur și simplu null:

export default function DefaultModal() {
  return null;
}

Fără fișierul ăsta, starea slotului devine undefined la soft navigation pe alte rute de nivel înalt.

Trade-off-uri reale și bube din producție

Să fim sinceri, soluția asta e genială pentru UX, dar vine cu niște costuri de mentenanță pe care trebuie să le cântărești bine înainte să o bagi în codul de producție:

  1. Iadul directoarelor: Când ai rute adânci gen @modal/(..)dashboard/settings/(.)photos/[id], structura de dosare începe să arate ca un regex scris la supărare. Colegii mai juniori din echipă se vor pierde sigur în ea.
  2. Gestionarea butonului de Close: Ca să închizi modalul, de obicei apelezi router.back(). Dar dacă userul a ajuns pe /photos/123 direct prin copy-paste la URL (hard navigation), nu există istoric anterior! În cazul ăsta, router.back() îl aruncă pe Google sau nu face nimic. Am fost nevoiți să scriem o verificare explicită: dacă istoricul e gol, facem router.push('/') manual.
  3. Scroll restoration: Pe mobil (mai ales iOS Safari), am văzut cazuri unde scroll-ul paginii din spate sare în sus când deschizi sau închizi modalul, mai ales dacă ai liste virtualizate.

E cea mai curată abordare din punct de vedere arhitectural dacă vrei un modal ruteabil și indexabil separat, dar nu o folosi pentru dialoguri simple de confirmare sau formulare mici. Voi ați trecut pe rute interceptate în producție sau încă mergeți pe clasicul query param gen ?modal=photo-123?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.