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

Modale cu URL dedicat în Next.js App Router: Parallel și Intercepting Routes explicate

De Ana Ionescu, 8 aug. 2026 · 8 vizualizări · 3 like-uri

Postat 8 aug. 2026
typescript
// app/@modal/(.)photos/[id]/page.tsx
import { Modal } from '@/components/Modal';
import { PhotoCard } from '@/components/PhotoCard';
import { getPhoto } from '@/lib/api';

interface PageProps {
  params: Promise<{ id: string }>;
}

export default async function PhotoModal({ params }: PageProps) {
  const { id } = await params;
  const photo = await getPhoto(id);

  if (!photo) {
    return null;
  }

  return (
    <Modal>
      <PhotoCard photo={photo} isModal />
    </Modal>
  );
}

Am avut anul trecut un proiect de licitații unde aveam un feed uriaș de poze. Clientul voia ceva aparent simplu: când dai click pe o poză din feed, să se deschidă un modal instant, fără să piardă scroll-ul din spate, iar URL-ul să se schimbe în /photos/123. Când trimiteai acel URL unui prieten, el trebuia să vadă direct pagina dedicată imaginii, nu tot feed-ul. Dacă ești pe Next.js App Router, combinația de Parallel Routes (@modal) și Intercepting Routes ((.)photos/[id]) e singura cale curată să rezolvi asta.

Nu e un concept ușor de digerat de la prima citire a docsurilor Vercel. Mie mi-a luat vreo 4 ore de înjurături la primul proiect cu 12k vizite zilnice pe galerie până am înțeles exact unde e șmecheria.

De ce nu folosești doar un state local isOpen?

Varianta clasică cu const [isOpen, setIsOpen] = useState(false) pica din start. Nu ai URL unic, nu ai server-side rendering pentru poza respectivă când intri din exterior, nu ai SEO.

Dacă schimbi URL-ul manual cu router.push fără interceptare, Next.js va reîncărca tot layout-ul și vei pierde poziția din scroll. Userul dă back, pagina se reîncarcă, scroll-ul sare sus. UX mizerabil.

Cum funcționează magia în App Router

Mecanica se bazează pe două concepte care funcționează în paralel:

  1. Parallel Routes (folderele cu @): Îți permit să randezi una sau mai multe pagini în același layout, ca niște slot-uri independente.
  2. Intercepting Routes (folderele cu (.)): Îți permit să "furi" o rută atunci când navigarea se face din interfață (soft navigation). Când utilizatorul vine de pe o legătură externă sau dă refresh (hard navigation), Next.js ignoră interceptarea și încarcă pagina normală.

Adică, la click pe poză din /feed, rutezi spre /photos/123. Rutarea e interceptată de (.)photos/[id], iar componenta respectivă este injectată automat în slotul @modal din layout-ul părinte. Rezultat? Meniul și feed-ul din spate rămân neatinse, iar peste ele se trage modalul cu noul URL. Niciun request inutil, timp de răspuns sub 200ms.

Structura folderelor (unde se încurcă lumea)

Arhitectura trebuie să arate fix așa în app/:

  • layout.tsx (primește children și @modal ca props)
  • page.tsx (feed-ul tău principal)
  • @modal/
    • default.tsx (CRUCIAL: returnează null când modalul e închis)
    • (.)photos/[id]/page.tsx (modalul propriu-zis)
  • photos/[id]/page.tsx (pagina full-screen când dai F5)

Greșeala pe care o face toată lumea prima dată? Uită de default.tsx din interiorul slotului @modal. Fără el, la prima navigare înapoi, App Router nu știe ce să randeze în slotul paralel nefolosit și îți aruncă o eroare nesuferită sau un 404 tăcut.

Trade-off-uri reale

Design-ul ăsta e genial pentru UX, dar vine cu niște costuri de mentenanță de care trebuie să fii conștient:

  • Complexitate vizuală în cod: Structura de foldere devine stufoasă. Dacă ai mai multe modale de genul ăsta, directorul app începe să arate ca o schemă de rețea.
  • Cache și Revalidare: Dacă faci mutation în modal (de exemplu, un buton de Delete pe imagine), trebuie să fii atent ce revalidezi (revalidatePath). Uneori modalul se închide, dar feed-ul din spate păstrează poza veche dacă nu ai configurat cache-ul corect.
  • Unmount manual: Când închizi modalul din butonul X, trebuie să dai router.back(). Dacă userul a ajuns pe modal printr-un link direct (copy-paste), router.back() îl va trimite pe site-ul de unde a venit (sau google.com), nu pe feed-ul tău. Trebuia să tratezi cazul ăsta separat cu router.push('/').

Când merită? La galerii foto, preview de produse pe e-commerce sau detaliu de postare stil Twitter/X. În rest, pentru formulare simple, rămâi la un state local.

Voi ați trecut la App Router pe proiectele mari de producție sau încă țineți de pages/ de frică?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

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