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 (6)

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
 
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 :