Course
Parallel Routes
Next.js Mastery: From Fundamentals to Full-Stack
Unlock the power of Next.js with this comprehensive course! Starting with the basics, you’ll learn essential skills such as routing, data fetching, and styling. Progress through practical projects, including building your own React Notes app, to gain hands-on experience. Dive into the source code to understand the inner workings and core principles of Next.js. Perfect for developers with a basic knowledge of React and Node.js, this course ensures you’ll be ready to create high-performance full-stack applications efficiently. Join us and master Next.js, backed by industry experts and a supportive learning community.
Parallel Routes
Parallel routes offer a powerful way to render multiple pages either simultaneously or conditionally within the same layout.
Conditional Rendering
For example, in an admin dashboard, you might need to display both the team and analytics pages simultaneously:
The usage of parallel routes involves naming folders with an
@
prefix, such as @team
and @analytics
in the above example.
These slots are passed as props to the shared parent layout. In the example,
app/layout.js
receives the @team
and @analytics
slots and renders them in parallel with children
:// app/layout.jsexport default function Layout({ children, team, analytics }) { return ( <> {children} {team} {analytics} </> )}
Note: From above image, you can also see that thechildren
prop is essentially an implicit slot, whereapp/page.js
is equivalent toapp/@children/page.js
.
Playground
Besides rendering them simultaneously, you can also conditionally render them:
In this example, the layout checks the user's role: if the role is admin, it shows the admin page; if not, it shows the user page. This approach keeps the code completely separate.
Independent Route Handling
Parallel routes allow you to define independent error handling and loading UI for each route:
Tab Groups
Note that when we refer to
team
and analytics
, we still call them "pages" because they use page.js
just like normal pages. Additionally, they can have sub-pages. For example, under @analytics
, we can add two sub-pages: /page-views
and /visitors
:
Parallel routes do not affect the URL, so
/@analytics/page-views/page.js
corresponds to /page-views
, and /@analytics/visitors/page.js
corresponds to /visitors
. You can navigate to these routes:// app/layout.tsximport type { Metadata } from 'next';import { Inter } from 'next/font/google';import { Header } from '@/components/header';import { SiteFooter } from '@/components/site-footer';import '@/styles/globals.css';
const inter = Inter({ subsets: ['latin'] });
export const metadata: Metadata = { title: 'Create Next App', description: 'Generated by create next app',};
export default function RootLayout({ children, analytics,}: { children: React.ReactNode; analytics: React.ReactNode;}) { return ( <html lang="en"> <body className={inter.className}> <div className="flex min-h-screen flex-col"> <Header /> <main className="flex-1"> {children} {analytics} </main> <SiteFooter /> </div> </body> </html> );}
When navigating to these sub-pages, the content of the sub-page replaces
/@analytics/page.js
and is injected into the layout as props, as shown below (Try clicking Views and Visitors and see the result):This means each slot can have its independent navigation and state management, like a mini-application. This feature is useful for building complex applications such as dashboards.
Summary
- Splitting a single layout into multiple slots makes code easier to manage, especially for team collaboration.
- Each slot can define its loading UI and error state. For instance, if a slot loads slowly, you can add a loading effect without affecting the rendering and interaction of other slots. If an error occurs, only that specific slot shows an error message, improving user experience.
- Each slot can have independent navigation and state management, allowing rich functionality. For example, under the
@analytics
slot, you can create sub-pages like/page-views
and/visitors
, allowing the same slot area to display different content based on the route.
You might ask, "Can I just split components and handle loading and error states myself? Can I handle sub-routes on my own?"
Of course, you can do that if you don't mind the extra complexity.
Note: When using parallel routes, hot reloading might occasionally cause errors. If you encounter any inexplicable issues, try restarting npm run dev
or building the production version to check the effect.
default.js
Now, you may have noticed that when clicking the
Visitors
link to navigate to the /visitors
route and refresh the page. You will see a 404 error:Why is this happening? Why can we navigate from the home page to
/visitors
correctly, but directly accessing /visitors
results in a 404 error?
When navigating from the home page to
/visitors
, Next.js tracks the state of each slot. The content displayed in the specific slot depends on the type of navigation:- For soft navigation (e.g., using
<Link />
), Next.js performs partial rendering, changing the slot content to match the current URL, while maintaining the previous state for unmatched slots. - For hard navigation (e.g., refreshing the browser), Next.js cannot determine the state of unmatched slots and renders a 404 error.
In simple terms, accessing
/visitors
causes a mismatch between slot content and the current URL, which should render a 404 error. However, for better user experience, Next.js maintains the previous state of unmatched slots during soft navigation and avoids rendering a 404 error. In other words, when accessing /visitors
, it reads not only app/@analytics/visitors/page.js
but also app/visitors/page.js
.
When a hard navigation occurs, Next.js renders the content defined in default.js for unmatched slots. If
default.js
is not defined, it renders a 404 error.Create
app/default.js
:export default function Page() { return ( <div className="container flex max-w-[64rem] flex-col items-center gap-4 text-center px-14"> <h1 className="text-2xl text-red-500">Hello, App default!</h1> </div> );}
Now the effect is as follows: