Tanstack Router

So now we have arrived to the point where we want multiple pages in our app. We need some sort of router tool to accomplish that. We could just work with the browser's history API to do it but there so many ways to get it wrong, plus we are spoiled for choice when it comes to great React routing libraries.

Historically I have taught react-router as part of this course. It has been around for a long time and is well tested. Nearly every version of this course uses it (there was a brief while where it was Reach Router, but that has since been merged back into React Router.) It is a great tool and it underpins Remix, a great full-stack React framework. Remix and React Router are actually merging and will eventually be the same thing. v8 of this course use React Router if you want to learn more about it from me.

TanStack Router

Today we are going to be using TanStack Router, another amazing router. Why the switch? Two reasons:

  • This is a client-side focused course, and TanStack Router is made for client-side use cases. React Router and Remix have shifted a lot of focus to a more full-stack experience (though they do still work client-side only).
  • In Intermediate React we will be covering more full-stack use cases, in which I will using Next.js to show off those features.

So, suffice to say, you have a lot of good choices. I'm a fan of TanStack Router and I think it's a great tool for you to get started with. Let's dive in.

Let's start by installing it.

npm install @tanstack/react-router@1.65.0
npm install -D @tanstack/router-plugin@1.65.0 @tanstack/router-devtools@1.65.0

We need to install the router and then we'll be using its code generation tool as well as its dev tools (which are dev-only, hence the -D). Let's add it first to our vite.config.js.

// at top
import { TanStackRouterVite } from "@tanstack/router-plugin/vite";

// add before react() in plugins
plugins: [TanStackRouterVite(), react()],

TanStack Router works by file-based conventions. You create routes in a specific way and TanStack Router will automatically glue it together for you. It does this by creating a file called routeTree.gen.ts. Even though this isn't a TypeScript project, the fact that this is TypeScript means that VS Code can read the types from your routes and help you with suggestions and intelligent errors. I would suggest adding this file to your .gitignore as well since it will get autogenerated with every build. Okay, so let's go make the files we need.

Create a routes directory in your src directory. Let's make a __root.jsx file in there. This file will be the base template used for every route. Most of this will come from App.jsx

import { useState } from "react";
import { createRootRoute, Outlet } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/router-devtools";
import PizzaOfTheDay from "../PizzaOfTheDay";
import Header from "../Header";
import { CartContext } from "../contexts";

export const Route = createRootRoute({
  component: () => {
    const cartHook = useState([]);
    return (
      <>
        <CartContext.Provider value={cartHook}>
          <div>
            <Header />
            <Outlet />
            <PizzaOfTheDay />
          </div>
        </CartContext.Provider>
        <TanStackRouterDevtools />
      </>
    );
  },
});
  • We added an <Outlet/> instead of our Order component. Now we can swap in new routes there! Notice that the Header will always be there as will the PizzaOfTheDay.
  • We added TanStack's excellent dev tools. We'll take a look at them in a bit, but they're in the bottom left, you can click on them once your app loads.
  • <> and </> are for when you want to render two sibling components (our context and our dev tools) but don't want to add a random div there. React requires you only return one top level component and we can do that with <> and </>

Great, let's go modify App.jsx now

// add at top
// remove useState import from react import
import { RouterProvider, createRouter } from "@tanstack/react-router";
import { routeTree } from "./routeTree.gen";

const router = createRouter({ routeTree });

// replace App
const App = () => {
  return (
    <StrictMode>
      <RouterProvider router={router} />
    </StrictMode>
  );
};

This just imports the generated routeTree and then makes use of it in the project. This file really only should be used for rendering the file. Everything else should likely live in __root.jsx.

Okay, move Order.jsx from the base directory and into routes. If it asks, say yes to update paths. Rename it to order.lazy.jsx Let's modify it now to make it a route.

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

// make sure you modified the relative paths here – VS Code may have done this for you already
import { CartContext } from "../contexts";
import Cart from "../Cart";
import Pizza from "../Pizza";

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

function Order() {
  // Order component code …
}
  • Here we're making a new route. We define what URL it's at, /order, and what component to render, Order.
  • We're making it lazy. It will now code split this for us and lazy-load our route for us. This really helps in large apps. In this tiny app it probably actually slows us down because adding 1KB to an app doesn't slow it down, but an extra round trip does. When you do lazy loading, please measure if it's helping. In any case, this is how you do it.

That's it! This used to be so hard to code split. In some of my old classes we had to go through some serious work to get it work but now this is all it takes! Pretty cool.

Let's add a home page! Make a file called index.lazy.jsx in the routes folder.

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

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

function Index() {
  return (
    <div className="index">
      <div className="index-brand">
        <h1>Padre Gino's</h1>
        <p>Pizza & Art at a location near you</p>
      </div>
      <ul>
        <li>
          <Link to="/order">Order</Link>
        </li>
        <li>
          <Link to="/past">Past Orders</Link>
        </li>
      </ul>
    </div>
  );
}
  • Nothing new here except maybe the links. These link components make it easy for TanStack Router to control the browser history so definitely use them.

Lastly let's modify Header to be able to link back to the home page.

// at the top
import { Link } from "@tanstack/react-router";

// surround the logo with a link
<Link to={"/"}>
  <h1 className="logo">Padre Gino's Pizza</h1>
</Link>

This should all work now. Open your app and see if it loads.

  • Notice you get a little flash before it loads. It's lazy-loading your route. If this was a real app, we'd make a more pleasant loading experience. I'll leave that as an exercise for you to do.
  • So, let's talk about the cart. Go to the order page, add a bunch of stuff to cart, and head back to the home page. Notice there's still stuff in our cart! Head back to the order page and see it's still there. That's because __root.jsx (where our cart state lives) never gets un-rendered. If this just lived in Cart.jsx or order.lazy.jsx, it'd be blown away whenever you navigate away. Why? Because when the component gets un-rendered, it loses all of its state. This is one of the reasons Context can be super powerful.
  • Take a gander at the dev tools being rendered in the bottom left (don't worry, it gets stripped out automatically in production.) Click into and peruse around. Lots of cool stuff here for free.

You did it! That's how easy routing has become. It's come so far from the early days of React.

🏁 Click here to see the state of the project up until now: 09-routing