Next.js Parallel ve Intercepting Routes

Next.js Parallel ve Intercepting Routes
Next.js App Router, geleneksel routing'in ötesine geçen iki güçlü özellik sunar: Parallel Routes ve Intercepting Routes. Parallel Routes ile aynı layout içinde birden fazla sayfayı eşzamanlı olarak render edebilir, Intercepting Routes ile bir route'u mevcut layout içinde yakalayabilirsiniz. Bu yazıda her iki özelliği pratik örneklerle detaylıca inceleyeceğiz.
Parallel Routes Nedir?
Parallel Routes, aynı layout içinde birden fazla sayfayı (slot) eşzamanlı olarak göstermenizi sağlar. Her slot bağımsız olarak yüklenir, hata yönetimi yapılır ve navigate edilebilir. Slot'lar @ prefix'i ile klasör adı olarak tanımlanır.
// Klasör yapısı
app/
├── layout.js
├── page.js
├── @dashboard/
│ ├── page.js
│ ├── loading.js
│ └── error.js
├── @notifications/
│ ├── page.js
│ ├── loading.js
│ └── error.js
└── @analytics/
├── page.js
└── loading.js
// app/layout.js
export default function Layout({ children, dashboard, notifications, analytics }) {
return (
<div className="app-layout">
<header>
<h1>Yönetim Paneli</h1>
</header>
<div className="grid grid-cols-3 gap-4">
{/* Her slot bağımsız loading/error state'ine sahip */}
<div className="col-span-2">{dashboard}</div>
<aside>
{notifications}
{analytics}
</aside>
</div>
{/* children = page.js içeriği */}
<main>{children}</main>
</div>
);
}
Conditional Routing ile Parallel Routes
Parallel Routes ile kullanıcı rolüne veya durumuna göre farklı içerikler gösterebilirsiniz:
// app/layout.js
import { getUser } from '@/lib/auth';
export default async function Layout({ children, admin, user }) {
const currentUser = await getUser();
return (
<div>
{currentUser.role === 'admin' ? admin : user}
{children}
</div>
);
}
// app/@admin/page.js
export default function AdminDashboard() {
return <h2>Admin Paneli</h2>;
}
// app/@user/page.js
export default function UserDashboard() {
return <h2>Kullanıcı Paneli</h2>;
}
default.js Dosyası
Parallel Routes'ta bir slot'un eşleşen route'u olmadığında gösterilecek varsayılan içeriği default.js ile tanımlarsınız. Bu dosya olmadan, eşleşmeyen slot'lar 404 döner.
// app/@notifications/default.js
export default function DefaultNotifications() {
return <p>Bildirim yok.</p>;
}
// app/@analytics/default.js
export default function DefaultAnalytics() {
return null; // Hiçbir şey gösterme
}
Intercepting Routes Nedir?
Intercepting Routes, bir route'a navigasyon yapıldığında, hedef route'u mevcut layout içinde yakalayarak (intercept ederek) göstermenizi sağlar. En yaygın kullanım örneği: bir listedeki öğeye tıklayınca modal açılması, URL'in güncellenmesi ve tam sayfanın da çalışır olmasıdır.
Intercepting Routes, klasör adında özel convention'lar kullanır:
(.)— Aynı seviyedeki route'u yakala(..)— Bir üst seviyedeki route'u yakala(..)(..)— İki üst seviyedeki route'u yakala(...)— Root'tan itibaren yakala
Modal Pattern: Photo Gallery Örneği
// Klasör yapısı
app/
├── layout.js
├── @modal/
│ ├── default.js
│ └── (.)photos/
│ └── [id]/
│ └── page.js ← Intercepted route (modal olarak açılır)
├── photos/
│ └── [id]/
│ └── page.js ← Tam sayfa (doğrudan erişimde)
└── page.js ← Ana sayfa (galeri listesi)
// app/page.js — Galeri listesi
import Link from 'next/link';
export default function Gallery() {
const photos = [
{ id: 1, src: '/photo1.jpg', title: 'Doğa' },
{ id: 2, src: '/photo2.jpg', title: 'Şehir' },
{ id: 3, src: '/photo3.jpg', title: 'Portre' },
];
return (
<div className="gallery-grid">
{photos.map((photo) => (
<Link key={photo.id} href={`/photos/${photo.id}`}>
<img src={photo.src} alt={photo.title} />
</Link>
))}
</div>
);
}
// app/@modal/(.)photos/[id]/page.js — Modal olarak intercept
import { PhotoModal } from '@/components/PhotoModal';
export default async function InterceptedPhoto({ params }) {
const { id } = await params;
const photo = await getPhoto(id);
return <PhotoModal photo={photo} />;
}
// app/photos/[id]/page.js — Tam sayfa görünümü
export default async function PhotoPage({ params }) {
const { id } = await params;
const photo = await getPhoto(id);
return (
<div className="photo-full">
<img src={photo.src} alt={photo.title} />
<h1>{photo.title}</h1>
<p>{photo.description}</p>
</div>
);
}
// app/layout.js
export default function RootLayout({ children, modal }) {
return (
<html>
<body>
{children}
{modal}
</body>
</html>
);
}
// app/@modal/default.js
export default function Default() {
return null; // Modal kapalıyken hiçbir şey gösterme
}
Modal Bileşeni
'use client';
import { useRouter } from 'next/navigation';
export function PhotoModal({ photo }) {
const router = useRouter();
return (
<div className="modal-overlay" onClick={() => router.back()}>
<div className="modal-content" onClick={(e) => e.stopPropagation()}>
<button className="close-btn" onClick={() => router.back()}>
×
</button>
<img src={photo.src} alt={photo.title} />
<h2>{photo.title}</h2>
</div>
</div>
);
}
/photos/1 olur. Sayfayı yenileyince veya URL'i doğrudan ziyaret edince tam sayfa görünümü gösterilir. Bu, Instagram'ın fotoğraf modal davranışına benzer.
E-ticaret Ürün Detay Örneği
// Klasör yapısı
app/
├── products/
│ ├── page.js ← Ürün listesi
│ └── [slug]/
│ └── page.js ← Ürün tam sayfası
├── @quickview/
│ ├── default.js
│ └── (..)products/
│ └── [slug]/
│ └── page.js ← Quick view modal
└── layout.js
// app/@quickview/(..)products/[slug]/page.js
export default async function QuickView({ params }) {
const { slug } = await params;
const product = await getProduct(slug);
return (
<div className="quickview-modal">
<div className="quickview-content">
<img src={product.image} alt={product.name} />
<h2>{product.name}</h2>
<p className="price">{product.price} TL</p>
<p>{product.shortDescription}</p>
<a href={`/products/${slug}`}>Detaylı İncele</a>
<button>Sepete Ekle</button>
</div>
</div>
);
}
Parallel Routes ile Tab Layout
// Klasör yapısı
app/settings/
├── layout.js
├── @tabs/
│ ├── profile/
│ │ └── page.js
│ ├── security/
│ │ └── page.js
│ └── notifications/
│ └── page.js
└── page.js
// app/settings/layout.js
import Link from 'next/link';
export default function SettingsLayout({ children, tabs }) {
return (
<div>
<h1>Ayarlar</h1>
<nav className="tabs">
<Link href="/settings/profile">Profil</Link>
<Link href="/settings/security">Güvenlik</Link>
<Link href="/settings/notifications">Bildirimler</Link>
</nav>
<div className="tab-content">{tabs}</div>
{children}
</div>
);
}
default.js dosyasını eklemeyi unutmayın, aksi halde eşleşmeyen route'larda 404 alırsınız.
Sonuç
Next.js Parallel Routes, tek bir layout içinde birden fazla bağımsız sayfa bölümünü eşzamanlı yönetmenizi sağlar. Her slot kendi loading, error ve default state'ine sahiptir. Intercepting Routes ise modal, quickview ve overlay pattern'lerini URL-first yaklaşımıyla uygulamanıza olanak tanır. Bu iki özellik birlikte kullanıldığında, karmaşık UI pattern'lerini temiz ve sürdürülebilir bir şekilde oluşturabilirsiniz. Özellikle e-ticaret, sosyal medya ve dashboard uygulamalarında büyük fayda sağlar.