Parallel and Intercepted Routes in Next.js

Stephen / October 8, 2025 in 

Next.js is a React framework that allows developers to better organize code and files in order to achieve single-page app like behavior, which still being able to access a number of server-only features. One of its features is its file-based routing, which allows a developer to associate file locations with its URL.

One day, when I was looking for other information on the file system conventions, I ran across two features that initially didn’t make sense to me, Parallel Routes and Intercepted Routes. While the documentation did a decent job of explaining technically how it worked, I found the examples too simplistic and had a tendency to use both, leaving me with two thoughts: “Why would you ever use this?” and “Why would you ever use one without the other?”

What started as a text message explaining to a friend how they work ballooned into several pages worth of documentation that could be helpful to more people. I think these features have some key uses that aren’t well understood, despite these features being the best solution to certain problems.

This is my attempt to explain what they are, how they work, and most importantly, why you’d use either.

Term Definitions

Bear with me; I’m erring on over-defining here because the language will get confusing due to overloaded terms like “route.”

  • directory: The file structure that exists in the code. The directory for /app/blog/(admin)/dashboard/page.tsx refers to this path and structure.
    • folder or file: One segment of a directory. The last segments of /app/blog/(admin)/dashboard/page.tsx are the folder dashboard and file page.tsx
  • route: How Next will interpret the directory structure to translate it to the URL. The route for /app/blog/(admin)/dashboard/page.tsx refers instead to the url: /blog/dashboard.
    • segment: One segment of a route. The route above, /blog/dashboard, has 3 segments: /, blog, and dashboard.
  • component: This refers to React components only.
  • route params: The dynamic segments of a route. A directory of /app/blog/post/[slug] would have a dynamic segment of [slug]. A route of /blog/post/new-year-new-me would have a route param of slug set to the value new-year-new-me. These are accessible in the layout and page components via the params prop.
  • search params: In a URL, this is the query string after the ?. These are not accessible in the layout component. In the page component, these are accessible through the searchParams prop.
  • soft navigation: Route updates happening within Next’s control, such as using Next’s <Link> component or updating via router or redirects.
    • Unless otherwise specified, “Navigation” refers to “Soft Navigation” in this document.
  • hard navigation: Any other type of route update that is not within Next’s control. This includes typing in a URL, opening a <Link> in a new tab, using window.location, or using an <a> tag instead of <Link>.
  • slot: This is a term used with Parallel Routes, referring to the content that gets rendered as part of the Parallel Route. These are created when the directory contains a folder whose name starts with an @ (e.g. @admin).

Parallel Routes

Parallel Routes are used to separate out functionality in the layout at the segment the parallel routes are being used. This can allow you to render pages, loading states, and error boundaries independent of the current route’s pages. Multiple parallel routes can exist in the same route, and the route under them can have any structure necessary, whether or not it matches the current route.

How are Parallel Routes used?

Parallel Route folders in the directory must be prefixed with an @; this will form what Next refers to as a “slot.” These can have any name, which will not affect the route (similar to route group folder names like (loggedIn)), however it will affect the props passed to the layout component.

When using parallel routes, the layout component will get additional props for each parallel route slot. Consider the following directory structure:

/app
  /@admin
    page.tsx  # page for the `admin` slot
  /@metrics
    page.tsx  # page for the `metrics` slot
  page.tsx  # normal page for the `/` route
  layout.tsx  # normal root layout

With this setup, the props passed the the component in layout.tsx will be the usual props, but also have an additional prop for each slot that has the type ReactNode:

export default function Layout({
  admin,
  metrics,
  children,
}: {
  admin: React.ReactNode;
  metrics: React.ReactNode;
  children: React.ReactNode;
}) {
  const canSeeAdmin = isAdmin();
  const canSeeMetrics = isAnalytics();
  return (
    <div>
      {children}
      {canSeeAdmin && admin}
      {canSeeMetrics && metrics}
    </div>
  )
}

As you can see from the example above, this will let you do things like conditionally render chunks of a page, which only gets more powerful with server components, allowing you to separate out logic that might otherwise get buried deep into a child component.

Navigation and default.tsx

Hard navigating to a route that does not exist for a parallel slot will cause it to error, unless you have a default.tsx file. This will handle any initial hard navigation to a route that does not exist for the slot, as well as any soft navigation before the slot has been included in the layout.

Soft navigation, once the slot has been added to the layout, then treats the slot as “sticky.” If the slot does not have a matching route after navigation, it will retain whatever state it was on before. Consider the following structure:

/app
  /dashboard
    /@admin
      /posts
        page.tsx  # posts page for the `admin` slot
      page.tsx  # normal page for the `admin` slot
      default.tsx  # default page for the `admin` slot
    /posts
      page.tsx  # posts page
    /users
      page.tsx  # users page
    page.tsx  # normal page for the `/dashboard` route
    layout.tsx  # `/dashboard` route layout
  page.tsx  # normal page for the `/` route

Assuming the admin route is always included in the layout, the way this behaves is the following:

  • Hard Navigation from anywhere or Soft Navigation from / to:
    • /: admin slot does not render because it’s part of the dashboard route
    • /dashboard: admin and dashboard render the normal page
    • /dashboard/posts: admin and dashboard render the posts page
    • /dashboard/users: dashboard renders the users page, but admin renders the default page instead because it does not have a /users segment
  • Soft Navigation from /dashboard to:
    • /dashboard/posts: admin and dashboard render the posts page
    • /dashboard/users: dashboard renders the users page, but admin renders the normal page, not the default page, instead
  • Soft Navigation from /dashboard/posts to:
    • /dashboard: admin and dashboard render the normal page
    • /dashboard/users: dashboard renders the users page, but admin continues to render the posts page instead, because it was last on the posts page previously
  • For completeness, and regardless of the currently displayed page for this slot, Soft Navigation from /dashboard/users to:
    • /dashboard: admin and dashboard render the normal page
    • /dashboard/posts: admin and dashboard render the posts page

The main takeaway is that the slots behave according to the following rules:

  • If the slot has not rendered (Hard Navigation or Soft Navigation from another route without the slot):
    • If the slot has a page file at the matching route: Render that page
    • Else If the slot has a default.tsx: Render the default component
    • Else: Throw an error
  • Else (the slot has rendered previously):
    • If the slot has a page file at the matching route: Render that page
    • Else: Continue to render the page it was previously rendering.

Other Gotchas

  • No search params: Because this happens in the layout, you don’t have access to search params. You do have access to route params though, and you’ll have access to search params in the actual page component like a normal page component.
  • No Static and Dynamic rendering mixing: When using parallel route slots, if one slot is dynamic, all other slots at the same segment become dynamic. However, this only matters if you are doing Static Rendering. Otherwise, this is a non-issue.

Okay, why?

You might be thinking “Yeah, sure, this seems neat, but when would I need this? Can’t I just use a component like normal?”

The answer to that is “Probably, yes.”

The problem that these solve quite nicely is when you’d need to swap in different components based on the route you’re looking at. If you just need to show an admin panel that always has the same controls, you probably don’t need a parallel route. But if you need different controls to manage users than you’d need to manage posts, and you don’t want to overwhelm the admin user with too many options at once, then parallel routes make a lot of sense. Imagine having 10 sections on the panel but only one was relevant at any given time.

Reddit user /u/cbrantley had an additional use case for feature walkthroughs:

When users land on certain routes that have new features we show walkthroughs of those features that users can dismiss. Parallel routes let us selectively load those depending on the route and it doesn’t clutter up the actual UI components.

If you only need simple conditional checks and data that works independent of route, you don’t need parallel routes. If you want to lock off entire sections conditionally, or if you have a subsection that needs to change based on the route, then you’d want to try parallel routes.

Intercepted Routes

Intercepted Routes are used to intercept movement from one route to another, changing the displayed page content without changing the URL. Because this only works moving from one route to another, hard navigating won’t show the intercepted route.

How are Intercepted Routes used?

Intercepted routes start with a relative route segment marker:

  • (.) Intercepts when moving within the same route segment
  • (..) Intercepts when moving to a route one segment above (you can use this multiple times, e.g. (..)(..)(..) would intercept going to a route 3 segments above)
  • (...) Intercepts when moving to a route going to the root-segment (i.e. starting from the /app folder)

Route names must match. If you want to intercept navigation from /blog to /blog/posts, you’d need an additional folder in /blog labeled /(.)posts. This would let you go to a different page depending on whether you went directly there or navigated from /blog. To better illustrate the directory structure:

/app
  /blog
    /(.)posts
      page.tsx  # Intercepted Posts page
    /posts
      page.tsx  # Normal blog/posts page
    page.tsx  # Blog page
  page.tsx  # root page

Given the above, to recap

  • Hard Navigating directly to /blog/posts via URL goes to the Normal Page.
  • Soft Navigating from / to /blog/posts goes to the Normal Page. This is because Next will not try to intercept the route from any segments above.
  • Soft Navigating from /blog to /blog/posts goes to the Intercepted Page.
  • Opening a link in a new tab on /blog that goes to /blog/posts goes to the Normal Page (as this is another form of Hard Navigation).

Okay, why?

Consider something like a post editor. Navigating to a post from your home dashboard, you could go to an intercepted route with an edit view, but sharing the link would take people directly to the read-only post. It could also be used for less dramatic changes (e.g. having a “back to blog” if you came from blog), which you could do by importing a common PostPage component and giving it different props depending if you’re on the normal or intercepted route.

Parallel + Intercepted Routes

Next’s only example of intercepting routes also uses parllel routes, and this also happens to be a good use case.

Imagine that you are building a photo gallery. You have a feed of images the user can click on, but you want the experience to be seamless and snappy, without a lot of page-changes. You can set up a parallel route with intercepting route as well to handle this. The relevant parts of their file structure looks like this:

/feed
  /@modal
    /(..)photo
      /[id]
        page.tsx  # Intercepted page
    default.tsx  # In this case, returns null to avoid errors.
  layout.tsx  # `/feed` layout
  page.tsx  # `/feed` page
/photo
  /[id]
    page.tsx `/photo/[id]` page

If the user is on the /feed page, clicking on a photo with an id of 1 will change the route to /photo/1, but Next won’t actually get the component from the /photo/[id] page, it will instead pull from the intercepted page’s component, which will be added into the layout. The intercepted page can be just a modal with a state set to open, and closing it can just run router.back() to take the user back to /feed, closing the modal in the process.

Wait, doesn’t the Parallel route persist, given the rules above?

One would assume so, but calling router.back() works just like pressing the back button, so it actually goes back one step. Soft navigating from the modal back to /feed would not restore the default-closed state thus you’d need to add a page.tsx file to the /@modal folder if you want this functionality (which probably just returns null).

Summary

Here’s the tl;dr:

Parallel Routes let you render sections of a page in an ecapsulated way where the content is dependent on the specific route segment the user is on by leveraging Next’s built-in app router instead of needing to manually check the route. It’s best used when you have route-dependent context that may need to change depending on the route.

Intercepted Routes let you render different content depending on where and how the user navigates. They’re best used when you would want to display significantly different pages if the user moves to the route in a particular way, while still having a discrete page the user can bookmark or share and return to later.

Both Together let you use the intercepting capabilities while having more flexibility in how the content is displayed, letting you change only portions of the content depending on how the user navigates without needing to change the entire page or include a lot of conditional route-checking.

Wow, cool, I’m going to rewrite our codebase!

Whoa, slow down, hypothetical over-eager reader. These features need to be used with proper consideration, moreso than the normal features of Next. If you just apply these without purpose, you run the risk of bloating your directory with over-engineered solutions to potentially straightforward problems. Instead, hold this in the back of your mind until you get that one really thorny ticket that needs a ton of routing-based conditional rendering, and then impress your team with how much you know about Next.


Career Opportunities
Are you looking to join a software company that invests in its teammates and promotes a strong engineering culture? Check out our current Career Opportunities. We’re always looking for like-minded engineers to join the BTI360 team.

Previous

BTI360 Announces Advisory Board

Next

Event: From Public Service to Private Sector

Close Form

Enjoy our Blog?

Then stay up-to-date with our latest posts delivered right to your inbox.

  • This field is for validation purposes and should be left unchanged.

Or catch us on social media

Stay in Touch

Whether we’re honing our craft, hanging out with our team, or volunteering in the community, we invite you to keep tabs on us.

  • This field is for validation purposes and should be left unchanged.