App Router'da Modal Route Deneyimi
Yumuşak geçişte modal, sert yenilemede sayfa

Galeride bir kareye tıkladım, üstte overlay açıldı, URL değişti. URL'yi yeni sekmeye yapıştırınca overlay değil, tam sayfa fotoğraf geldi. Bunu bug sanmıştım. Intercepting route bunu bilinçli yapar: yumuşak geçişte mevcut sayfanın üstüne slot, sert girişte gerçek rota. Paylaşılabilir adres ile bağlamsal overlay aynı ağaçta durur.
Paralel slot ve (.) yakalama
// app/gallery/layout.tsx
export default function GalleryLayout({
children,
modal,
}: {
children: React.ReactNode;
modal: React.ReactNode;
}) {
return (
{children}
{modal}
);
}
// app/gallery/@modal/(.)photo/[id]/page.tsx
import { PhotoDialog } from "./photo-dialog";
import { getPhoto } from "@/lib/photos";
export default async function InterceptedPhoto({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const photo = await getPhoto(id);
return ;
}
"use client";
import { useRouter } from "next/navigation";
export function PhotoDialog({
id,
src,
alt,
}: {
id: string;
src: string;
alt: string;
}) {
const router = useRouter();
return (
);
}
(.) aynı seviyeyi yakalar. /gallery'den /gallery/photo/1'e client geçişinde @modal slot dolar, children galeri olarak kalır. Tam yenilemede intercept çalışmaz, app/gallery/photo/[id]/page.tsx tam sayfa render edilir. İki page, iki deneyim, tek adres.
default.tsx unutulunca
Paralel slot her zaman bir şey render etmek ister. Modal kapalıyken @modal/default.tsx null dönmezse eski overlay takılı kalır veya çerçeve boş slotu şikayet eder. default.tsx'im null dönüyor. Bu dosya 'boş hali' anlatır; unutulunca ilk sert girişte modal slotu 404 benzeri bir boşluk bırakabilir.
router.back() overlay'i kapatır çünkü yakalama bir history kaydı ekler. replace ile açarsan geri tuşu galeriyi de atlayabilir. Paylaşım linki tam sayfaya gitsin istiyorum; o yüzden dialog içinde düz bir tam sayfa linki var. Overlay bir kısayol, adres bir belge. İkisini tek client state ile taklit etmek, yenilenen sekmeyi kaybettiriyor.
Tam sayfa rotası ayrı durur
app/gallery/photo/[id]/page.tsx intercept değil, gerçek belge. generateMetadata burada durur; overlay'de metadata çoğu zaman ebeveyni korur. Paylaşılan kart tam sayfanın başlığını taşımalı. Overlay'e metadata yazmak, Slack'in galeri başlığını göstermesine yol açabilir. Beşinci yazıdaki kural burada da: kart, sert girişin HTML'inden gelir.
Fotoğraf verisini iki page'de çoğaltmamak için getPhoto cache() ile sarmalı. Yakalama ve tam sayfa aynı isteği paylaşmaz; ayrı isteklerdir. Deduping istek içidir. Helper aynı kalır, fatura iki boyamada da tek fonksiyondur. Kopya JSX'i dert etmiyorum; kopya sorguyu dert ediyorum.
Kapatma ve odak
dialog open, odak ve Escape'i tarayıcıya bırakır. Kendi div overlay'imi yazınca odak tuzağı, arka plan scroll kilidi ve aria'yı ben taşırım. Native dialog, on üçüncü yazının client yaprağını küçük tutar. router.back onClose'da; form method=dialog Escape'de. İkisini karıştırmayınca 'modal kapanmıyor' ticket'ı azalıyor.
Paralel slot + intercept, sihirli bir modal kütüphanesi değil. History'yi, iki page'i ve bir default.tsx'i sen kurarsın. Kurmayınca ya overlay yenilemede kaybolur — aslında kaybolmaz, hiç yakalanmaz — ya da slot boş kalır. Deneyim, bu üç dosyanın anlaşması. Anlaşma durunca adres ile overlay çatışmaz.
Yakalama seviyesi
(.) aynı seviye, (..) bir üst, (...) kök. Yanlış nokta, yanlış sayfayı overlay sanır. /gallery içinden /settings/profile yakalamak istemem. Nokta sayısı, niyet. Niyeti dosya adında görmek, state makinesinden daha okunur. 'Her link modal' diye (..) koymak, ayarlar sayfasını da overlay yapar. Kullanıcı sert yenilemede tam ayar görür, yumuşak geçişte üstte bir dialog. Bu, deneyim değil, kaza.
Catch-all ve opsiyonel catch-all, yakalama ile birleşince klasör ağacı kalabalıklaşır. Kalabalık, default.tsx unutulan ilk yerdir. Her @modal dalına default koyuyorum. Null dönmesi yeter. Koymayınca bir dal 404, diğer dal overlay, üçüncü dal boş. Üç davranış, bir unutkanlık.
Erişilebilirlik ve tarihçe
Overlay açıkken arka planı inert veya aria-hidden yapmak, okuyucuyu dialog'a kilitler. Native dialog bunu kısmen halleder. Kendi div'imde unutunca galeri hâlâ okunur, odak dağılır. router.back kapatınca tarihi doğru yerde bırakmak, inert'i de kaldırmak demek. Unutulan inert, 'sayfa tıklanmıyor' ticket'ı.
Paylaşılabilirlik, modal route'un tek ciddi gerekçesi. Gerekçe yoksa client state yeter, URL değişmez. URL değişsin istiyorsam intercept. Değişmesin istiyorsam bir useState. İkisini karıştırmak, geri tuşunun bazen galeriyi, bazen siteyi kapatmasıdır. On üçüncü yazı bu karışıklığı istemiyor. Overlay bir kısayol. Adres bir belge. default.tsx boş hali. Üçü yazılınca deneyim durur.
Soft navigation şartı
Yakalama yalnızca client geçişinde çalışır. tam sayfa yapabilir, Link yumuşak yapar. Galeri içindeki kartlar Link. Dışarıdan gelen paylaşım sert. İkisini aynı href ile kuruyorum. Farklı URL uydurmak, paylaşımı bozar. Deneyim farklı, adres aynı. Bu cümle on üçüncü yazının omurgası.
Modal açıkken alttaki galerinin scroll konumunu kaybetmemek, children'ın unmount olmamasına bağlı. Slot ayrı, children ayrı. State'i URL'ye yığmadan children'ı yerinde tutmak, intercept'in kazancı. Kazancı client state modal ile taklit etmek, scroll'u da sen saklarsın. Saklamayı unutunca kullanıcı kareye dönemez. Route bunu bedava verir. Bedava olduğu için değil, iki slot olduğu için.
Kapatınca URL
back() galeri adresine döner. replace ile açtıysan back bazen site dışına çıkar. Push varsayılanım. Paylaşım zaten tam sayfa; overlay bir katman. Katmanı replace ile yok saymak, tarihi yalan söylemektir. Yalan, on üçüncü yazının istemediği şey. Adres dürüst kalsın, overlay geçici kalsın.
default.tsx, yakalama, tam sayfa — üç dosyayı bir PR'da açıyorum. Birini sonraya bırakınca staging'de tıklama çalışır, yenileme kırılır. Yenilemeyi ilk test olarak yapıyorum. Tıklama yalan söyleyebilir. Sert giriş söylemez.
Yorumlar
Yorumlar (0)
Yorumlar üyelere açık. Üye ol · Giriş yap