ProNextJS
    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

    Parallel routes are potentially my favorite feature of the App Writer that nobody is talking about. I don't know why they are so amazing. Let's go have a look at our detail page. So one thing is missing from our detail page is a list of the chats that we already have.

    Let's go and add another column over here that has the previous chats on it. So we can navigate around. We have a sidebar. Wouldn't that be nice? There's different ways that we could do that. We can certainly just go and add to the slash chats slash ID route an additional sidebar. Yeah. Okay. You can do that.

    But I want to experiment with this parallel routes thing because I think it's really interesting. So what it allows you to do is actually have multiple routing systems running simultaneously. And so the layout at the top level takes these different

    route handlers and actually combines them into the layout at the last moment, but they all run in parallel. So we're going to have this chat menu over here as a parallel route, and it's going to go off and make its own request while the main route is actually making its request for the chat data.

    So let's go and build out our parallel route. So the way parallel routes work from the file system level is you've got a folder up here and you got at the beginning of the folder name, and then you got the name of the parallel route. So in this case, it's chats. And then within that, you have pages that correspond to the pages in the primary routing

    system. So we've got the top level page. It's going to be page.tsx. And for that page, we don't want anything. We just want a blank div because we don't want that extra column on the homepage. Same kind of thing for about, we don't really want the list of chats on the about page either. So put a blank page there too,

    but we do want that sidebar for the chat ID route. So I'm going to go and create another file, chats, chat ID, page.tsx. And in here, I'm going to create a component called chat menu column. And it's just going to have the word chat menu in it.

    And we'll just see, can we get the chat menu in the right place? So now how do we actually use this thing? So now over in the layout, we're going to roll up all of these routes. So go to the layout and now in our route layout, we're going to get another prop called chats.

    Chat corresponds to @chats up here. So that's the parallel route react nodes. Bring that in as chats and we'll drop that here. If it's available, let's hit save and we'll try it again.

    Let's go back to our local host, hit refresh. Okay. Nothing going on there, but if I hit link, we can see, chat menu just right up there. So it's going and getting the children, which is the primary content here for the route.

    And then it's going and also getting that parallel route of the chat menu and putting it over here, but it's going to do it in parallel. So if we have requests to the database for each one of those, it's going to manage that in parallel. That's really cool. So let's go and implement our chat menu.

    So go back over into our visual studio code, create a new component, we'll call it chat menu. The code for that is in the instructions, but I'll walk you through it. We're going to get our chat menu react server component. It's going to be asynchronous because it's going to need to make the call to get the server session to know who you are,

    as well as the database call to get the chats. All that's going to happen on the server, thus react server component. Then we're just going to format that all as a set of links that go to the chat detail page for the given chats. So now let's go bring that into our parallel route. Instead of this chat menu placeholder,

    we'll bring in chat menu as well as get server session. And then we'll only render the chat menu if we actually are authenticated. Now the middleware should handle all that for us, but this is just a little bit of extra protection. Let's hit save. Of course we need to make this an async function, handle the await. Okay.

    Looking good. And there you go. There are chat sessions. How cool is that? We have a whole nav system on the side, but now let's actually try and again, show off that parallelism.

    Again, we'll put in a set timeout and now we'll go over here and we'll hit refresh. And now, well, we are blocking for three seconds. So there's no implicit suspense around this. There's no implicit streaming,

    but let me show you something cool. So inside of any one of these route directories, there are several extra files we haven't covered yet. One of those files is loading.tsx. I'm going to create that file. And I'm going to export out of that, a loading component.

    It's going to be a regular React component that says loading. So now let's give it another try and we can see, we got loading. That's really cool. So what's happening is that Next.js is detecting that component is blocking. So it's going to use that optional loading to put in some UI

    while that component is loading. So this essentially there is an implicit suspense already there. And all you need to do is if you want to use that loading. So you've got suspense. If you want fine grained suspense boundaries and loading parts and pieces,

    or you can do everything at the route level by just adding a loading.tsx. And if that route takes a little bit of extra time, it'll put up that loading UI and just handle it all for you. Of course, we're not slow. So let's go and remove our await.

    Hit refresh again, and it just shows up immediately. Okay. Let's go put this up into production. All right, let's go check out our deployment. And let's have a look and see if it worked.

    And there you go. Now we're on detail route and we get our chat sessions sidebar using that parallel route. So easy. In the next session, we're going to cover how to do a streaming server action. Again,

    app router only functionality, which is awesome. I'll see you over there.