eduardweb.
App RouterAvansat#nextjs#react#frontend#web-dev#app-router

Modale cu URL propriu în Next.js App Router: Parallel și Intercepting Routes fără dureri de cap

De Mihai Popescu, 23 iul. 2026 · 9 vizualizări · 3 like-uri

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

export default function RootLayout({
  children,
  modal,
}: {
  children: React.ReactNode;
  modal: React.ReactNode;
}) {
  return (
    <html lang="ro">
      <body>
        <main>{children}</main>
        {/* Slot-ul paralel pentru modal */}
        <div id="modal-root">{modal}</div>
      </body>
    </html>
  );
}

// app/@modal/default.tsx
export default function Default() {
  // Obligatoriu! Returnează null când niciun modal nu e activ.
  return null;
}

M-am lovit de cerința asta la un proiect e-commerce cu vreo 14k de produse: clientul voia ca din lista principală, când dai click pe o poză, să se deschidă un modal rapid cu URL schimbat în /photos/[id], fără să piardă scroll-ul sau contextul din spate. În același timp, dacă cineva dă copy-paste la acel URL într-un tab nou sau dă Refresh (F5), aplicația trebuie să randeze pagina completă de produs, nu un modal urât pe fundal alb.

În Pages Router era o varză totală de router.push cu as prop și stare globală. În App Router, soluția nativă este combinația dintre Parallel Routes (slots) și Intercepting Routes ((.)).

Cum arată structura folderelor

Ideea e simplă dar puțin contraintuitivă la început. Ai nevoie de un slot paralel (notat cu @) și de o rută interceptată (notată cu (.) pentru același nivel de folder).

Structura minimă în app/ arată cam așa:

app/
├── layout.tsx
├── page.tsx
├── photos/
│   └── [id]/
│       └── page.tsx
└── @modal/
    ├── (.)photos/
    │   └── [id]/
    │       └── page.tsx
    └── default.tsx

Când navighezi prin Link de pe / către /photos/42, Next.js prinde tranziția pe client. În loc să te trimită pe ruta fizică app/photos/[id]/page.tsx, declanșează ruta interceptată din @modal/(.)photos/[id]/page.tsx. Pagina principală rămâne vizibilă sub slot-ul @modal.

Capcana de care te lovești sigur: default.tsx

Aici se încurcă 80% din devii care încearcă asta prima dată. Slot-urile paralele au nevoie neapărat de un fișier default.tsx. Când utilizatorul e pe ruta principală (/) și nu există niciun modal deschis, Next.js trebuie să știe ce să randeze în slot-ul @modal.

Dacă nu pui default.tsx care să returneze null, o să-ți iei 404 sau erori ciudate de hidratare la un simplu soft-navigation. Am pierdut vreo două ore la primul proiect căutând de ce îmi crapă build-ul pe Vercel din cauza asta.

Trade-off-uri reale

Arată spectaculos pentru UX și economisești cam 300ms la redarea modalului pentru că nu mai reîncarci tot layout-ul root. Totuși, vin și părțile mai puțin plăcute:

  1. Complexitate vizuală în proiect: Structura de directoare devine stufosă repede. Dacă ai 3 modale de genul ăsta, folderul app începe să arate ca un puzzle.
  2. State management la închidere: Ca să închizi modalul, nu e de ajuns să schimbi un state local isOpen=false. Trebuie să faci router.back() sau un router.push() către pagina părinte ca să cureți slot-ul.
  3. Caching amestecat: Dacă faci fetch la date pe server în photos/[id]/page.tsx și separat în (.)photos/[id]/page.tsx, asigură-te că folosești aceleași tag-uri de cache, altfel vezi date inconsecvente între modal și pagina standalone.

Nu folosiți abordarea asta pentru modale simple de tip "Termeni și Condiții" sau confirmări de ștergere. E proiectată strict pentru resurse care merită URL dedicat, indexabil și shareable.

Voi cum gestionați modalele cu URL deep-linkable în producție, mergeți pe App Router nativ sau tot pe query params (?modal=photo-123)?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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