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

Modal-uri stil Instagram în Next.js cu Parallel și Intercepting Routes

De Cosmin Rotaru, 20 iun. 2026 · 18 vizualizări · 2 like-uri

Postat 20 iun. 2026
typescript
// app/layout.tsx
export default function RootLayout({
  children,
  modal,
}: {
  children: React.ReactNode;
  modal: React.ReactNode;
}) {
  return (
    <html lang="ro">
      <body>
        <main>{children}</main>
        {modal}
      </body>
    </html>
  );
}

Am implementat treaba asta acum vreo jumătate de an pe un proiect de portofoliu cu peste 5.000 de imagini de înaltă rezoluție. Clientul voia exact comportamentul de pe Instagram sau Pinterest: dai click pe o imagine în grid, se deschide un modal superb cu detalii, URL-ul se schimbă în /photos/123, dar dacă utilizatorul dă refresh la pagina aia sau trimite link-ul unui prieten, ruta se randează ca o pagină de sine stătătoare, nu în modal.

În Next.js Pages Router, făceai asta cu niște hack-uri dubioase de state în URL query. În App Router, avem în sfârșit un mod nativ, deși recunosc că mi-a luat vreo două zile de înjurături până m-am prins de toate nuanțele.

Sistemul se bazează pe două concepte din App Router: Parallel Routes (sloturile cu @) și Intercepting Routes (folderele cu paranteze și punct (.)).

Structura de foldere care nu te lasă la greu

Aici se rupe filmul pentru majoritatea oamenilor. Structura trebuie să fie extrem de precisă. Dacă greșești o singură paranteză, Next.js pur și simplu ignoră interceptarea fără să-ți dea vreun warning util în consolă.

Uite cum arată structura curată de care ai nevoie:

  • app/layout.tsx (aici injectezi slotul @modal pe lângă children)
  • app/page.tsx (grid-ul tău de poze cu link-uri normale către /photos/id)
  • app/photos/[id]/page.tsx (pagina full, care se încarcă doar la refresh direct)
  • app/@modal/(.)photos/[id]/page.tsx (modalul care interceptează navigarea pe client)
  • app/@modal/default.tsx (salvatorul tău de la erorile de randare)

De ce e critic default.tsx?

Am pățit asta la primul build în producție. Totul mergea local, dar când am dat deploy, paginile adiacente aruncau erori ciudate la refresh.

De ce? Pentru că Next.js folosește slotul @modal pe toate rutele din folderul părinte. Dacă ești pe /about și dai refresh, framework-ul caută ce să pună în slotul @modal pentru acea rută. Dacă nu găsește nimic, crapă. Soluția e să creezi un fișier default.tsx în interiorul folderului @modal care să returneze pur și simplu null. Astfel, Next.js știe să ignore slotul când nu e activ.

Trade-off-ul sincer: UX excelent vs complexitate mărită

Metoda asta e genială pentru că păstrezi SEO curat pe ruta /photos/[id], crawler-ul vede pagina normală, iar utilizatorul are o experiență extrem de fluidă pe client, fără să piardă contextul listei din spate.

Dar vine cu niște costuri de mentenanță destul de mari. De exemplu, dacă vrei să adaugi o animație de închidere (exit animation) cu Framer Motion, devine extrem de complicat. Când apelezi router.back(), Next.js demontează ruta interceptată instantaneu, înainte ca animația ta să apuce să ruleze. Am pierdut ore bune încercând să fentez asta cu timeout-uri și state-uri locale dublate.

La fel și cu formularele. Dacă ai un formular de comentarii în modalul ăla și vrei să previi închiderea accidentală la click pe overlay, e mult mai greu de controlat fluxul rutei interceptate decât dacă aveai un simplu state boolean isOpen.

Voi cum gestionați animațiile de închidere pe rutele interceptate în proiectele voastre? Ați găsit un workaround curat sau ați renunțat complet la ele?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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