- Lab
-
Libraries: If you want this lab, consider one of these libraries.
- Core Tech
Guided: Build a Multi-page App with TanStack Router
In this Code Lab, you will build a dynamic, multi-page React application using TanStack Router. You’ll utilize key routing concepts like nested routes, URL parameters, data loading, and error handling. By the end of this lab, you will have established a simple and practical application with these concepts.
Lab Info
Table of Contents
-
Challenge
Introduction
Welcome to this Code Lab on building a multi-page application with TanStack Router!
TanStack Router is a modern, powerful routing library for React that emphasizes type safety, performance, and features like asynchronous data loading out of the box. In this lab, you'll build a simple blog application to explore its core concepts.
You will start with a basic React project and incrementally add routes, layouts, data loading, and error handling. If you would like to check your implementations, you can find solutions within the
solutiondirectory. These solutions are not definitive, so don't worry about semantics such as having exact variable names, formatting, etc.You can also run the application at any point in the lab with
npm run devwithin the Terminal. -
Challenge
Basic Router Configuration
The first step is to define the structure of your application's routes and integrate the router with your React component tree. You'll define a root route which acts as the main entry point, and then create a router instance that knows about your application's pages.
Your first priority will be to define the foundational routes for the application.
Define the Initial Route Tree
- Inside the
main.jsxfile, find the// TODO: Task 2.1comment. - Create a
rootRouteusingcreateRootRoute(). This route will serve as the parent for all other routes. - Define two child routes for the home (
/) and about (/about) pages. Create and attach them to therootRoutewithcreateRoute({ getParentRoute: () => rootRoute, path: '', component: }); - For the home route, set the
pathto'/'and associate it with theIndexcomponent. For the about route, set thepathto'/about'and associate it with theAboutcomponent. - Finally, assemble these routes into a
routeTreeby adding them as children torootRoutewithrootRoute.addChildren([indexRoute, aboutRoute]);.
With the routes defined, you now need to create the router instance and provide it to your React application.
Create and Provide the Router
- Still in
src/main.jsx, locate the// TODO: Task 2.2comments. - Create a new router instance using
createRouter(). When creating it, pass therouteTreeyou defined in the previous task to the configuration object. - Find the
rootelement renderingApp. Replace the<App />component with the<RouterProvider />component from TanStack Router. Pass your newly created router instance to therouterprop of the provider.
- Inside the
-
Challenge
Layouts and Navigation
Most web applications have a shared layout—a common header, footer, or navigation bar. TanStack Router handles this elegantly using nested routes and an
<Outlet/>component. In this step, you'll create a persistent layout and add client-side navigation.
A layout route allows you to share UI, like a navigation bar, across multiple pages. You'll create a root component that includes navigation and a placeholder for child routes to render into.
Create the Root Layout Component
- Open the
src/routes/__root.jsxfile and find the// TODO: Task 3.1comment. - This component needs to render the UI for its child routes. Import the
Outletcomponent from@tanstack/react-routerand render it within themainelement. TheOutletacts as a placeholder where nested routes will be displayed.
Now, you'll make the navigation interactive. Instead of standard
<a>tags, TanStack Router provides a<Link>component for client-side navigation that avoids full-page reloads.Implement Navigation with the Link Component
- Still in
src/routes/__root.jsx, find the// TODO: Task 3.2comments. - Import the
Linkcomponent from@tanstack/react-router. - Replace the
<a>tags for Home, About, and Posts with the<Link>component. - Set the
toprop on each<Link>to the correct path:'/'for Home,'/about'for About, and'/posts'for Posts. - Set the
activePropsprop on each<Link>to{{ className: 'active' }}.
With the layout component ready, you need to tell the router to use it. You'll associate the layout component with the root route.
Connect the Layout to the Root Route
- Return to
src/main.jsxand find the// TODO: Task 3.3comment. - Modify the
rootRoutedefinition. Pass a configuration object tocreateRootRoute()and set thecomponentproperty to theRootcomponent you just worked on.
- Open the
-
Challenge
Dynamic Routes for Blog Posts
Static pages are great, but real applications need dynamic content. Here, you'll add routes for your blog. This includes a route to list all posts and a parameterized route to display a single post based on its ID from the URL.
Now, add you'll add routes for the blog. You'll need one route to list all posts and another, parameterized route to show a single post.
Define Routes for Posts
- In
src/main.jsx, find the// TODO: Task 4.1comments. - Create a new route for
/poststhat will render thePostscomponent. Make sure it's a child of therootRoute. - Create a nested route for individual posts. This route's path should be
'$postId', making it a child of thepostsRouteroute. The$prefix signifies a URL parameter. This route should render thePostcomponent. - Add these routes to the route tree by adding
postsRoute.addChildren([postIdRoute])afteraboutRoute.
With the routes defined, you need to implement the components that will display the data. You'll fetch the data in a later step; for now, just set up the component structure.
Implement the Post and Posts Components
- Open
src/routes/posts.jsxand find// TODO: Task 4.2and define an instance ofgetRouteApi()passing in the'/posts'route. Then modify thepostsvariable to retrieve loader data withrouteApi.useLoaderData(). - Map over the
postsarray to render a list of post titles. Each title should be a<Link>pointing to that post's specific URL (e.g.,/posts/1). - Open
src/routes/post.jsxand find// TODO: Task 4.2and define an instance ofgetRouteApi()passing in the'/posts/$postId'route. Then modify thepostsvariable to retrieve loader data withrouteApi.useLoaderData().
- In
-
Challenge
Data Loading with Loaders
One of TanStack Router's standout features is its data loading strategy.
loaderfunctions allow you to fetch data before a component renders, simplifying state management and eliminating data-fetching waterfalls. You'll now hook up your post routes to a mock API.
Loaders are functions that fetch data before a route component renders. This is a powerful feature for handling data dependencies gracefully.
Implement Data Loaders for Post Routes
- Open
src/main.jsxand find// TODO: Task 5.1. - Add a
loaderfunction to the/postsroute definition. This function should call thefetchAllPostsfunction from our mock API (src/api.js) and return the result. - Add another
loaderfunction to the/$postIdroute. This loader is special because it is an asynchronous arrow function that receives the route'sparams. Useparams.postIdto call thefetchPostByIdAPI function and return the result.
- Open
-
Challenge
Advanced Route Handling
A robust application provides clear feedback to the user. In this final step, you'll implement loading indicators for when data is being fetched and create fallback UIs for when things go wrong, such as a data fetching error or a user navigating to a page that doesn't exist.
To improve user experience, it's crucial to show a loading state while data is being fetched. TanStack Router makes this easy with the
pendingComponentoption.Add a Pending Component for Loading States
- Open
src/main.jsxand find the// TODO: Task 6.1comments. - First, create a simple
PendingComponentfunction that returns some loading UI, like a<div>Loading...</div>. - Next, add the
pendingComponentproperty to both the/postsroute and the/$postIdroute definitions, assigning your newPendingComponentto them.
Finally, you'll handle errors. What happens if a post isn't found or the API fails? You can define an
errorComponentto render a fallback UI.Implement Error and Not Found Components
- Still in
src/main.jsx, find// TODO: Task 6.2. - Add the
errorComponentproperty to the/$postIdroute. For its value, provide an inline component function that receives anerrorprop. This component should render a message likeError: {error.message}. - To handle routes that don't exist at all, add a
notFoundComponentto therootRoute. This component should render a simple 'Not Found' message.
After completing all of these implementations, run your application to test it. You should see three links at the top for Home, About, and Posts. While Home and About should be self-explanatory, Posts should display three mock blog posts that should each display their own text snippets (through an
Outlet) when clicked.If your application is not behaving as expected, re-check your implementations against the provided instructions or compare them to the solutions within the
solutiondirectory. Otherwise, you've completed the lab! - Open
About the author
Real skill practice before real-world application
Hands-on Labs are real environments created by industry experts to help you learn. These environments help you gain knowledge and experience, practice without compromising your system, test without risk, destroy without fear, and let you learn from your mistakes. Hands-on Labs: practice your skills before delivering in the real world.
Learn by doing
Engage hands-on with the tools and technologies you’re learning. You pick the skill, we provide the credentials and environment.
Follow your guide
All labs have detailed instructions and objectives, guiding you through the learning process and ensuring you understand every step.
Turn time into mastery
On average, you retain 75% more of your learning if you take time to practice. Hands-on labs set you up for success to make those skills stick.