cf.completefrontend
Open lab My progress
THE NEXT.JS FIELD GUIDE

Intercepting routes and modals

Open a tea or a sign-in form in a modal on top of the page with intercepting routes, keep a shareable URL, and show the full page on a reload.

By the end, you can
  • 01
    Name an intercepting folderPick (.), (..), (..)(..) or (...) by counting route segments, and say which clicks it intercepts.
  • 02
    Build a modal with a real addressCombine a @modal slot, an intercepting route and default.tsx, so a click opens a modal and a reload shows the full page.
  • 03
    Close it the right wayClose with router.back(), keep the modal from following the visitor, and make it work with the keyboard and screen readers.

One address, two ways to show it

In the tea shop's list of teas, a visitor often wants a quick look at one tea without leaving the list. The tea should still have an address of its own, like /teas/green, that the visitor can share, bookmark and reload. Intercepting routes give one address two pages: one for a client-side navigation from the right part of the app, like a click on the tea in the list, and one for every other way in.

A folder whose name starts with (.), (..) or (...) is an intercepting route. The rest of its name is another route, the one it intercepts, counted from where the folder sits. Here, app/teas/(.)[id] intercepts /teas/[id], the address of each tea ([id] is a dynamic segment). This shop is a real Next.js 16.4.3 project, built with next build, run with next start, and clicked through in real Chrome. Its root layout shows a line, You are on /teas., from a small Client Component, app/address.tsx, that reads the address with usePathname(), so each step shows where the visitor is.

A quick look that intercepts a tea's addressNext.js
app/_lib/cart/teas/(.)[id]/[id]/
app/teas/(.)[id]/page.tsx
import { findTea, teas } from "@/app/_lib/teas"; export function generateStaticParams() {  return teas.map((tea) => ({ id: tea.id }));} export default async function QuickLook({ params }: { params: Promise<{ id: string }> }) {  const { id } = await params;  const tea = findTea(id);  return (    <main>      <p>Quick look: {tea?.name}, ${tea?.price} a box</p>    </main>  );}
Recorded from Next.js 16.4.3

Each step was taken in real Chrome against next start. Pick one to see the page after it.

localhost:3000/teas/greenTea shop
Home Teas Cart

You are on /teas/green.

Quick look: Green tea, $4 a box

Focus
nothing: the page itself
Scrolled
0 px down

No requests during this step: the page already had what it needed

The file shown first is app/teas/(.)[id]/page.tsx. Line 12 is the whole quick look: one line with the tea's name and price. Lines 3 to 5 list every tea's id for the build with generateStaticParams, as the real tea page does (generateStaticParams explains why a page that awaits params needs it, or a loading state, with Cache Components on). The real page is app/teas/[id]/page.tsx: the tea's name, its note, its price and a link back to the list. The Steps tab lists three steps:

  • Step 1, open /teas: the page, then the router's prefetches, which ask early for the pages the links point to. Each one carries the header next-url: /teas: the router telling the server which address the visitor is on.
  • Step 2, click Green tea: the address is /teas/green, and the page shows Quick look: Green tea, $4 a box. Whatever the router still needed for it, it asked for with the same next-url: /teas, or had already prefetched.
  • Step 3, reload /teas/green: one document request, for a whole page, and the full tea page: Green tea, Fresh and grassy. Brew it for 3 minutes., $4 a box and All teas.

One address, two pages. A click on a Link is a soft navigation: Next.js's router changes the page in place, without loading a new HTML page; so is a call to router.push. Only a soft navigation can be intercepted, and only one that starts from the intercepting folder's route or below it: here, /teas. A reload, an address typed into the browser, or a link from another site is a hard navigation: the browser asks the server for a whole new page, and gets the real one. Here, the quick look took the list's place. A modal keeps the list and puts the tea on top of it, with one more folder: a slot, from Parallel routes.

A card for the address slash teas slash green. One arrow, labeled a click from slash teas, goes to a window that shows the page from teas/(.)[id]/page.tsx. Another arrow, labeled a reload, goes to a window that shows the page from teas/[id]/page.tsx./teas/greena click from /teasa reloadsoftteas/(.)[id]/page.tsxhardteas/[id]/page.tsxthe quick lookthe full page
The same address, /teas/green, has two pages. A click from /teas gets the intercepting one, in (.)[id]. A reload, or any other full page load, gets the real one, in [id].
Real-life analogyA book at your table, or on its shelf

In a library, every book has a call number. If you're already at a reading table and ask for one, the librarian brings it to your table, and your other books stay open around it. If you walk in from the street with the same call number, you're shown to the shelf where it lives.

In real life: The book's call number
In Next.js: The address, /teas/green
In real life: You ask at your reading table, and the book is brought to you
In Next.js: A click from /teas: the tea opens on top of the list
In real life: You walk in from the street with the call number
In Next.js: A reload or a shared link: a hard navigation
In real life: You're shown to the shelf where the book lives
In Next.js: The full page, app/teas/[id]/page.tsx
The main idea

An intercepting route shows another route's address with a different page, but only on a client-side navigation that starts from the intercepting folder's route or below it. A reload or a shared link shows the real page. Put the intercepting route in a slot, and the page you came from stays underneath: a modal with an address of its own.

(.), (..), (..)(..) and (...) count route segments

The marker at the start of the folder's name says where the intercepted route starts, like ./ and ../ in a file path:

  • (.) starts at the same level as the folder's own route.
  • (..) starts one level up.
  • (..)(..) starts two levels up.
  • (...) starts at the root of the app.

The levels are route segments: the parts of the address between slashes. Only folders that add a segment count. A slot like @modal adds none, and neither does a route group like (shop), from Route groups and private folders. So a folder inside app/teas/@modal counts from /teas, not from @modal. Here the shop's teas get a layout with a modal slot, and the cart opens in it:

Playground: which marker intercepts /cart?
The tea shop, with the cart modal in this folderNext.js
app/_lib/cart/teas/@modal/(.)cart/[id]/
app/teas/@modal/(.)cart/page.tsx
import Modal from "@/app/modal"; export default function CartModal() {  return (    <Modal title="Your cart">      <p>2 boxes of Green tea</p>    </Modal>  );}
Recorded from Next.js 16.4.3

Each step was taken in real Chrome against next start. Pick one to see the page after it.

localhost:3000/cartTea shop
Home Teas Cart

You are on /cart.

Your cart

2 boxes of Green tea

Focus
the link “Cart”
Scrolled
0 px down

No requests during this step: the page already had what it needed

Try it yourself
(.)cart

(.) is the same level as /teas, so this folder means /teas/cart. The build passes, but a click on Cart goes to /cart, which it never matches: step 4 shows the full cart page.

Every version was built with next build from Next.js 16.4.3. The ones that built were clicked through in real Chrome: open /, click Cart, click Teas, then click Cart on /teas. Only the folder's name changes. Reset goes back to the first version.

Before you read on, pick each marker and look at step 4, a click on Cart from /teas. With (.)cart, the build passed and listed ○ /teas/(.)cart, but step 4 shows the full cart page: (.) counted from /teas, so the folder intercepts /teas/cart, an address no link goes to. With (..)cart and (...)cart, step 4 shows Your cart in the modal, on top of the list. (..)(..)cart would start above the root, and the build stops: Cannot use (..)(..) marker at the root level or one level up. Each row is one marker, in a folder inside app/teas/@modal:

Four markers, counted from /teas
MarkerWhere it startsWhat it means here
(.)The same segment level as the folder's route: /teas.(.)cart is /teas/cart.
(..)One segment up from /teas: /.(..)cart is /cart.
(..)(..)Two segments up from /teas: above the root.The build stops.
(...)The root, wherever the folder is.(...)cart is /cart.
An editor window for app/teas/@modal, with the note that its route is slash teas, because @modal is not a segment. Arrows go to four cards: (.)cart means slash teas slash cart, (..)cart means slash cart, (...)cart means slash cart, and (..)(..)cart would go above the root, so it is crossed out.teas/@modalits route: /teas@modal adds no segment(.)cart: /teas/cart(..)cart: /cart(...)cart: /cart(..)(..)cart: above /the build stops on the last one
Each marker counts route segments from the folder's route. @modal isn't a segment, so a folder in app/teas/@modal counts from /teas. Two segments up from there is above the root, and the build stops.

Next.js's own error proves that slots don't count. Put the tea modal in app/@modal/(..)teas/[id], and the build stops with Invalid interception route: /(..)teas/[id]. Cannot use (..) marker at the root level, use (.) instead. The route it names has no @modal in it: Next.js dropped the slot, found the folder at the root, and saw nothing above it.

Steps 1 and 2 show the other half of the rule: where the visitor is. In every version, step 2 clicks Cart on /, and the full cart page shows, even with (..)cart. An intercepting route only catches clicks from the route its folder belongs to, or a route below it. That's what next-url is for: app/teas/@modal/(..)cart intercepts /cart only when next-url is /teas or starts with /teas/.

Read it in Next.js's own code

Lines 166 to 211 of interception-routes.ts turn a folder into the route it intercepts. Line 166 removes groups and slots from the folder's route, then one case per marker counts segments from there. Then lines 34 to 55 of generate-interception-routes-rewrites.ts make the rule for each one: the intercepted address, and a next-url that is the folder's route or below it.

A click or a reload decides what shows

What shows depends on how the visitor arrived, not on the address. Each row is one thing the visitor did in the recorded steps of the modal above:

What the visitor did, and what showed
What the visitor didWhat showedRecorded in
Clicks Green tea on /teasThe modal, on top of the list.Steps 2, 4 and 8.
Presses Escape, or clicks CloseThe list, at /teas.Steps 3 and 5.
Presses the browser's Back buttonThe list, at /teas.Step 7.
Clicks Open the full page, a plain <a>The tea's full page, after a full page load.Step 9.
Reloads /teas/green, or opens it from a shared linkThe tea's full page.Step 10.

Steps 9 and 10 are two ways to the full page. Step 9 clicks Open the full page, the plain <a> on line 17. A plain link isn't handled by Next.js's router, so the browser loads a whole new page: the step made one document request, and a full page load is never intercepted. Step 10 reloads, with the same result. That's how a modal can offer its full page.

Step through both below. Before you start, guess which step decides between the modal and the full page:

Playground: who decides between the modal and the full page
Who does whatA click from /teas
What the visitor seesafter step 1
/teas

Home Teas Cart

You are on /teas.

Our teas

Green tea

Mint tea

React isn't running yet: clicks do nothing.

The page isn't complete yet.

Step 1 of 5
Browser: The browser shows /teas

A full page load of the list: the HTML arrives with both teas in it.

Each step is one thing that happens, in order. The screens and the headers are from the recordings of the modal above, in real Chrome. Times aren't to scale. Reset goes back to the first step of the click.

In the click, step 4 decides. The address and next-url both match the intercepting route, so the server answers with the modal's page. In the reload, there's no next-url to read: a whole page is asked for by the browser, which knows nothing about where the visitor was. The recorded document requests carry none of the router's headers.

Closing the modal

The modal has its own address, so closing it means leaving that address, back to /teas. The recording closes it three ways:

  • Escape, in step 3, and the Close button, in step 5. The button sits in a <form method="dialog">, on lines 16 to 18 of app/modal.tsx: a form that closes its dialog instead of sending anything. Both close the <dialog>, and line 15's onClose calls router.back().
  • The browser's Back button, in step 7. The browser goes back on its own: the address is /teas, the modal is gone, and the address stayed /teas, one entry back, not two.
Three cards on the left: Escape, Close button, and the browser's Back button. Arrows from Escape and Close go to a card that says the dialog closes, then to a card that says router.back(). Arrows from router.back() and from the Back button both go to a tag for slash teas.EscapeClose buttonBack buttondialog closesrouter.back()/teas
Escape and the Close button close the <dialog>, and its onClose calls router.back(). The browser's Back button goes back on its own. All three end on /teas, one history entry back.

router.back() goes to the previous entry in the browser's history, like the Back button, as Linking and navigating showed. Closing the modal that way leaves the history as the visitor saw it: the list, then the tea they opened, and back. Next.js's docs also show closing with a Link to /teas; that's a new navigation, with a new history entry, and it needs the catch-all page further down.

router.back() assumes there's a page to go back to, inside the shop. The modal only exists after a client-side navigation from /teas or below, so for the modal that's always true. The full page is different: a visitor who opens a shared link arrives on it directly. Here the full page has a Back button that calls router.back(), and Chrome opened its address the way a shared link does:

A full page that goes back with router.back()Next.js
app/@modal/(.)teas/[id]/_lib/cart/teas/[id]/
app/teas/[id]/page.tsx
import BackButton from "@/app/back-button";import { notFound } from "next/navigation";import { findTea, teas } from "@/app/_lib/teas"; export function generateStaticParams() {  return teas.map((tea) => ({ id: tea.id }));} export default async function TeaPage({ params }: PageProps<"/teas/[id]">) {  const { id } = await params;  const tea = findTea(id);  if (!tea) notFound();  return (    <main>      <h1>{tea.name}</h1>      <p>{tea.note}</p>      <p>${tea.price} a box</p>      <BackButton />    </main>  );}
Recorded from Next.js 16.4.3

Each step was taken in real Chrome against next start. Pick one to see the page after it.

localhost:3000blank
Focus
nothing: the page itself
Scrolled
0 px down

No requests during this step: the page already had what it needed

Step 2 clicked Back, and the visitor left the shop. The recording labels the address blank: that's Chrome's empty tab, about:blank, which was this window's previous page. For a real visitor, it's the chat app the link came from, or nothing at all in a new tab. So the full page links to the list with a Link, as line 18 of the real app/teas/[id]/page.tsx does, and only the modal uses router.back().

One more trap. Give the modal a link to another page, like Go to your cart, and click it. Guess first: does the modal close?

Playground: does the modal follow the visitor to the cart?
The tea modal, with a link to the cartNext.js
app/@modal/(.)teas/[id]/_lib/cart/teas/[id]/
app/@modal/(.)teas/[id]/page.tsx
import Link from "next/link";import { notFound } from "next/navigation";import Modal from "@/app/modal";import { findTea, teas } from "@/app/_lib/teas"; export function generateStaticParams() {  return teas.map((tea) => ({ id: tea.id }));} export default async function TeaModal({ params }: { params: Promise<{ id: string }> }) {  const { id } = await params;  const tea = findTea(id);  if (!tea) notFound();  return (    <Modal title={tea.name}>      <p>{tea.note}</p>      <p>${tea.price} a box</p>      <a href={`/teas/${tea.id}`}>Open the full page</a> <Link href="/cart">Go to your cart</Link>    </Modal>  );}
Recorded from Next.js 16.4.3

Each step was taken in real Chrome against next start. Pick one to see the page after it.

localhost:3000/cartTea shop
Home Teas Cart

You are on /cart.

Your cart

2 boxes of Green tea

Close
Green tea

Fresh and grassy. Brew it for 3 minutes.

$4 a box

Open the full page Go to your cart
Focus
the link “Go to your cart”
Scrolled
0 px down

No requests during this step: the page already had what it needed

Try it yourself
Only default.tsx

Go to your cart is a client-side navigation to /cart. @modal has no page for /cart, so it keeps its last page: the modal stays open on top of the cart.

Both versions were built with next build from Next.js 16.4.3 and clicked through in real Chrome: open /teas, click Green tea, then click Go to your cart in the modal. The second adds one file. Reset goes back to the first version.

With only default.tsx, the cart shows, and the modal stays open on top of it. It's the parallel routes rule again: on a client-side navigation, a slot with nothing for the new address keeps its last page. default.tsx only shows when no page in the slot matches the address. The fix is a page in the slot that matches the other addresses and shows nothing: app/@modal/[...rest]/page.tsx, a catch-all segment (Dynamic routes showed them) that returns null. It handles every address outside the modal that has at least one segment, like /cart, on a click and after a full page load alike. Keep default.tsx too: it's the fallback when no page in the slot matches, which here is mainly /, because [...rest] needs at least one segment.

A modal everyone can use

A modal blocks the page behind it, so people who use a keyboard or a screen reader need to know it's there and how to leave it. The HTML <dialog> element, opened with its showModal() method, does most of that for you. Here is app/modal.tsx again, with the steps that pressed Escape:

The modal component, in ChromeNext.js
app/@modal/(.)teas/[id]/_lib/cart/teas/[id]/
app/modal.tsx
"use client"; import { useEffect, useRef } from "react";import { useRouter } from "next/navigation"; export default function Modal({ title, children }: { title: string; children: React.ReactNode }) {  const router = useRouter();  const dialog = useRef<HTMLDialogElement>(null);   useEffect(() => {    dialog.current?.showModal();  }, []);   return (    <dialog ref={dialog} aria-labelledby="modal-title" onClose={() => router.back()}>      <form method="dialog">        <button>Close</button>      </form>      <h2 id="modal-title">{title}</h2>      {children}    </dialog>  );}
Recorded from Next.js 16.4.3

Each step was taken in real Chrome against next start. Pick one to see the page after it.

localhost:3000/teasTea shop
Home Teas Cart

You are on /teas.

Our teas
  • Green tea
  • Mint tea
Focus
the link “Green tea”
Scrolled
0 px down

No requests during this step: the page already had what it needed

Line 8 makes a ref to the <dialog> element, and the Effect on lines 10 to 12 calls showModal() once the dialog is on the page. Line 15 names the dialog with aria-labelledby, which points at the heading on line 19, so screen readers can announce the dialog by its heading, like Green tea. Each row is one thing a modal needs: how <dialog> gives it, and where the recording shows it.

What a modal needs
What it needsHow the dialog gives itWhere you can see it
Focus moves into the modal.showModal() focuses the first thing in it you can focus.Steps 2, 4, 6 and 8: focus on Close.
Escape closes it.The browser closes the dialog; onClose calls router.back().Step 3: back on /teas.
Focus goes back where it was.Closing the dialog returns focus to what had it before.Steps 3 and 5: focus on the Green tea link.
The page behind can't be used.showModal() makes the rest of the page inert.Not recorded: from the HTML standard.
Screen readers hear its name.aria-labelledby points at the heading.Line 15 of app/modal.tsx.

Steps 2, 4, 6 and 8 have the focus on Close, the first thing in the dialog you can focus. Steps 3 and 5 have it back on the Green tea link, so a keyboard user carries on from where they were (Keyboard and focus explains focus). The page behind being inert (it can't be clicked, and Tab skips it) comes from the HTML standard for showModal(); the recording doesn't show it.

Two look-alikes that aren't modals

A <div> styled to look like a box on top of the page moves no focus, ignores Escape, and leaves the page behind it usable. A <dialog open>, shown with the open attribute instead of showModal(), is a non-modal dialog: the page behind stays usable. Use showModal(), and see Accessible React for the rest.

A sign-in form in the same slot

One slot can hold many modals. The shop adds Sign in to its links. app/@modal/(.)sign-in/page.tsx shows the form in the same Modal, and app/sign-in/page.tsx is the full page. Both import one form, from app/sign-in/sign-in-form.tsx, so they never drift apart. The form is a stand-in: it doesn't sign anyone in. The Authentication lesson builds a real one. The slot also has the catch-all page.

Sign in, in the modal or on its own pageNext.js
app/@modal/(.)sign-in/(.)teas/[id]/[...rest]/_lib/cart/sign-in/teas/[id]/
app/@modal/(.)sign-in/page.tsx
import Modal from "@/app/modal";import SignInForm from "@/app/sign-in/sign-in-form"; export default function SignInModal() {  return (    <Modal title="Sign in">      <SignInForm />    </Modal>  );}
Recorded from Next.js 16.4.3

Each step was taken in real Chrome against next start. Pick one to see the page after it.

localhost:3000/sign-inTea shop
Home Teas Cart Sign in

You are on /sign-in.

Tea shop

Fresh tea, sent to your door.

Close
Sign in
Send me a sign-in link
Focus
the button “Close”
Scrolled
0 px down

No requests during this step: the page already had what it needed

In the file shown first, lines 6 and 7 put the shared form inside Modal, with the heading Sign in. Step 2 clicks Sign in on the home page: the address is /sign-in, the home page is still there, and the form is in the modal, with focus on Close. (.)sign-in sits at the root's level, so it catches clicks from every page. Step 3 types asha@example.com into Email. Step 4 presses Escape: the address is / again, with focus back on the Sign in link. Step 5 reloads /sign-in, and gets the full page. The Terminal tab lists both ○ /(.)sign-in and ○ /sign-in.

Sort these visits: does the visitor get the modal, or the full page?

Modal or full page?
  • Click Green tea on /teas
  • Reload while the modal is open
  • Open /teas/green from a link in a chat app
  • Click Open the full page, a plain <a>
  • Click Mint tea on /teas
  • Click Sign in on the home page
  • Click Cart on /, with app/teas/@modal/(..)cart
Try it yourself
0 of 7 correct

Sort each visit by what the visitor sees, from the recordings in this shop.

Choose a category for every card. You can change an answer at any time; Reset clears them all.

Common mix-ups

  • "The markers count folders." No. They count route segments: slots and route groups don't count. (..) in app/@modal stopped the build.
  • "An intercepted address always shows the modal." No. Only a client-side navigation from the folder's route or below does: Cart from / showed the full cart. A reload or a shared link showed the full page.
  • "The modal's page is enough." No. Next.js 16.4.3 stops the build without the full page: Interception routes must have a canonical route.
  • "default.tsx closes the modal when the visitor clicks away." No. On a click, a slot keeps its last page, so on a click to the cart the slot kept the modal open. A [...rest] page that returns null closes it.
  • "A Back button with router.back() is fine on the full page." No. A visitor from a shared link has no page of yours to go back to: the recorded Back left the shop.
  • "Any box on top of the page is a modal." No. A modal moves focus into itself, closes with Escape, and keeps the page behind out of reach. showModal() does all three.

Each row is one file of the modal: when it shows, and why it's there.

The modal's files
FileWhen it showsWhy it's there
app/@modal/(.)teas/[id]/page.tsxAfter a client-side navigation to a tea, from anywhere in the app: the slot sits at the root.It's the modal itself.
app/teas/[id]/page.tsxAfter a reload, a typed address or a shared link.It's the canonical route. The build stops without it.
app/@modal/default.tsxWhen no page in the slot matches the address. With [...rest] beside it, that's mainly /.The slot needs something for every address. The build stops without it.
app/@modal/[...rest]/page.tsxOn every other address outside the modal, like /cart, on a click and after a full page load.It empties the slot, so the modal doesn't follow the visitor.

Practice exercises

These exercises are about Next.js projects, which can't run inside this page. Answer first, then use the hints.

Exercise 1 · Warm-upRead an intercepting folder

Which address does app/@modal/(.)teas/[id]/page.tsx intercept?

Starter codeText
app/
  @modal/
    (.)teas/
      [id]/
        page.tsx
    default.tsx
  _lib/
    teas.ts
  cart/
    page.tsx
  teas/
    [id]/
      page.tsx
    page.tsx
  address.tsx
  layout.tsx
  modal.tsx
  page.tsx

Answer, then press Check. Spacing and letter case don’t matter.

    Exercise 2 · Warm-upA link from a chat app

    A friend sends a visitor the address /teas/mint in a chat app, and the visitor opens it. Do they see the modal, or the full page?

    Starter codeTSX
    import Link from "next/link";
    import { notFound } from "next/navigation";
    import { findTea, teas } from "@/app/_lib/teas";
    
    export function generateStaticParams() {
      return teas.map((tea) => ({ id: tea.id }));
    }
    
    export default async function TeaPage({ params }: PageProps<"/teas/[id]">) {
      const { id } = await params;
      const tea = findTea(id);
      if (!tea) notFound();
      return (
        <main>
          <h1>{tea.name}</h1>
          <p>{tea.note}</p>
          <p>${tea.price} a box</p>
          <Link href="/teas">All teas</Link>
        </main>
      );
    }
    

    Answer, then press Check. Spacing and letter case don’t matter.

      Exercise 3 · PracticeName the folder

      The shop wants Cart to open in the modal when it's clicked on /teas. What should the folder in app/teas/@modal be called?

      Starter codeText
      app/
        _lib/
          teas.ts
        cart/
          page.tsx
        teas/
          @modal/
            ????cart/
              page.tsx
            default.tsx
          [id]/
            page.tsx
          layout.tsx
          page.tsx
        address.tsx
        layout.tsx
        modal.tsx
        page.tsx

      Answer, then press Check. Spacing and letter case don’t matter.

        Exercise 4 · PracticeFix the build

        With these files, next build stops: Parallel route slots cannot render the same URLs. Which file is missing?

        Starter codeText
        app/
          @modal/
            (.)teas/
              [id]/
                page.tsx
          _lib/
            teas.ts
          cart/
            page.tsx
          teas/
            [id]/
              page.tsx
            page.tsx
          address.tsx
          layout.tsx
          modal.tsx
          page.tsx

        Answer, then press Check. Spacing and letter case don’t matter.

          Exercise 5 · ChallengeKeep the modal from following the visitor

          Line 18 adds Go to your cart to the modal. A click on it shows the cart with the modal still open on top. Which file empties the slot?

          Starter codeTSX
          import Link from "next/link";
          import { notFound } from "next/navigation";
          import Modal from "@/app/modal";
          import { findTea, teas } from "@/app/_lib/teas";
          
          export function generateStaticParams() {
            return teas.map((tea) => ({ id: tea.id }));
          }
          
          export default async function TeaModal({ params }: { params: Promise<{ id: string }> }) {
            const { id } = await params;
            const tea = findTea(id);
            if (!tea) notFound();
            return (
              <Modal title={tea.name}>
                <p>{tea.note}</p>
                <p>${tea.price} a box</p>
                <a href={`/teas/${tea.id}`}>Open the full page</a> <Link href="/cart">Go to your cart</Link>
              </Modal>
            );
          }
          

          Answer, then press Check. Spacing and letter case don’t matter.

            Exercise 6 · ChallengeA full page that strands the visitor

            This full page closes with <BackButton /> on line 18, which calls router.back(). What should replace it, so a visitor from a shared link stays in the shop?

            Starter codeTSX
            import BackButton from "@/app/back-button";
            import { notFound } from "next/navigation";
            import { findTea, teas } from "@/app/_lib/teas";
            
            export function generateStaticParams() {
              return teas.map((tea) => ({ id: tea.id }));
            }
            
            export default async function TeaPage({ params }: PageProps<"/teas/[id]">) {
              const { id } = await params;
              const tea = findTea(id);
              if (!tea) notFound();
              return (
                <main>
                  <h1>{tea.name}</h1>
                  <p>{tea.note}</p>
                  <p>${tea.price} a box</p>
                  <BackButton />
                </main>
              );
            }
            

            Answer, then press Check. Spacing and letter case don’t matter.

              Check your understanding

              The intercepting routes quiz · 7 questionsScore: first tries count
              1. Question 1 of 7In app/teas/@modal, which address does a folder named (..)cart intercept?

                Choose an answer to see the explanation.

              2. Question 2 of 7The tea modal's folder is app/@modal/(.)teas/[id]. Why (.), and not (..)?

                Read the code, then predictText
                app/
                  @modal/
                    (.)teas/
                      [id]/
                        page.tsx
                    default.tsx
                  _lib/
                    teas.ts
                  cart/
                    page.tsx
                  teas/
                    [id]/
                      page.tsx
                    page.tsx
                  address.tsx
                  layout.tsx
                  modal.tsx
                  page.tsx

                Choose an answer to see the explanation.

              3. Question 3 of 7With app/teas/@modal/(..)cart, a visitor on / clicks Cart. What shows?

                Choose an answer to see the explanation.

              4. Question 4 of 7The modal is open at /teas/green, and the visitor reloads. What do they get?

                Choose an answer to see the explanation.

              5. Question 5 of 7The visitor opened the modal with the Green tea link and presses Escape. Where does the keyboard focus go?

                Choose an answer to see the explanation.

              6. Question 6 of 7The modal has a Go to your cart link, and @modal has only default.tsx. What happens on that click?

                Choose an answer to see the explanation.

              7. Question 7 of 7Why does the server answer /teas/green with Vary that names next-url?

                Choose an answer to see the explanation.

              Key takeaways

              • A folder starting with (.), (..), (..)(..) or (...) intercepts another route, counted in route segments. Slots and route groups don't count.
              • Only a client-side navigation (a Link click or router.push) from the folder's route, or below it, is intercepted. The router says where the visitor is with next-url.
              • A reload, a typed address or a shared link is a full page load, and shows the real page.
              • A modal is a @modal slot, an intercepting route in it, a default.tsx that returns null, and the full page. Next.js 16.4 stops the build without the last two.
              • Close the modal with router.back(), give the slot a catch-all page that returns null, and link the full page to the list with a Link.
              • Use <dialog> with showModal() and aria-labelledby: focus moves in, Escape closes it, and focus comes back.

              Remember the one-liner.
              A client-side navigation from the folder's route gets the modal; every other way in gets the full page.

              Coming next: proxy.ts. It runs code before a request reaches any route: rewrites, redirects, headers and cookies.

              CompleteFrontend Clear concepts. Working examples.