Uvod: Napredni routing obrasci u Next.js App Routeru
Ako ste pratili naše prethodne vodiče o React Server Components, Server Actions i Middlewareu, već imate solidnu osnovu za gradnju modernih Next.js aplikacija. No, postoji jedan aspekt App Routera koji mnogi developeri jednostavno preskoče — a koji može stvarno podići korisničko iskustvo vaših aplikacija na novu razinu: paralelne rute i intercepting routes.
Zamislite ovu situaciju: korisnik pregledava galeriju proizvoda i klikne na jedan od njih. Umjesto da ga aplikacija odvede na potpuno novu stranicu (pri čemu gubi kontekst pretraživanja, poziciju scrollanja i filtere), otvara se elegantni modal s detaljima proizvoda — a URL se pritom ažurira tako da korisnik može podijeliti direktan link. Ako taj link netko otvori u novom tabu ili osvježi stranicu, prikazat će se potpuna stranica proizvoda. Ovo je tzv. modal pattern, i upravo ga paralelne rute u kombinaciji s intercepting routes čine mogućim.
Ali modali su tek početak priče. Paralelne rute omogućuju i izgradnju kompleksnih dashboarda gdje se svaki panel neovisno učitava, ima vlastito stanje pogreške i loading indikator, te podržava neovisnu navigaciju unutar slotova — poput tabova. U ovom vodiču ćemo proći kroz sve: od temeljnih koncepata i strukture datoteka, preko praktičnih primjera za modale i dashboarde, pa sve do naprednih obrazaca i onih dosadnih zamki koje vas mogu iznenaditi kad se najmanje nadate.
Što su paralelne rute i kako funkcioniraju
Paralelne rute (Parallel Routes) omogućuju vam da istovremeno ili uvjetno renderirate jednu ili više stranica unutar istog layouta. Definiraju se pomoću imenovanih slotova (named slots), a konvencija je zapravo prilično jednostavna: koristite @folder sintaksu u sustavu datoteka.
Svaki slot se automatski prosljeđuje kao prop nadređenom layout.tsx fajlu. Evo najjednostavnijeg primjera — dashboard s dva neovisna panela:
app/
├── dashboard/
│ ├── layout.tsx # Prima @team i @analytics kao propse
│ ├── page.tsx # Glavni sadržaj (children)
│ ├── @team/
│ │ └── page.tsx # Panel za tim
│ └── @analytics/
│ └── page.tsx # Panel za analitiku
Layout komponenta tada prima slotove kao React čvorove:
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
team,
analytics,
}: {
children: React.ReactNode;
team: React.ReactNode;
analytics: React.ReactNode;
}) {
return (
<div className="grid grid-cols-12 gap-6">
<main className="col-span-8">{children}</main>
<aside className="col-span-4 space-y-6">
{team}
{analytics}
</aside>
</div>
);
}
Ključna stvar koju trebate zapamtiti: slotovi nisu segmenti rute. To znači da @team folder ne utječe na URL strukturu. Datoteka na putanji app/dashboard/@team/members/page.tsx bit će dostupna na /dashboard/members, a ne na /dashboard/@team/members. Ovo u početku može zbuniti, ali kad se naviknete, zapravo je dosta logično.
Zašto su paralelne rute korisne
Bez paralelnih ruta, tipičan dashboard bi zahtijevao jednu veliku komponentu koja dohvaća sve podatke odjednom. Ako jedan dio podataka kasni, cijela stranica čeka. Frustrirajuće, zar ne?
S paralelnim rutama, svaki slot se neovisno streama do layouta, što donosi tri konkretne prednosti:
- Neovisni loading indikatori — svaki slot može imati vlastiti
loading.tsx, pa ako panel s analitikom sporije učitava podatke, korisnik vidi spinner samo na tom panelu dok ostatak dashboarda ostaje potpuno interaktivan. - Izolirano upravljanje greškama — svaki slot može imati vlastiti
error.tsx, tako da greška u jednom panelu ne ruši cijeli dashboard. Ovo je po meni jedna od najkorisnijih stvari. - Neovisna navigacija — svaki slot može imati vlastite podstranice i navigaciju (npr. tabove unutar panela), a promjena u jednom slotu ne utječe na ostale.
Datoteka default.tsx: Spriječavanje 404 pogrešaka
Ovo je vjerojatno najčešći izvor frustracije kod developera koji prvi put koriste paralelne rute. Iskreno, gotovo svi se ovdje spotaknu barem jednom. Kad korisnik navigira na podrutu unutar jednog slota, drugi slotovi koji nemaju odgovarajuću podrutu moraju znati što prikazati. Tu na scenu stupa default.tsx.
Ponašanje ovisi o vrsti navigacije:
- Soft navigacija (klik na
<Link>) — Next.js zadržava prethodno aktivno stanje slota čak i ako se URL promijeni. - Hard navigacija (osvježavanje stranice, direktno otvaranje URL-a) — Next.js traži
default.tsxu svakom slotu koji ne odgovara trenutnom URL-u. Ako ta datoteka ne postoji, dobivate 404.
Rješenje je, srećom, jednostavno — dodajte default.tsx u svaki slot:
// app/dashboard/@analytics/default.tsx
export default function AnalyticsDefault() {
return <p>Odaberite metriku za prikaz analitike.</p>;
}
// Ili jednostavno:
export default function Default() {
return null;
}
Važno: children je implicitni slot. To znači da u nekim slučajevima trebate dodati default.tsx i za glavni sadržaj stranice (u direktoriju rute, ne u @children folderu), kako bi Next.js mogao obnoviti stanje roditeljske stranice nakon hard navigacije. Ovo je detalj koji se lako previdi, pa ga vrijedi zapamtiti.
Struktura s default.tsx za dashboard
app/
├── dashboard/
│ ├── layout.tsx
│ ├── page.tsx
│ ├── default.tsx # Fallback za children slot
│ ├── @team/
│ │ ├── page.tsx
│ │ ├── default.tsx # Fallback za team slot
│ │ └── members/
│ │ └── page.tsx
│ └── @analytics/
│ ├── page.tsx
│ ├── default.tsx # Fallback za analytics slot
│ ├── page-views/
│ │ └── page.tsx
│ └── visitors/
│ └── page.tsx
Uvjetno renderiranje s paralelnim rutama
Evo nečeg što mi se posebno sviđa. Paralelne rute vam omogućuju da uvjetno prikažete potpuno različita sučelja na istom URL-u, temeljeno na nekom uvjetu — najčešće korisničkoj ulozi ili statusu autentifikacije. Za dashboard aplikacije, ovo je nevjerojatno praktično.
// app/dashboard/layout.tsx
import { checkUserRole } from '@/lib/auth';
export default async function DashboardLayout({
admin,
user,
}: {
admin: React.ReactNode;
user: React.ReactNode;
}) {
const role = await checkUserRole();
return (
<div className="min-h-screen">
{role === 'admin' ? admin : user}
</div>
);
}
Struktura datoteka za ovaj obrazac:
app/
├── dashboard/
│ ├── layout.tsx
│ ├── @admin/
│ │ ├── page.tsx # Admin dashboard
│ │ └── default.tsx
│ └── @user/
│ ├── page.tsx # Korisnički dashboard
│ └── default.tsx
Prednost ovog pristupa nad tradicionalnim if/else unutar jedne komponente jest što su admin i user paneli potpuno neovisni — imaju vlastite loading indikatore, error handlere i podstranice. Osim toga, kod je čistije organiziran jer svaka varijanta živi u zasebnom direktoriju. Nema više onog napornog uvjetnog renderiranja s hrpom if-ova unutar jedne komponente.
Tabovi unutar paralelnih ruta
Još jedan čest obrazac (i osobno jedan od mojih favorita) je implementacija tabova unutar jednog slota. Svaki tab je zapravo podstranica slota, a navigacija između tabova ne utječe na ostale slotove na stranici.
app/
├── dashboard/
│ ├── layout.tsx
│ ├── page.tsx
│ └── @analytics/
│ ├── layout.tsx # Sadrži navigaciju (tabove)
│ ├── page.tsx # Zadani prikaz
│ ├── default.tsx
│ ├── page-views/
│ │ └── page.tsx
│ └── visitors/
│ └── page.tsx
Layout unutar @analytics slota sadrži navigaciju za tabove:
// app/dashboard/@analytics/layout.tsx
import Link from 'next/link';
export default function AnalyticsLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="rounded-lg border p-4">
<nav className="flex gap-4 border-b pb-2 mb-4">
<Link
href="/dashboard"
className="text-sm font-medium hover:text-blue-600"
>
Pregled
</Link>
<Link
href="/dashboard/page-views"
className="text-sm font-medium hover:text-blue-600"
>
Pregledi stranica
</Link>
<Link
href="/dashboard/visitors"
className="text-sm font-medium hover:text-blue-600"
>
Posjetitelji
</Link>
</nav>
{children}
</div>
);
}
Kad korisnik klikne na tab "Posjetitelji", URL se mijenja u /dashboard/visitors, ali samo @analytics slot se ažurira. Slot @team ostaje nepromijenjen i čuva svoje stanje. Pokušajte to postići bez paralelnih ruta — nije nemoguće, ali vjerujte mi, bit će dosta kompliciranije.
Što su intercepting routes
Intercepting routes (presretajuće rute) omogućuju vam da učitate sadržaj s jedne rute unutar konteksta trenutne stranice, bez da korisnik napusti stranicu na kojoj se nalazi. URL se ažurira, ali umjesto pune navigacije, sadržaj se prikazuje unutar trenutnog layouta — tipično kao modal ili bočni panel.
Konvencija za definiranje intercepting ruta koristi posebne oznake u imenima direktorija:
(.)— presreće rutu na istoj razini(..)— presreće rutu jednu razinu iznad(..)(..)— presreće rutu dvije razine iznad(...)— presreće rutu od korijena (appdirektorija)
Važna napomena: konvencija (..) temelji se na segmentima rute, a ne na datotečnom sustavu. To znači da se @slot folderi i (group) folderi ne računaju kao segmenti rute prilikom određivanja razine presretanja. Ovo može biti malo zbunjujuće na prvu, ali držite se pravila — brojite samo stvarne URL segmente, a ne foldere u file systemu.
Kako intercepting rute funkcioniraju u praksi
Dakle, postoje dva scenarija:
- Soft navigacija (klik na
<Link>) — Next.js presreće navigaciju i prikazuje sadržaj iz intercepting rute unutar trenutnog layouta. URL se ažurira, ali stranica se ne osvježava. - Hard navigacija (direktan pristup URL-u, osvježavanje) — prikazuje se potpuna stranica s originalne rute, ne interceptirana verzija. Modal se ne pojavljuje.
I upravo je to ponašanje koje želite: kad korisnik klikne na fotografiju u galeriji, vidi modal. Kad podijeli URL i netko ga otvori u novom tabu, vidi potpunu stranicu fotografije. Jednostavno elegantno.
Modal pattern: Kombinacija paralelnih i intercepting ruta
Sad dolazimo do najzanimljivijeg dijela — najmoćniji i najčešće korišteni obrazac koji kombinira obje tehnike. Izgradimo ga korak po korak na primjeru galerije fotografija, slično Instagramu ili Unsplashu.
Korak 1: Struktura datoteka
app/
├── layout.tsx # Root layout (prima @modal slot)
├── page.tsx # Početna stranica s galerijom
├── default.tsx # Fallback za children
├── @modal/
│ ├── default.tsx # Vraća null kad modal nije aktivan
│ └── (.)photo/
│ └── [id]/
│ └── page.tsx # Interceptirana verzija (modal)
└── photo/
└── [id]/
└── page.tsx # Potpuna stranica fotografije
Na prvi pogled ovo možda izgleda kao puno datoteka, ali svaka ima svoju jasnu ulogu. Kad jednom složite ovu strukturu, sve sjeda na mjesto.
Korak 2: Root layout s modal slotom
// app/layout.tsx
export default function RootLayout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
<html lang="hr">
<body>
{children}
{modal}
</body>
</html>
);
}
Korak 3: Default za modal slot
// app/@modal/default.tsx
export default function ModalDefault() {
return null;
}
Ova datoteka osigurava da se ništa ne renderira kad nijedna intercepting ruta nije aktivna. Mala, ali bitna.
Korak 4: Galerija na početnoj stranici
// app/page.tsx
import Link from 'next/link';
import { db } from '@/lib/database';
export default async function GalleryPage() {
const photos = await db.photo.findMany({
orderBy: { createdAt: 'desc' },
take: 20,
});
return (
<div className="grid grid-cols-3 gap-4 p-8">
{photos.map((photo) => (
<Link key={photo.id} href={`/photo/${photo.id}`}>
<img
src={photo.thumbnailUrl}
alt={photo.title}
className="rounded-lg hover:opacity-80 transition-opacity"
/>
</Link>
))}
</div>
);
}
Ključno: morate koristiti Next.js <Link> komponentu, a ne standardni HTML <a> tag. Intercepting rute funkcioniraju samo s klijentskom navigacijom putem Link komponente. Ovo se zaboravi lakše nego što mislite.
Korak 5: Modal komponenta
// components/Modal.tsx
'use client';
import { useRouter } from 'next/navigation';
import { useCallback, useEffect } from 'react';
export function Modal({ children }: { children: React.ReactNode }) {
const router = useRouter();
const onDismiss = useCallback(() => {
router.back();
}, [router]);
const onKeyDown = useCallback(
(e: KeyboardEvent) => {
if (e.key === 'Escape') onDismiss();
},
[onDismiss]
);
useEffect(() => {
document.addEventListener('keydown', onKeyDown);
return () => document.removeEventListener('keydown', onKeyDown);
}, [onKeyDown]);
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
onClick={onDismiss}
>
<div
className="relative max-w-2xl w-full mx-4 bg-white rounded-xl shadow-2xl"
onClick={(e) => e.stopPropagation()}
>
<button
onClick={onDismiss}
className="absolute top-4 right-4 text-gray-500 hover:text-gray-900"
aria-label="Zatvori"
>
✕
</button>
{children}
</div>
</div>
);
}
Korak 6: Interceptirana verzija (modal prikaz)
// app/@modal/(.)photo/[id]/page.tsx
import { Modal } from '@/components/Modal';
import { db } from '@/lib/database';
export default async function PhotoModal({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const photo = await db.photo.findUnique({
where: { id },
include: { author: true },
});
if (!photo) return null;
return (
<Modal>
<div className="p-6">
<img
src={photo.fullUrl}
alt={photo.title}
className="w-full rounded-lg"
/>
<h2 className="text-xl font-bold mt-4">{photo.title}</h2>
<p className="text-gray-600 mt-2">
Autor: {photo.author.name}
</p>
</div>
</Modal>
);
}
Korak 7: Potpuna stranica fotografije
// app/photo/[id]/page.tsx
import { db } from '@/lib/database';
import Link from 'next/link';
import { notFound } from 'next/navigation';
export default async function PhotoPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const photo = await db.photo.findUnique({
where: { id },
include: { author: true, comments: true },
});
if (!photo) notFound();
return (
<div className="max-w-4xl mx-auto p-8">
<Link href="/" className="text-blue-600 hover:underline">
← Natrag na galeriju
</Link>
<img
src={photo.fullUrl}
alt={photo.title}
className="w-full rounded-xl mt-4"
/>
<h1 className="text-3xl font-bold mt-6">{photo.title}</h1>
<p className="text-gray-600 mt-2">
Autor: {photo.author.name}
</p>
<section className="mt-8">
<h2 className="text-xl font-semibold">Komentari</h2>
{photo.comments.map((comment) => (
<div key={comment.id} className="border-b py-3">
<p>{comment.text}</p>
</div>
))}
</section>
</div>
);
}
S ovom implementacijom dobivate potpuno ponašanje koje smo opisali na početku: klik na fotografiju otvara modal, URL se ažurira na /photo/123, korisnik može podijeliti link, a osvježavanje stranice prikazuje potpunu stranicu fotografije s komentarima. Kad prvi put vidite da sve radi zajedno — prilično je zadovoljavajuće.
Zatvaranje modala: zamke i rješenja
Zatvaranje modala zvuči jednostavno — pozovete router.back() i gotovo. Ali (uvijek postoji "ali"), u praksi postoji nekoliko zamki koje mogu uzrokovati ponašanje koje niste očekivali.
Problem s uzastopnim modalima
Ako korisnik otvori jedan modal, pa iz njega navigira na drugi (npr. klikne na povezanu fotografiju unutar modala), svaki router.back() će ga vratiti na prethodni modal umjesto da zatvori sve modale. Rješenje: koristite replace umjesto push za navigaciju između modala:
// Unutar modala, za navigaciju na drugi modal:
<Link href={`/photo/${nextPhotoId}`} replace>
Sljedeća fotografija
</Link>
Korištenjem replace, novi URL zamjenjuje trenutni u povijesti preglednika, pa router.back() vraća korisnika na stranicu ispod modala, a ne na prethodni modal. Jednostavan trik, a čuva vas od hrpe problema.
Problem s Link komponentom i zatvaranjem
Intuitivno biste mogli pokušati zatvoriti modal koristeći <Link href="/" replace>, ali to neće uvijek funkcionirati kako očekujete — modal se ponekad može zadržati vidljiv. Najsigurnije rješenje za zatvaranje je uvijek router.back().
Catch-all ruta za čišćenje modala
Da biste osigurali da se @modal slot ispravno očisti pri navigaciji na bilo koju drugu rutu, dodajte catch-all rutu unutar slota:
app/
├── @modal/
│ ├── default.tsx
│ ├── (.)photo/
│ │ └── [id]/
│ │ └── page.tsx
│ └── [...catchAll]/
│ └── page.tsx # Vraća null
// app/@modal/[...catchAll]/page.tsx
export default function CatchAll() {
return null;
}
Ova catch-all ruta hvata sve URL-ove koji ne odgovaraju intercepting ruti i vraća null, čime se modal automatski zatvara. Jedna datoteka, a štedi vas od puno potencijalnih bugova.
Praktični primjer: Košarica kao bočni modal
Pogledajmo još jedan primjer iz prakse koji je čest u e-commerce aplikacijama — košarica koja se otvara kao bočni panel kad korisnik klikne ikonu košarice, ali ima i vlastitu potpunu stranicu:
app/
├── layout.tsx
├── page.tsx # Stranica s proizvodima
├── @cart/
│ ├── default.tsx # Vraća null
│ └── (.)cart/
│ └── page.tsx # Bočni panel košarice
├── cart/
│ └── page.tsx # Potpuna stranica košarice
└── components/
└── CartSidebar.tsx # Klijentska komponenta za sidebar
// components/CartSidebar.tsx
'use client';
import { useRouter } from 'next/navigation';
export function CartSidebar({ children }: { children: React.ReactNode }) {
const router = useRouter();
return (
<div className="fixed inset-0 z-50 flex justify-end">
<div
className="absolute inset-0 bg-black/40"
onClick={() => router.back()}
/>
<div className="relative w-96 bg-white h-full shadow-xl p-6 overflow-y-auto">
<div className="flex justify-between items-center mb-6">
<h2 className="text-xl font-bold">Vaša košarica</h2>
<button
onClick={() => router.back()}
className="text-gray-500 hover:text-gray-900"
>
✕
</button>
</div>
{children}
</div>
</div>
);
}
// app/@cart/(.)cart/page.tsx
import { CartSidebar } from '@/components/CartSidebar';
import { getCartItems } from '@/lib/cart';
export default async function CartModal() {
const items = await getCartItems();
return (
<CartSidebar>
{items.length === 0 ? (
<p className="text-gray-500">Košarica je prazna.</p>
) : (
<ul className="space-y-4">
{items.map((item) => (
<li key={item.id} className="flex gap-4 border-b pb-4">
<img
src={item.imageUrl}
alt={item.name}
className="w-16 h-16 rounded object-cover"
/>
<div>
<p className="font-medium">{item.name}</p>
<p className="text-sm text-gray-500">
{item.quantity}x — {item.price} EUR
</p>
</div>
</li>
))}
</ul>
)}
</CartSidebar>
);
}
Ikona košarice u navigaciji koristi standardni Link:
<Link href="/cart">
🛒 Košarica ({cartCount})
</Link>
Klik otvara bočni panel. Direktan pristup na /cart prikazuje potpunu stranicu košarice s detaljima, opcijama dostave i naplatom. Korisnici vole ovu vrstu interakcije jer se čini brzo i ne prekida tok kupnje.
Neovisni loading i error indikatori po slotovima
Jedna od najvrjednijih mogućnosti paralelnih ruta jest što svaki slot podržava vlastite loading.tsx i error.tsx datoteke. To vam daje granularnu kontrolu nad korisničkim iskustvom svakog dijela stranice.
app/
├── dashboard/
│ ├── layout.tsx
│ ├── @analytics/
│ │ ├── page.tsx
│ │ ├── loading.tsx # Skeleton samo za analitiku
│ │ └── error.tsx # Error UI samo za analitiku
│ └── @team/
│ ├── page.tsx
│ ├── loading.tsx # Skeleton samo za tim
│ └── error.tsx # Error UI samo za tim
// app/dashboard/@analytics/loading.tsx
export default function AnalyticsLoading() {
return (
<div className="animate-pulse space-y-3">
<div className="h-4 bg-gray-200 rounded w-1/3"></div>
<div className="h-32 bg-gray-200 rounded"></div>
<div className="h-4 bg-gray-200 rounded w-2/3"></div>
</div>
);
}
// app/dashboard/@analytics/error.tsx
'use client';
export default function AnalyticsError({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div className="p-4 bg-red-50 border border-red-200 rounded-lg">
<p className="text-red-800">
Greška pri učitavanju analitike: {error.message}
</p>
<button
onClick={reset}
className="mt-2 text-sm text-red-600 underline"
>
Pokušaj ponovno
</button>
</div>
);
}
Ovo je ogromna prednost u usporedbi s tradicionalnim pristupom gdje bi greška u jednom dijelu stranice srušila cijeli layout. S ovim pristupom, korisnik može nastaviti koristiti ostatak dashboarda dok se problematični panel popravlja.
Praćenje aktivnog segmenta: useSelectedLayoutSegment
Hookovi useSelectedLayoutSegment i useSelectedLayoutSegments prihvaćaju parametar parallelRoutesKey, koji vam omogućuje čitanje aktivnog segmenta unutar određenog slota. Ovo je korisno za označavanje aktivnog taba ili uvjetno prikazivanje sadržaja:
// components/AnalyticsTabs.tsx
'use client';
import Link from 'next/link';
import { useSelectedLayoutSegment } from 'next/navigation';
export function AnalyticsTabs() {
const activeSegment = useSelectedLayoutSegment('analytics');
const tabs = [
{ label: 'Pregled', href: '/dashboard', segment: null },
{ label: 'Pregledi stranica', href: '/dashboard/page-views', segment: 'page-views' },
{ label: 'Posjetitelji', href: '/dashboard/visitors', segment: 'visitors' },
];
return (
<nav className="flex gap-2">
{tabs.map((tab) => (
<Link
key={tab.href}
href={tab.href}
className={`px-4 py-2 rounded-md text-sm font-medium ${
activeSegment === tab.segment
? 'bg-blue-600 text-white'
: 'bg-gray-100 text-gray-700 hover:bg-gray-200'
}`}
>
{tab.label}
</Link>
))}
</nav>
);
}
Česte zamke i najbolje prakse
Nakon svega što smo prošli, evo kratkog pregleda najvažnijih pravila i pogrešaka koje trebate izbjegavati. Držite ovo kao referencu — sigurno će vam trebati.
Uvijek dodajte default.tsx
Svaki slot treba imati default.tsx datoteku. Bez nje, hard navigacija (osvježavanje stranice) rezultirat će 404 pogreškom za slotove koji ne odgovaraju trenutnom URL-u. Čak i ako samo vraća null, ta datoteka je neophodna. Ponovit ću: uvijek dodajte default.tsx.
Slotovi rade samo u layout.tsx
Paralelni slotovi se prosljeđuju isključivo u layout.tsx. Ne možete ih koristiti u page.tsx. Ovo je česta pogreška — ako pokušate primiti @modal prop u page.tsx, jednostavno neće raditi i nećete dobiti nikakvu jasnu grešku.
Izbjegavajte crtice u imenima slotova
Budući da se ime slota koristi kao varijabla (prop) u vašem kodu, izbjegavajte crtice. Koristite @cartPanel umjesto @cart-panel.
Koristite Link, ne anchor tag
Intercepting rute funkcioniraju samo s Next.js <Link> komponentom. Standardni <a> tag uzrokuje hard navigaciju koja potpuno zaobilazi interceptor.
Koristite Next.js 15 ili noviji
Preporučuje se korištenje najmanje Next.js 15. Starije verzije imaju poznate probleme s intercepting rutama u kombinaciji s dinamičkim rutama koji su riješeni u novijim verzijama. Ako se mučite s nekim čudnim ponašanjem, provjera verzije je dobar prvi korak.
Nema podrške za route grupe u intercepting rutama
Trenutno intercepting rute ne podržavaju (group) sintaksu za grupiranje ruta. Ako koristite route grupe u svojoj aplikaciji, imajte ovo na umu i planirajte strukturu datoteka u skladu s tim.
Često postavljana pitanja
Mogu li koristiti intercepting rute bez paralelnih ruta?
Tehnički da, ali najmoćniji slučaj upotrebe — modali koji su djeljivi putem URL-a i koji se ispravno ponašaju pri osvježavanju stranice — zahtijeva kombinaciju obje tehnike. Bez paralelnog slota, nemate mjesto za renderiranje interceptiranog sadržaja unutar trenutnog layouta na način koji se ispravno čisti pri navigaciji.
Što se dogodi kad korisnik osvježi stranicu dok je modal otvoren?
Budući da je URL modala stvarna ruta (npr. /photo/123), osvježavanje će prikazati potpunu stranicu fotografije — ne modal. I to je zapravo poželjno ponašanje. Modal je optimizacija korisničkog iskustva za navigaciju unutar aplikacije, dok potpuna stranica služi za direktan pristup i SEO.
Kako zatvoriti modal bez korištenja router.back()?
Najsigurniji način za zatvaranje modala je router.back(). Alternativno, možete koristiti catch-all rutu unutar @modal slota koja vraća null. No izbjegavajte <Link href="/" replace> jer u nekim slučajevima neće ispravno ukloniti modal iz prikaza.
Mogu li imati više paralelnih slotova na istoj stranici?
Da, apsolutno. Nema ograničenja na broj slotova. Možete imati @sidebar, @modal, @notifications, @chat — koliko god vam treba. Svaki slot funkcionira neovisno s vlastitim loading i error stanjima.
Utječu li paralelne rute na performanse aplikacije?
Ne negativno — zapravo je suprotno. Budući da se svaki slot neovisno streama, korisnik brže vidi sadržaj koji se prvi učita. Spori slotovi ne blokiraju brze. A slotovi koji nisu vidljivi (poput @modal s default.tsx koji vraća null) ne uzrokuju dodatno opterećenje jer Next.js ne renderira komponentu koja vraća null.