ProNextJS
    Loading
    lesson

    Implement Parallel Routes for a Chat Menu Sidebar

    Jack HerringtonJack Herrington

    Parallel Routes are one of the coolest features of the Next.js App Router. They allow you to have multiple routing systems running simultaneously, but then combines them into the layout at the last moment.

    Let's put them into use by adding a chat menu sidebar to our chat detail page. This sidebar will display a list of previous chats and will load in parallel with the main chat content.

    Setting up the Parallel Route

    Parallel routes work on the file system level by creating a folder with the @ symbol followed by the name of the parallel route. In our case, we'll create a folder named @chats.

    Inside of the @chats folder, we'll create pages that correspond to the pages in the primary routing system:

    First up is page.tsx. This will be an empty page for the top-level route, as we don't want the extra chat menu column on the homepage.

    // inside src/app/@chats/page.tsx
    
    export default function BlankPage() {
      return <div />;
    }
    

    Next we'll create about/page.tsx. Similarly, we'll leave this page empty since we don't need the chat menu on the about page.

    Then we'll create the route for the chat menu sidebar at @chats/[chatId]/page.tsx. This is where we'll implement the chat menu sidebar for the chat detail page.

    Inside the @chats/[chatId]/page.tsx file, create a placeholder component called ChatMenuColumn:

    // inside of src/app/@chats/chats/[chatId]/page.tsx
    
    export default async function ChatMenuColumn() {
      return <div>Chat Menu</div>;
    }
    

    With the routes created, we can roll them together in the layout.

    Updating the Layout for Parallel Routes

    Inside of the layout.tsx file, we will first update the RootLayout component to include the chats prop, which corresponds to the @chats route:

    // inside of src/app/layout.tsx
    
    export default function RootLayout({
      children,
      chats,
    }: Readonly<{
      children: React.ReactNode;
      chats: React.ReactNode;
    }>) {
      return (
        <SessionProvider>
          ...
    

    Then we'll render chats above the children:

    //inside the return statement of RootLayout
    
    <div className="flex">
      {chats}
       <div className="flex-1">{children}</div>
    </div>
    

    Now when refreshing the browser and navigating to a different page than the homepage, you should see the "Chat Menu" text displayed above the children content of the route:

    Chat Menu appears above the children of the route

    What's happening is when we visit the page, Next is getting the children, which is the primary content for the route, and then it's also getting the parallel route of the chat menu and putting them together in parallel.

    Implementing the Chat Menu

    Back in VS Code, create a new component called ChatMenu at src/components/ChatMenu.tsx. This component will be an async React Server Component that will make calls to the server to get the user session and fetch the chats from the database, which will then be formatted as a set of links that go to the chat detail page for each chat.

    Here's what the markup looks like all together:

    import { getServerSession } from "next-auth";
    import Link from "next/link";
    
    import { Separator } from "@/components/ui/separator";
    
    import { getChats } from "@/db";
    
    export default async function ChatMenu() {
      const session = await getServerSession();
      const chats = await getChats(session?.user?.email!);
    
      return (
        <>
          <div className="text-2xl font-bold">Chat Sessions</div>
          <Separator className="my-3" />
          <div className="flex flex-col gap-2">
            {chats.map((chat) => (
              <div key={chat.id}>
                <Link href={`/chats/${chat.id}`} className="text-lg line-clamp-1">
                  {chat.name.trim().slice(0, 20)}
                  {chat.name.length >= 20 ? "..." : ""}
                </Link>
              </div>
            ))}
          </div>
        </>
      );
    }
    

    With the ChatMenu component created, we need to update the @chats parallel route to use it.

    Inside of @chats/[chatId]/page.tsx, replace the ChatMenuColumn placeholder with the imported ChatMenu component. We'll also surround it in a conditional check to see if the user is authenticated by using getServerSession:

    // inside of src/app/@chats/chats/[chatId]/page.tsx
    
    import { getServerSession } from "next-auth";
    
    import ChatMenu from "@/app/components/ChatMenu";
    
    export default async function ChatMenuColumn() {
      const session = await getServerSession();
      const authenticated = !!session?.user?.email;
    
      return authenticated ? (
        <div className="md:w-1/3 md:min-w-1/3 pr-5 w-full text-nowrap">
          <ChatMenu />
        </div>
      ) : null;
    }
    

    After saving, when you navigate to the chat detail page, you should see the list of chats displayed in the sidebar:

    the sidebar shows chat sessions

    Handling Loading States

    In order to demonstrate the parallelism of the Parallel Routes, let's simulate a delay in the ChatMenu component by adding a setTimeout:

    // inside of src/app/@chats/chats/[chatId]/page.tsx
    
    export default async function ChatMenuColumn() {
      await new Promise((resolve) => setTimeout(resolve, 3000));
      // ...
    }
    

    Now when you refresh the page, you'll notice that the entire page is blocked for 3 seconds. There's no implicit Suspense or streaming behavior, but we can handle this.

    Inside of @chats/chats/[chatId], create a loading.tsx file and export a Loading component that displays a message:

    // inside of src/app/@chats/chats/[chatId]/loading.tsx
    export default function Loading() {
      return <div>Loading</div>;
    }
    

    Now, when you refresh the page, you'll see the "Loading..." message displayed in the sidebar while the ChatMenu component is loading.

    The loading message in the sidebar

    Next.js automatically detects that the component is blocking and uses the optional loading.tsx file to display a loading UI!

    This is an example of implicit suspense behavior at the route level. If you want more fine-grained control over suspense boundaries and loading states, you can use the Suspense component as we did in the previous lesson.

    We can now remove the setTimeout from the ChatMenu component to restore the normal loading behavior, and push the changes to the repository.

    Wrapping Up & Next Steps

    Parallel Routes in Next.js App Router provide a powerful way to load multiple components simultaneously, improving the performance and user experience of your application. By using Parallel Routes, we were able to add a chat menu sidebar that loads in parallel with the main chat content, without blocking the entire page.

    In the next lesson, we'll cover how to implement a streaming server action, another exciting feature exclusive to the App Router.

    Transcript