Next.js 15 Fullstack (App Router + Server Actions) · Lezione

Route intercettanti per esperienze modali condivise

Usi le convenzioni (.) e (..) per mostrare i contenuti in una modale mantenendo un URL condivisibile.

Lezione 2 di 413 passaggi

Route intercettanti per esperienze modali condivise è una lezione Next.js 15 Fullstack (App Router + Server Actions) gratuita su CoddyKit. Questa è la lezione 2 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Next.js 15 Fullstack (App Router + Server Actions), e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Next.js 15 Fullstack (App Router + Server Actions) include 4 lezioni in totale.

Che cosa sono le route intercettanti?

Next.js 15 introduce le route intercettanti: una convenzione di routing che consente di caricare una route nel contesto del layout corrente, invece di passare a una pagina completamente nuova.

Il caso d'uso più comune è il pattern della modale: facendo clic su una foto in una griglia, la foto viene mostrata in una sovrapposizione modale, ma l'URL viene aggiornato a /photos/42. Se condivide quell'URL, il destinatario visualizza la pagina completa della foto, non la modale.

  • L'URL del browser cambia, quindi può essere condiviso e aggiunto ai segnalibri
  • La pagina corrente rimane visibile dietro la modale
  • Un aggiornamento completo della pagina o una navigazione diretta carica la pagina completa indipendente

Questo pattern viene talvolta chiamato pattern della navigazione soft con modale ed è stato reso popolare da Pinterest, Instagram e dallo stesso sito di Vercel.

Le convenzioni di intercettazione: (.) e (..)

Next.js usa prefissi nei nomi delle cartelle per dichiarare che una route intercetta un'altra. Il prefisso rispecchia la sintassi dei percorsi relativi:

  • (.) — intercetta una route allo stesso livello nell'albero dei segmenti
  • (..) — intercetta una route un livello più in alto
  • (...) — intercetta una route dalla root dell'app

La cartella denominata con questi prefissi si trova all'interno di uno slot di route parallelo (una cartella con prefisso @), perciò le due funzionalità lavorano insieme.

Esempio: per intercettare /photos/[id] dalla home page /, crei app/@modal/(.)photos/[id]/page.tsx.

Struttura del progetto per la modale delle foto

Costruiamo un esempio concreto: una galleria di foto in cui facendo clic su una miniatura si apre una modale, mentre l'URL diventa /photos/42.

L'albero dei file è il seguente:

app/
  layout.tsx               ← root layout with @modal slot
  page.tsx                 ← gallery grid (home)
  @modal/
    default.tsx            ← renders null (slot default)
    (.)photos/
      [id]/
        page.tsx           ← intercepted modal UI
  photos/
    [id]/
      page.tsx             ← full standalone photo page

Quando l'utente fa clic su una foto dalla galleria, Next.js visualizza @modal/(.)photos/[id]/page.tsx all'interno dello slot @modal del root layout. Un aggiornamento completo di /photos/42 visualizza normalmente photos/[id]/page.tsx.

Root layout: accettare lo slot @modal

Il root layout deve accettare lo slot parallelo @modal come prop insieme a children. È così che Next.js inserisce il contenuto della route intercettata.

// app/layout.tsx
import type { ReactNode } from 'react';

interface RootLayoutProps {
  children: ReactNode;
  modal: ReactNode;   // injected by the @modal parallel route slot
}

export default function RootLayout({ children, modal }: RootLayoutProps) {
  return (
    <html lang="en">
      <body>
        {children}
        {modal}  {/* renders null by default, modal content when intercepted */}
      </body>
    </html>
  );
}

Slot predefinito: non visualizzare nulla quando nessuna modale è attiva

Quando l'utente visita / senza alcuna intercettazione, Next.js deve visualizzare qualcosa per lo slot @modal. A questo scopo si fornisce un default.tsx che restituisce null.

Senza questo file, Next.js restituisce un 404 perché non riesce a risolvere lo slot.

// app/@modal/default.tsx
// This file is required — it tells Next.js to render nothing
// when no route is being intercepted into this slot.
export default function ModalDefault() {
  return null;
}

La pagina della route intercettante: interfaccia della modale

Ora crei la pagina della route intercettata. È ciò che viene visualizzato nello slot @modal quando l'utente esegue una navigazione soft (tramite <Link>) verso /photos/[id].

La pagina riceve gli stessi params della pagina reale e può recuperare gli stessi dati. Il contenitore della modale è semplicemente un componente: è lei a controllare lo stile della sovrapposizione.

// app/@modal/(.)photos/[id]/page.tsx
import { Modal } from '@/components/Modal';
import { getPhoto } from '@/lib/photos';

interface Props {
  params: Promise<{ id: string }>;
}

export default async function PhotoModal({ params }: Props) {
  const { id } = await params;
  const photo = await getPhoto(id);

  return (
    <Modal>
      <img
        src={photo.url}
        alt={photo.title}
        className="max-w-full max-h-screen object-contain"
      />
      <h2 className="mt-4 text-lg font-semibold">{photo.title}</h2>
    </Modal>
  );
}

Creare il componente Modale con router.back()

Il componente Modal deve chiudersi quando l'utente lo nasconde. Il modo idiomatico consiste nell'usare router.back(): questo torna indietro nella cronologia, rimuove la route intercettata e ripristina la pagina della galleria senza un ricaricamento completo.

Usi una direttiva 'use client' perché useRouter è un hook lato client.

// components/Modal.tsx
'use client';

import { useRouter } from 'next/navigation';
import { useEffect, useCallback, type ReactNode } from 'react';

export function Modal({ children }: { children: ReactNode }) {
  const router = useRouter();

  const close = useCallback(() => router.back(), [router]);

  // Close on Escape key
  useEffect(() => {
    const handler = (e: KeyboardEvent) => {
      if (e.key === 'Escape') close();
    };
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, [close]);

  return (
    <div
      className="fixed inset-0 z-50 flex items-center justify-center bg-black/70"
      onClick={close}  // click backdrop to close
    >
      <div
        className="relative bg-white rounded-xl p-6 max-w-2xl w-full"
        onClick={(e) => e.stopPropagation()}  // prevent backdrop close on content
      >
        <button
          onClick={close}
          className="absolute top-3 right-3 text-gray-500 hover:text-gray-900"
          aria-label="Close modal"
        >
          ✕
        </button>
        {children}
      </div>
    </div>
  );
}

La pagina completa della foto: fallback per la navigazione diretta

La pagina indipendente /photos/[id] è quella che gli utenti visualizzano quando visitano direttamente l'URL, ad esempio tramite un link condiviso, un aggiornamento completo della pagina o un crawler di un motore di ricerca. Visualizza il layout completo della pagina senza alcun contenitore modale.

Questa pagina e quella intercettata possono condividere la stessa funzione di recupero dei dati (getPhoto): non è necessaria alcuna duplicazione per il livello di accesso ai dati.

// app/photos/[id]/page.tsx
import { getPhoto } from '@/lib/photos';
import Link from 'next/link';

interface Props {
  params: Promise<{ id: string }>;
}

export default async function PhotoPage({ params }: Props) {
  const { id } = await params;
  const photo = await getPhoto(id);

  return (
    <main className="container mx-auto py-12">
      <Link href="/" className="text-blue-600 hover:underline mb-6 inline-block">
        ← Back to gallery
      </Link>
      <img
        src={photo.url}
        alt={photo.title}
        className="w-full rounded-xl shadow-lg"
      />
      <h1 className="mt-6 text-3xl font-bold">{photo.title}</h1>
      <p className="mt-2 text-gray-600">{photo.description}</p>
    </main>
  );
}

La pagina della galleria: collegarsi alla modale

La pagina iniziale della galleria si collega a /photos/[id] usando un componente <Link> standard. Next.js rileva automaticamente la route intercettante: non è necessaria alcuna prop speciale sul link.

Quando JavaScript è abilitato e l'utente fa clic sul link, l'intercettazione viene eseguita. Senza JavaScript (o al primo caricamento), la pagina completa viene caricata normalmente.

// app/page.tsx
import Link from 'next/link';
import { getPhotos } from '@/lib/photos';

export default async function GalleryPage() {
  const photos = await getPhotos();

  return (
    <main className="container mx-auto py-12">
      <h1 className="text-3xl font-bold mb-8">Photo Gallery</h1>
      <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
        {photos.map((photo) => (
          <Link key={photo.id} href={`/photos/${photo.id}`}>
            <img
              src={photo.thumbnailUrl}
              alt={photo.title}
              className="rounded-lg aspect-square object-cover hover:opacity-90 transition"
            />
          </Link>
        ))}
      </div>
    </main>
  );
}

Usare (..) per intercettare un livello più in alto

Il prefisso (..) intercetta una route che si trova un segmento più in alto nell'albero degli URL rispetto alla posizione dello slot.

Consideri una pagina di prodotto all'indirizzo /shop/[category]/[productId]. Desidera che facendo clic su un prodotto all'interno di /shop/[category] si apra una modale. Lo slot @modal si trova all'interno di app/shop/[category]/, ma la route di destinazione è anch'essa un livello più in basso; quindi usa (..) per risalire di un livello e intercettarla da lì.

Esempio di percorso del file:

app/shop/[category]/
  @modal/
    default.tsx
    (..)shop/[category]/[productId]/
      page.tsx   ← intercepts the product page

La regola generale è contare quanti livelli di segmenti separano la cartella @modal dalla route di destinazione e usare altrettanti punti.

Interfaccia di caricamento e Suspense nelle route intercettate

Le pagine delle route intercettate sono Server Components asincroni, quindi è possibile racchiuderle in Suspense per mostrare uno skeleton durante il recupero dei dati, esattamente come per qualsiasi altra route Next.js.

Aggiunga un file loading.tsx accanto alla pagina intercettata oppure racchiuda il contenuto in <Suspense fallback={...}> direttamente nel componente della modale.

// app/@modal/(.)photos/[id]/loading.tsx
// Shown while the async PhotoModal page is fetching data.
export default function PhotoModalSkeleton() {
  return (
    <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/70">
      <div className="bg-white rounded-xl p-6 max-w-2xl w-full animate-pulse">
        <div className="h-80 bg-gray-200 rounded-lg" />
        <div className="mt-4 h-6 bg-gray-200 rounded w-1/2" />
      </div>
    </div>
  );
}

Verifica delle conoscenze: quale convenzione usare?

Verifichi la sua comprensione delle convenzioni delle route intercettanti.

Riepilogo della lezione: route intercettanti per esperienze con modali

In questa lezione ha imparato a implementare il pattern della modale che mantiene l'URL in Next.js 15 usando le route intercettanti.

  • La convenzione (.) intercetta una route allo stesso livello di segmenti; (..) risale di un livello; (...) raggiunge la root dell'app.
  • Le route intercettanti si trovano sempre all'interno di uno slot di route parallelo (@modal), così il layout padre può visualizzare contemporaneamente sia la pagina di sfondo sia la modale.
  • Nello slot è necessario un default.tsx che restituisca null per evitare errori 404 quando nessuna modale è attiva.
  • La modale si chiude con router.back(), che rimuove la route intercettata dalla cronologia senza un ricaricamento completo.
  • La pagina completa indipendente al percorso reale della route continua a esistere: gli aggiornamenti completi e i link condivisi caricano sempre la pagina completa, rendendo l'URL realmente condivisibile.
  • Aggiunga loading.tsx o Suspense all'interno della route intercettata per mostrare uno skeleton durante il caricamento dei dati asincroni.

Questo pattern offre agli utenti un'esperienza rapida simile a quella di un'app, mantenendo al contempo le pagine ottimizzate per la SEO e accessibili a chiunque disponga semplicemente di un link.

Gratis per iniziare

Impara TypeScript con un tutor IA — gratis

Scrivi ed esegui vero codice nel tuo browser, ricevi aiuto istantaneo da un tutor IA disponibile 24/7, e riprendi da dove hai lasciato sul web o nell'app.

Corsi
22
Lezioni
88

Domande Frequenti

La lezione «Route intercettanti per esperienze modali condivise» è gratuita?

Sì — il testo completo di «Route intercettanti per esperienze modali condivise» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Next.js 15 Fullstack (App Router + Server Actions), passa a CoddyKit PRO. Il corso Next.js 15 Fullstack (App Router + Server Actions) include 4 lezioni in totale.

Cosa imparerò in «Route intercettanti per esperienze modali condivise»?

Usi le convenzioni (.) e (..) per mostrare i contenuti in una modale mantenendo un URL condivisibile. Eserciti Next.js 15 Fullstack (App Router + Server Actions) con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Next.js 15 Fullstack (App Router + Server Actions)?

Non è richiesta alcuna esperienza precedente. Next.js 15 Fullstack (App Router + Server Actions) su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 2 di 4.

Quanto tempo richiede la lezione «Route intercettanti per esperienze modali condivise»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Next.js 15 Fullstack (App Router + Server Actions)?

Sì. Ogni lezione Next.js 15 Fullstack (App Router + Server Actions) include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Route parallele con slot nominati e segmenti predefiniti
  2. Route intercettanti per esperienze modali condivise
  3. Rendering condizionale degli slot per dashboard e tab
  4. Creazione di un flusso foto-modale con navigazione morbida
← Torna a Next.js 15 Fullstack (App Router + Server Actions)