Route groups, parallel and intercepting routes

Next.js · Advanced · 14 min

Dashboards, modals and multiple root layouts need routing patterns beyond simple folders.

Route groups: a folder in parentheses, like (marketing), organises routes without affecting the URL. app/(marketing)/about/page.tsx is /about. Groups let different sections have different layouts, and even multiple root layouts (each group with its own layout.tsx containing <html>).

Private folders: _lib or _components are excluded from routing entirely.

Parallel routes render several pages in the same layout simultaneously. Named slots are folders starting with @: app/dashboard/@team/page.tsx and @analytics/page.tsx are passed to app/dashboard/layout.tsx as team and analytics props next to children. Each slot has its own loading and error states and can be shown conditionally (e.g. based on the user's role). Add default.tsx to a slot as the fallback when the slot has no match for the current URL after a full page load; otherwise you get a 404.

Intercepting routes load a route inside the current layout during client navigation, while a direct visit shows the full page. The convention mirrors relative paths: (.)photo intercepts a sibling segment, (..) one level up, (...) from the root. Combined with a parallel @modal slot, this creates a URL-backed modal: clicking a photo opens a modal with a shareable URL; refreshing shows the photo page; back closes it.

`template.tsx` is like a layout but remounts on every navigation, for enter animations or per-page effects.

Example

// app/
// ├── (marketing)/about/page.tsx          -> /about   (group not in URL)
// ├── layout.tsx                          receives children + modal slot
// ├── @modal/default.tsx                  -> return null when no modal
// ├── @modal/(.)photos/[id]/page.tsx      intercepts /photos/[id] on client navigation
// └── photos/[id]/page.tsx                full page on direct visit or refresh

// app/layout.tsx
export default function RootLayout({ children, modal }: LayoutProps<'/'>) {
  return (
    <html lang="en">
      <body>{children}{modal}</body>             {/* modal = the @modal parallel slot */}
    </html>
  )
}

// app/@modal/(.)photos/[id]/page.tsx
export default async function PhotoModal({ params }: PageProps<'/photos/[id]'>) {
  const { id } = await params
  return <dialog open>Photo {id} (press back to close)</dialog>
}

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

Remember

  • (group) folders organise routes and layouts without changing URLs
  • @slot folders are parallel routes passed to the layout as props; add default.tsx
  • (.), (..), (...) intercept routes for URL-backed modals

Watch out for

  • Forgetting default.tsx in a parallel slot, so a hard refresh of another URL renders a 404.

Check yourself

  1. With @modal/(.)photos/[id] and photos/[id], what does a user see when opening /photos/7 directly in a new tab?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: nextjs.org/docs/app/api-reference/file-conventions/parallel-routes

Builds on

Unlocks

Nothing builds on this yet.