Routing library for Single Page Applications
For a long time, React's default routing library was React Router. However, over the past few years, an alternative has become more popular: the TanStack Router. In this article, we’ll look at both the TanStack Router and the general features of a routing library for an SPA, and show how you can use these features in your application.
Modern single page applications (SPAs) consist of a tree structure of components that information flows through. The more extensive an application is, the larger the component tree becomes. However, an app doesn’t consist of just one view, but usually several that are independent of each other. This means you can display a specific app subtree depending on the current view and hide other subtrees. This task happens so often that there are libraries that deal with exactly this aspect.
There are two major router approaches for React that a newcomer like the TanStack Router must compete with. There’s the modernized React Router as well as increasingly relevant hybrid approaches like Next.js with its file system-based routing. The TanStack Router offers both code-based and file system-based routing. In the code-based approach, which is familiar from the React Router, the TanStack Router works with a series of functions that can be used to implement the configuration for the application's routing. In the broadest sense, the routes are objects you can use to specify which components should be rendered for which path. Integration into an application is done with a provider that ensures the router’s functionality can be accessed from all subordinate components. Most of the features you know from the React Router or from the App Router of Next.js can be found similarly in the TanStack Router. This lets you define variables in the path, implement nested routes, or implement loaders.
You can use the TanStack Router in both a JavaScript and a TypeScript application. The router itself is fully implemented in TypeScript and only shows its full strength in such an environment. Throughout this article, we’ll assume you are using a TypeScript application. Use the command npm install @tanstack/react-router to install the library in your application. The TanStack Router, like TanStack Query, was created by Tanner Linsley. At the moment, the TanStack Router is still focused purely on React. This may change in the future, as was the case with TanStack Query, which was originally called React Query.
Unlike the usual routers in SPAs, the TanStack Router takes a different approach. This should be more familiar to you from full-stack frameworks like Next.js or Remix. The official recommendation from the TanStack Router’s documentation is to use file-based routing to configure the router. For file-based routing to work, the router needs a suitably configured build system. The TanStack Router works best with Vite as a build system. It provides the @tanstack/router-vite-plugin plug-in for this, which you must enter as an extension in the vite.config.ts file’s plug-in section.
In the next step, create the files for the different routes. There are three different route categories. The application has exactly one root route, which you create with createRootRoute, and any number of file routes. If you use the createFileRoute function to create them, the Vite build system will integrate these directly. If you create them with the createLazyFileRoute function, they will be created in a separate bundle and loaded as needed.
The root route is the entry point into the component tree (Listing 1). This route has no assigned path and is always activated, so the associated component is always rendered. It is a full-fledged route and in its component, you have access to the router’s full functionality. All files related to route configuration are stored in a directory named routes in your application’s src directory. Create the root route by calling the createRootRoute function, and passing it an object of the type RouteOptions. In the simplest case, you can also call the createRootRoute function without any arguments. Here, the router only renders the TanStack Router’s outlet component, where the router inserts the subordinate routes’ results. There is a clear rule for the root route regarding the file’s naming and storage location. The file name is __root.tsx and must be located in the routesDirectory. By default, this is the src/routes directory. In the file, export the route object in a constant with the name Route. This naming convention applies to all route definitions.
Listing 1: Root route code (src/routes/__root.tsx)
import { createRootRoute } from '@tanstack/react-router';
export const Route = createRootRoute();
Besides the root route, you also need other routes that will render the respective content. In our example, we’re using an application for managing books with a component for displaying all existing data records and a form for creating and editing data records. So we need two new routes: the /list route, which you save in the src/routes/list.tsx file, and the /form route, located in the src/routes/form/index.tsx file. As you can see, the TanStack Router supports two different approaches for storing routes. You can either store the route files as flat routes in a flat structure and model the paths with the file name, or store the files in a deeper structure, the route trees, where the path name maps the URL path. Theoretically, you can also combine both approaches, as seen in the example. However, take care that the route configuration stays comprehensible.
Now it's time to implement the two routes. Calling the createFileRoute function creates a function that you can pass the route configuration to. These nested function calls are necessary to tell TypeScript which route the current file is responsible for. The router can get this information from the path and file name, but TypeScript cannot. However, you don’t have to worry about this path. If the Vite dev server is running in the background, the router plug-in will automatically update the path for you based on the information from the file system. This ensures that the file system and code are always in sync.
The second function accepts a configuration object that requires at least one component property containing a valid React component. You can either define this component inline in the object or, as seen in Listing 2, as a separate component function. The advantage is that the route configuration stays more readable.
Listing 2: The /list-Route
import { createFileRoute } from '@tanstack/react-router';
export const Route = createFileRoute('/list')({
component: ListComponent,
});
function ListComponent() {
return (
<div>
<h1>List works</h1>
</div>
);
}
The ListComponent (the component behind the /list route) has its own state and loads the data for display from the server when it’s first rendered by calling the useEffect function. The component displays the data in the form of a list. To make sure that the list is correctly displayed, it only needs to be integrated into the application. This is usually done in the entry point, in the main.tsx file. In the first step, create a new router instance with the createRouter function. Here, too, you work with a configuration object that you pass to the function. This object requires at least the routeTree property. You can get the structure from the routeTree.gen.ts file...