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

حذيفة @xb16

About: i am intersted about Full-Stack Web Developement and ecpecially React & Laravel.

Location:
Morocco, Laayoune
Joined:
Jun 15, 2024

Custom Layout for Specific Route Group in Tanstack Router - Solution

Publish Date: Aug 17 '25
1 7

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.

Comments 7 total

  • Jang Haemin
    Jang HaeminAug 20, 2025

    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.

    • حذيفة
      حذيفةAug 20, 2025

      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.

  • Jang Haemin
    Jang HaeminAug 20, 2025

    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.

    • حذيفة
      حذيفةAug 20, 2025

      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.

      • Jang Haemin
        Jang HaeminAug 21, 2025

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

    • Saleh Motiwala
      Saleh MotiwalaDec 1, 2025

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

  • حذيفة
    حذيفةAug 20, 2025

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

Add comment