typescriptadvanced

Next.js Intercepting Routes for Modals

Implement modal patterns with intercepting routes that work with both soft and hard navigation.

typescript
// app/@modal/(.)photo/[id]/page.tsx
// The (.) prefix intercepts the route at the same level
import { Modal } from '@/components/Modal';

export default function PhotoModal({
  params,
}: {
  params: { id: string };
}) {
  return (
    <Modal>
      <img
        src={`/photos/${params.id}.jpg`}
        alt="Photo"
        className="max-h-[80vh] rounded-lg"
      />
    </Modal>
  );
}

// app/@modal/default.tsx
export default function ModalDefault() {
  return null;
}

// app/layout.tsx
export default function RootLayout({
  children,
  modal,
}: {
  children: React.ReactNode;
  modal: React.ReactNode;
}) {
  return (
    <html>
      <body>
        {children}
        {modal}
      </body>
    </html>
  );
}

// app/photo/[id]/page.tsx (full page fallback for hard navigation)
export default function PhotoPage({
  params,
}: {
  params: { id: string };
}) {
  return (
    <div className="flex justify-center p-8">
      <img src={`/photos/${params.id}.jpg`} alt="Photo" />
    </div>
  );
}

// components/Modal.tsx
'use client';
import { useRouter } from 'next/navigation';

export function Modal({ children }: { children: React.ReactNode }) {
  const router = useRouter();
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60"
         onClick={() => router.back()}>
      <div onClick={(e) => e.stopPropagation()}>{children}</div>
    </div>
  );
}

Use Cases

  • Image gallery modals with shareable URLs
  • Product quick-view in e-commerce listings
  • Preview overlays that work as full pages on refresh

Tags

Related Snippets

Similar patterns you can reuse in the same workflow.