patterntypescriptnextjsTip
Intercepting Routes: (..) convention for modal patterns
Viewed 0 times
Next.js 13.3+ with App Router
intercepting routesmodal routingphoto gallery(..) conventionparallel routes modalclient navigation
Problem
Showing a photo in a modal when clicked from a gallery, while keeping the full photo page accessible via direct URL, requires complex client-side state management in traditional routing.
Solution
Use intercepting routes with the (.) convention to intercept navigation:
// File structure for photo gallery with modal:
// app/photos/page.tsx — gallery grid
// app/photos/[id]/page.tsx — full photo page (direct URL)
// app/photos/@modal/(..)photos/[id]/page.tsx — intercepted modal
// app/photos/layout.tsx — renders both
// The (..) means 'intercept the parent segment'
// (.) = same segment, (..) = one level up, (...) = root
// app/photos/layout.tsx
export default function Layout({ children, modal }) {
return (
<>
{children}
{modal}
</>
);
}
// app/photos/@modal/(..)photos/[id]/page.tsx
import { PhotoModal } from '@/components/PhotoModal';
export default function ModalPage({ params }) {
return <PhotoModal id={params.id} />;
}
// PhotoModal uses useRouter().back() to close:
'use client';
import { useRouter } from 'next/navigation';
export function PhotoModal({ id }) {
const router = useRouter();
return (
<dialog open>
<button onClick={() => router.back()}>Close</button>
<Photo id={id} />
</dialog>
);
}
// File structure for photo gallery with modal:
// app/photos/page.tsx — gallery grid
// app/photos/[id]/page.tsx — full photo page (direct URL)
// app/photos/@modal/(..)photos/[id]/page.tsx — intercepted modal
// app/photos/layout.tsx — renders both
// The (..) means 'intercept the parent segment'
// (.) = same segment, (..) = one level up, (...) = root
// app/photos/layout.tsx
export default function Layout({ children, modal }) {
return (
<>
{children}
{modal}
</>
);
}
// app/photos/@modal/(..)photos/[id]/page.tsx
import { PhotoModal } from '@/components/PhotoModal';
export default function ModalPage({ params }) {
return <PhotoModal id={params.id} />;
}
// PhotoModal uses useRouter().back() to close:
'use client';
import { useRouter } from 'next/navigation';
export function PhotoModal({ id }) {
const router = useRouter();
return (
<dialog open>
<button onClick={() => router.back()}>Close</button>
<Photo id={id} />
</dialog>
);
}
Why
Intercepting routes let you load a route in a different context (e.g., a modal) while keeping the URL updated. Refreshing the page shows the full page (not the modal), because interception only happens during client-side navigation.
Gotchas
- Interception only works on client-side navigation — hard refresh always shows the actual route
- Combine with parallel routes (@modal slot) to render the modal alongside the background page
- (..) matches one URL segment up, not one filesystem folder up
- The intercepting folder must be inside a parallel route slot (@modal) to work correctly
Code Snippets
Intercepting route for photo modal
// Folder: app/photos/@modal/(..)photos/[id]/page.tsx
// This intercepts /photos/:id navigation and renders as modal
export default function InterceptedPhoto({ params }) {
return <PhotoModal photoId={params.id} />;
}Context
When implementing modal patterns where the modal content also has a dedicated full page URL
Revisions (0)
No revisions yet.