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 haveuserId
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
About route
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.
Similarly if we navigate to /about/posts
path, our posts component is rendered as shown below.
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.
We can further click on any post to view it as well.
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 .