DEV Community

Cover image for Custom Layout for Specific Route Group in Tanstack Router - Solution
حذيفة
حذيفة

Posted on

Custom Layout for Specific Route Group in Tanstack Router - Solution

if you read this issue discussion on GitHub :
Custom Layout for Specific Routes in tanstack/router #1102

you’ll see that this is a big issue. Surprisingly, such a basic feature is not available in a large project like TanStack Router. Even though the issue is marked as resolved, that’s not really the case. I tried all the suggested solutions, but none of them worked for me. I also went through the documentation and found that there’s still no official support for this. However, I managed to solve it with a workaround.

Image 1 Image 2 Image 3

The Scenario :

I am working on a [react + vite + Tanstack Router] Project for a clothes store,
I have two type of pages :

  • regular pages : Home Page, Product Page, Collection Page, Search Page, Checkout Page.
  • Dashboard Pages : Statistics Page, CRUD Product Page, Handling Orders Page,

The regular pages anyone can navigate them, and they have layout of <NavBar /> & <Footer />
The Dashboard pages for the admin, and they have layout of <SideBar />

The routing type implemented here is the File-Based Routing.

The Problem :

when I set a custom layout for the dashboard, it wrapped by the layout of the regular pages.

The /src/routes directory Hierarchy :

\routes
|   checkout.jsx
|   collection.jsx
|   index.jsx
|   product.jsx
|   search.jsx
|   __root.jsx
|
\---dashboard
    |   index.jsx
    |   route.jsx
    |   
    \---products
            index.jsx
Enter fullscreen mode Exit fullscreen mode

the src/routes/__root.jsx file :

import { createRootRoute, Outlet } from "@tanstack/react-router";
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
import NotFoundPage from "@/components/NotFoundPage";

export const Route = createRootRoute({
  notFoundComponent: () => <NotFoundPage />,

  component: () => {
    return (
      <div>
        <Navbar />
        <Outlet />
        <Footer />
      </div>
    );
  },
});
Enter fullscreen mode Exit fullscreen mode

the /src/routes/dashboard/route.jsx file :

import { Outlet, Link, createFileRoute } from "@tanstack/react-router";
import SideBar from "@/componenets/SideBar";

export const Route = createFileRoute("/dashboard")({
  component: () => {
    return (
      <div className="flex h-screen bg-gray-50">
        <SideBar />
        <main className="flex-1 p-6 overflow-y-auto">
          <Outlet />
        </main>
      </div>
    );
  },
});
Enter fullscreen mode Exit fullscreen mode

so when i browse /Dashboard/products
The Given Result :

<NavBar />
  <div className="flex h-screen bg-gray-50">
    <SideBar />
    <main className="flex-1 p-6 overflow-y-auto">
      <Outlet />
    </main>
  </div>
<Footer/>
Enter fullscreen mode Exit fullscreen mode

The Wanted Result :

<div className="flex h-screen bg-gray-50">
  <SideBar />
  <main className="flex-1 p-6 overflow-y-auto">
    <Outlet />
  </main>
</div>
Enter fullscreen mode Exit fullscreen mode

So How can you set a custom Layout for Dashboard Sub Pages.

The Solution :

The trick is to use conditional rendering: if the route starts with /dashboard/, it won’t be wrapped by any component.

the src/routes/__root.jsx file again, but with Conditional Rendering :

import React, { useState} from "react";
import { createRootRoute, Outlet, useLocation } from "@tanstack/react-router";
import Navbar from "../components/Navbar";
import Footer from "../components/Footer";
import NotFoundPage from "../components/NotFoundPage";

export const Route = createRootRoute({
  notFoundComponent: () => <NotFoundPage />,
  component: RootComponent,
});

function RootComponent() {
  const location = useLocation();
  const pathname = location.pathname;

  if (pathname.startsWith("/dashboard")) {
    return <DashboardRoute />;
  } else {
    return <RegularRoute />;
  }
}

function DashboardRoute() {
  return (
    <>
      <Outlet />
    </>
  );
}

function RegularRoute() {
  return (
    <Navbar  />
    <Outlet />
    <Footer />
  );
}

Enter fullscreen mode Exit fullscreen mode

I hope this is helpful to everyone.
leave a love & a comment so more people can reach it.

Top comments (7)

Collapse
 
jhaemin profile image
Jang Haemin •

Here is a complete solution that achieves multiple root routes while leveraging the efficient partial route rendering.

This solution uses pathless routes for both route groups.

src/routes/
      _dashboard/
        dashboard/
          index.tsx
          products.tsx
        route.tsx  <- Layout for dashboard pages
      _regular/
        index.tsx  <- It replaces the root index.tsx
        product.tsx
        route.tsx  <- Layout for regular pages
        search.tsx
Enter fullscreen mode Exit fullscreen mode

These are the keys.

  • Remove routes/index.tsx and create routes/_regular/index.tsx.
  • Move layout code from __root.tsx to _regular/route.tsx.

Note that routes/index.tsx and routes/_regular/index.tsx cannot exist at the same time.

Collapse
 
xb16 profile image
حذيفة •

I saw like this solution in the issue discussion before, it didn't work to me, or may be I didn't understand it well, it was confusing.
and about pathless layout, I read about it before in the official documentation and it didn't work also.

Anyway !

When I looked at your solution, I realized I had been overcomplicating things. I tried your approach in my project, and it worked well—at least until I ran into the following error:

Error: rootRouteNode must not be undefined. Make sure you've added your root route into the route-tree.
Make sure that you add a "__root.tsx" file to your routes directory.
Enter fullscreen mode Exit fullscreen mode

The issue was that __root.jsx is still a crucial file. It’s required by the @tanstack/router-plugin/vite plugin to generate the routeTree.gen.ts file, which is then imported and initialized in the app’s entry point (/src/main.jsx in my case).

Here’s my /src/main.jsx:
/src/main.jsx :

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./index.css";

import { RouterProvider, createRouter } from "@tanstack/react-router";

import { routeTree } from "./routeTree.gen"; // <-  HERE
const router = createRouter({ routeTree });

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <RouterProvider router={router} /> 
  </StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

So, your solution works perfectly, but it just needs this small additional file: /src/routes/__root.jsx

import { Outlet, createRootRoute } from "@tanstack/react-router";

export const Route = createRootRoute({
  component: RootComponent,
});

function RootComponent() {
  return <Outlet />;
}

Enter fullscreen mode Exit fullscreen mode

Thank you so much, I appreciate your help.

Collapse
 
jhaemin profile image
Jang Haemin •

Yeah I forgot the __root.tsx sorry. It is required.

Collapse
 
salehmotiwala profile image
Saleh Motiwala •

Thanks mate. Been stuck on this for hours. You're a legend.

Collapse
 
jhaemin profile image
Jang Haemin •

Thanks for sharing the idea.
However, the problem is, since the root component is using the hook useLocation(), whenever the route changes, the whole application is re-rendered.

Collapse
 
xb16 profile image
حذيفة •

Your solution follows the official guidelines and represents best practices. In contrast, my approach was more of a workaround. Your implementation is clean and maintainable, whereas mine turned into spaghetti code. Clearly, your approach is the better one.

Collapse
 
xb16 profile image
حذيفة •

@jhaemin
may be the cover image should be like this :