Recommended resources
The freely available resources this lesson will direct you to.
Hello! Welcome back to our course.
In the last lesson, we established how to create a robust application structure using nested routes and shared layouts. We saw how the <Outlet /> component acts as a placeholder for child routes, allowing us to build persistent UI like navigation bars and sidebars.
Today, we'll build directly on that foundation to tackle a core concept of server-side rendering: fetching data before a page renders. This is a significant shift from the typical client-side approach where components mount and then trigger data fetches. With Tanstack Router, we use route loaders to ensure data is ready the moment the component needs it, which is the key to fast initial page loads and better SEO.
This lesson will cover:
- Defining an
asyncloaderfunction on a route. - Fetching data from an external API within the loader.
- Using route parameters to fetch dynamic data.
- Accessing the loader's data type-safely within your components.
1. The loader Function: Data Fetching at the Route Level
In a traditional React SPA, you might fetch data inside a component using a useEffect hook or a library like React Query. The component renders a loading state, fetches data, and then re-renders with the data.
Tanstack Router flips this model. A loader is an asynchronous function you attach directly to a route definition. This function is executed before the route's component is rendered. In an SSR context like Tanstack Start, this means the loader runs on the server.
The process is as follows:
- A user navigates to a URL.
- Tanstack Router matches the URL to a route.
- It executes the
loaderfunction associated with that route. - Once the
loader's promise resolves, the returned data is made available. - The route's component renders, with immediate access to the data.
This eliminates the client-side fetch-and-rerender cycle for the initial page load.
2. A Practical Example: Fetching a Blog Post
Let's see this in action. Imagine we have a dynamic route for blog posts at /posts/$postId. We want to fetch the data for a specific post before rendering the page.
The following video provides an excellent, concise demonstration of this entire workflow.
Tanstack Router Tutorial - Routing, Lazy Loading, Data Fetching, Params...
This clip from PedroTech's "Tanstack Router Tutorial" clearly demonstrates how to add a loader function to a dynamic route, fetch data using the route's ID parameter, and then display that data in the component.
Watch the loader function. Pay close attention to how the loader function is defined as an async function, how it accesses params.id, and how useLoaderData is used in the component.
3. Code Breakdown
Let's break down the code from the video to understand each part.
The Route Definition (/routes/posts/$postId.tsx)
Here is what the route file looks like. We've added a loader property to the object passed to createFileRoute.
// src/routes/posts/$postId.tsx
import { createFileRoute } from '@tanstack/react-router'
// A helper function to fetch a single post
const fetchPost = async (postId: string) => {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
if (!res.ok) {
throw new Error('Failed to fetch post')
}
return res.json()
}
export const Route = createFileRoute('/posts/$postId')({
// 1. Define the loader function
loader: async ({ params }) => {
const post = await fetchPost(params.postId)
return { post } // Return data in an object
},
component: PostComponent,
})
function PostComponent() {
// 2. Access the data with useLoaderData
const { post } = Route.useLoaderData()
return (
<div>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
)
}
Let's analyze the key parts:
-
The
loaderfunction:- It's an
asyncfunction, allowing us to useawaitfor data fetching. - It receives an object containing context about the navigation, including
params. Since our route file is named$postId.tsx, the router automatically provides apostIdproperty on theparamsobject. - It calls our
fetchPostutility and returns the result. The data you return here is what becomes available to your component.
- It's an
-
The
useLoaderDataHook:- Inside the component, we call
Route.useLoaderData(). This hook retrieves the data returned by theloaderfor this specific route. - Because of Tanstack Router's type inference, the
postvariable is fully typed based on what theloaderfunction returns. There's no need for manual type casting.
- Inside the component, we call
4. The Official Documentation
The video provides a great practical overview. For a deeper, more formal understanding, the official documentation is the definitive source. Please read the following sections to solidify these concepts.
Data Loading | TanStack Router React Docs
The official Tanstack Router documentation provides detailed explanations of the data loading lifecycle and API. These sections cover the core concepts we've just discussed.
Please read the following three sections: Route loaders: Find this section by scrolling down. It gives the basic definition and syntax. Consuming data from loaders: This section immediately follows and explains the useLoaderData hook for consuming data. Using Path Params: Further down, this section shows exactly how to access dynamic URL parameters within your loader, as we did in our example.
5. Loaders in a Full SSR Context with Tanstack Start
So far, the examples use fetch directly. In a real-world SSR application built with a framework like Tanstack Start, you'll often need to use secret API keys or access a database. This logic must only run on the server.
This is where loaders become even more powerful. A loader, running on the server, can securely access environment variables and call other server-only functions. Tanstack Start provides a createServerFn utility for this exact purpose.
Let's look at a more realistic example from the official Tanstack Start tutorial.
Calling an external API using TanStack Start
This tutorial, "Calling an external API using TanStack Start", shows a production-ready pattern. It demonstrates a route loader that calls a separate, secure server function to fetch data.
Read Step 3: Creating the Route with API Fetch Function to study the route implementation. Notice how the loader doesn't contain the fetch logic itself. Instead, it calls fetchPopularMovies, which is defined with createServerFn. This ensures the Authorization header and process.env.TMDB_AUTH_TOKEN are never exposed to the client's browser. Then, quickly review the section Understanding How It All Works Together for a summary of the data flow.
This pattern is central to your goal of building an SSR application. The loader acts as the bridge between the routing layer and your secure, server-side business logic. The createServerFn guarantees that sensitive code is stripped from the client bundle, a topic we will explore in detail in Module 2.
Conclusion
You've now learned the fundamental pattern for server-side data fetching in Tanstack Router. By moving data fetching from components to route loaders, you ensure data is available on the server before the initial render, leading to faster, SEO-friendly pages.
Key Takeaways:
loaderFunctions: Asynchronous functions on route definitions that run on the server (in SSR) before the component renders.- Data Access: The
useLoaderDatahook provides type-safe access to the data returned by the loader within the route's component. - Dynamic Data: Loaders receive route
params, allowing you to fetch data specific to the current URL (e.g., a post ID, a username). - SSR Security: In a full-stack framework like Tanstack Start, loaders should call server-only functions (like those created with
createServerFn) to handle sensitive operations and credentials securely.
In our next lesson, we'll address the user experience around data fetching. We'll learn how to implement route-level error boundaries and pending UI states, so you can gracefully handle API failures and show loading indicators for slow requests.