File based routing with tanstack router.

Published on: Jan 28, 2024 | 6 min read |

In this blog post, we'll implement file based routing with Tanstack Router.

In my earlier blog post (you can read it here), we learned about the basics of Tanstack Router and created a simple app with code based routing approach, but we can also use file system based routing with Tanstack Router and we'll look at how to implement the same in this post.

Get Started.

To implement file system based routing we'll need to install Tanstack Router CLI or the Vite plugin (in case of Vite) to generate our route tree from the file structure. I'll use the Vite plugin for our example. Let's install it first.

yarn add --dev @tanstack/router-vite-plugin

Once installed, we need to add it to our plugins array in Vite config file as such

import { TanStackRouterVite } from '@tanstack/router-vite-plugin';

export default defineConfig({
  plugins: [TanStackRouterVite(),],
});

File Name Conventions

Before we can start creating our routes, there are certain file naming conventions we need to follow to correctly generate our route tree. These are as follows.

  • __root.tsx - __root.tsx is the root route file and it should be placed in our routes directly.
  • . separator - . separated can be use to create nested routes. For eg: profile.settings.tsx, /settings will be the nested child of /profle route.
  • $ - $ separator is used to create a dynamic route. For eg: users.$userId.tsx will have userId as route param.
  • _ prefix - _ prefix can be used to create layout routes.
  • _ suffix - Route path having _ suffix cannot be nested in any other parent route.
  • .index - Route paths which have .index in file name will match the parent route of the url pathname.
  • .component.tsx - We can define the component to use for our route path in this file.
  • .loader.tsx - We can define our loaders in this file.

Addition to files, we can also use directories for organizing our routes, just by creating a directory of our route path and then creating .route.tsx file in it.

Creating Routes

For the sake of this blog post, we will create a simple app and our route tree will look like this.

  • Root route.
  • / - Home route.
  • /about - About route.
    • / - Profile route.
    • /posts - My posts route.
  • /posts - All posts route.
    • / - All posts index route.
    • /$postId - Single post dynamic route.

First let's create a routes directory and add a __root.tsx file in it with the following snippet.

// __root.tsx

import { createLazyFileRoute } from "@tanstack/react-router";

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

const links = [
  { path: "/", name: "Home" },
  { path: "/about", name: "About Me" },
  { path: "/posts", name: "Posts" },
];

export default function Root() {
  return (
    <div>
      <header>
        <nav>
          <ul>
            {links.map((link, idx) => (
              <li key={idx}>
                <Link
                  to={link.path}
                  activeOptions={{ exact: true }}
                  activeProps={{ style: { fontWeight: "bold" } }}
                >
                  {link.name}
                </Link>
              </li>
            ))}
          </ul>
        </nav>
      </header>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

We render an Outlet in our root file. Next we'll create the rest of our routes as well.

// index.tsx

import { createLazyFileRoute } from "@tanstack/react-router";

export const Route = createLazyFileRoute("/")({
  component: Index,
});

function Index() {
  return (
    <div className="p-2">
      <h3>Welcome Home!</h3>
    </div>
  );
}
// about.tsx

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

export const Route = createLazyFileRoute("/about")({
  component: About,
});

function About() {
  return (
    <div className="p-2">
      Hello from About!
    </div>
  );
}

Now if you save all the files, the Tanstack Router Vite plugin will create the routes for us and generate a route tree in routeTree.gen.ts file. We can pass this route tree to the router provider to finish our setup.

// app.tsx

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

import { routeTree } from "./routeTree.gen";

const router = createRouter({ routeTree });

export default function App() {
  return <RouterProvider router={router} />;
}

If you check our app now it will look something like this.

Home route

Screenshot 2024-01-27 215142

About route

Screenshot 2024-01-27 215156

Nested Routes

Next we'll look at how to create nested routes. We are going to create two routes / and /posts routes in about route. To achieve this we are going to create tabs and render an Outlet in about route first and then we'll create our routes.

const aboutLinks = [
  {
    name: "Profile",
    path: "/about/",
  },
  {
    name: "My Posts",
    path: "/about/posts",
  },
];

function About() {
  return (
    // rest of the component...
      <nav>
        <ul className="about-tabs">
          {aboutLinks.map((link, idx) => (
            <li key={idx}>
              <Link
                activeProps={{
                  style: {
                    fontWeight: "bold",
                    background: "blue",
                    color: "#fff",
                  },
                }}
                to={link.path}
              >
                {link.name}
              </Link>
            </li>
          ))}
        </ul>
      </nav>
      <Outlet />
    // rest of the component...
  );
}

We have created a tab navigation with two links to route paths /about/ and /post. As we want to render profile on /about/ route, we'll create an index route as about.index.tsx for this and a nested child for posts as about.posts.tsx.

// about.index.tsx

import { createFileRoute } from "@tanstack/react-router";

export const Route = createFileRoute("/about/")({
  component: AboutSummary,
});

function AboutSummary() {
  return <div>this is a profile route</div>;
}

// about.posts.tsx

import { Link, createFileRoute } from "@tanstack/react-router";
import { posts } from "../data";

export const Route = createFileRoute("/about/posts")({
  component: MyPosts,
});

function MyPosts() {
  return (
    <div>
      <span>This are my posts</span>
      <ul>
        {posts.map((post, idx) => (
          <li key={idx}>
            <Link to={"/posts/$postId"} params={{ postId: post.id }}>
              <h6>{post.title}</h6>
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}

Now if navigate to /about/ path our profile component will be rendered as shown below.

Screenshot 2024-01-27 235952

Similarly if we navigate to /about/posts path, our posts component is rendered as shown below.

Screenshot 2024-01-28 000012

So far we looked at how we can create routes and child routes using file-based approach with Tanstack Router, next we'll create dynamic routes using file based approach.

Dynamic Routes

Next we'll create /posts and /posts/$postId routes to complete our route tree. So we'll be creating following three files as

  • posts.tsx - Route file for /posts/
  • posts.index.tsx - Index route for /posts/ path.
  • posts.$postId.tsx - We can use $ token to define a dynamic segment in our route path.
// posts.tsx

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

export const Route = createFileRoute("/posts")({
  component: Posts,
});

function Posts() {
  return <Outlet />;
}
// posts.index.tsx

import { Link, createFileRoute } from "@tanstack/react-router";
import { posts } from "../data";

export const Route = createFileRoute("/posts/")({
  component: PostsIndex,
});

function PostsIndex() {
  return (
    <div>
      <span>This are some posts</span>
      <ul>
        {posts.map((post, idx) => (
          <li key={idx}>
            <Link to={"/posts/$postId"} params={{ postId: post.id }}>
              <h6>{post.title}</h6>
            </Link>
          </li>
        ))}
      </ul>
    </div>
  );
}
// posts.$postId.tsx

import { createFileRoute } from "@tanstack/react-router";
import { posts } from "../data";

export const Route = createFileRoute("/posts/$postId")({
  component: SinglePost,
});

function SinglePost() {
  const { postId } = Route.useParams();

  const post = posts.find((post) => post.id === +postId);

  return (
    <div>
      <h1>{post?.title}</h1>
      <p>{post?.body}</p>
    </div>
  );

Now if we navigate to /posts/ path, our posts are rendered correctly.

Screenshot 2024-01-28 121756

We can further click on any post to view it as well.

Screenshot 2024-01-28 121840

So that's how we can setup our routing with file based approach using Tanstack Router.

A word on Tanstack Router CLI

If you are not using Vite, you can still generate the route tree using the Tanstack Router CLI. To start using, we can install it as follows

yarn add @tanstack/router-cli

Once installed, we can use the following commands to generate our route tree.

generate

Generate command will create our route tree based on the configuration. Tanstack Router CLI comes with a default configuration which we'll look at later.

tsr generate

watch

The watch command will watch files in the routes directory for any change and if found will regenerate the route tree automatically.

tsr watch

You can learn more about the Tanstack Router CLI and the configuration from the official docs.

Conclusion

All in all Tanstack Router provides a elegant way to setup file based routing in a single-page application and I think it's time, it becomes a default choice for single-page applications in React.

You can the find the source code for this blog on github and codesandbox .

Related Tags
reacttanstack router
Loading comments...