UNPKG

@tanstack/react-router

Version:

Modern and scalable routing for React applications

1,279 lines (930 loc) 234 kB
export default `# Authenticated Routes Authentication is an extremely common requirement for web applications. In this guide, we'll walk through how to use TanStack Router to build protected routes, and how to redirect users to login if they try to access them. ## The \`route.beforeLoad\` Option The \`route.beforeLoad\` option allows you to specify a function that will be called before a route is loaded. It receives all of the same arguments that the \`route.loader\` function does. This is a great place to check if a user is authenticated, and redirect them to a login page if they are not. The \`beforeLoad\` function runs in relative order to these other route loading functions: - Route Matching (Top-Down) - \`route.params.parse\` - \`route.validateSearch\` - Route Loading (including Preloading) - **\`route.beforeLoad\`** - \`route.onError\` - Route Loading (Parallel) - \`route.component.preload?\` - \`route.load\` **It's important to know that the \`beforeLoad\` function for a route is called _before any of its child routes' \`beforeLoad\` functions_.** It is essentially a middleware function for the route and all of its children. **If you throw an error in \`beforeLoad\`, none of its children will attempt to load**. ## Redirecting While not required, some authentication flows require redirecting to a login page. To do this, you can **throw a \`redirect()\`** from \`beforeLoad\`: \`\`\`tsx // src/routes/_authenticated.tsx export const Route = createFileRoute('/_authenticated')({ beforeLoad: async ({ location }) => { if (!isAuthenticated()) { throw redirect({ to: '/login', search: { // Use the current location to power a redirect after login // (Do not use \`router.state.resolvedLocation\` as it can // potentially lag behind the actual current location) redirect: location.href, }, }) } }, }) \`\`\` > [!TIP] > The \`redirect()\` function takes all of the same options as the \`navigate\` function, so you can pass options like \`replace: true\` if you want to replace the current history entry instead of adding a new one. Once you have authenticated a user, it's also common practice to redirect them back to the page they were trying to access. To do this, you can utilize the \`redirect\` search param that we added in our original redirect. Since we'll be replacing the entire URL with what it was, \`router.history.push\` is better suited for this than \`router.navigate\`: \`\`\`tsx router.history.push(search.redirect) \`\`\` ## Non-Redirected Authentication Some applications choose to not redirect users to a login page, and instead keep the user on the same page and show a login form that either replaces the main content or hides it via a modal. This is also possible with TanStack Router by simply short circuiting rendering the \`<Outlet />\` that would normally render the child routes: \`\`\`tsx // src/routes/_authenticated.tsx export const Route = createFileRoute('/_authenticated')({ component: () => { if (!isAuthenticated()) { return <Login /> } return <Outlet /> }, }) \`\`\` This keeps the user on the same page, but still allows you to render a login form. Once the user is authenticated, you can simply render the \`<Outlet />\` and the child routes will be rendered. ## Authentication using React context/hooks If your authentication flow relies on interactions with React context and/or hooks, you'll need to pass down your authentication state to TanStack Router using \`router.context\` option. > [!IMPORTANT] > React hooks are not meant to be consumed outside of React components. If you need to use a hook outside of a React component, you need to extract the returned state from the hook in a component that wraps your \`<RouterProvider />\` and then pass the returned value down to TanStack Router. We'll cover the \`router.context\` options in-detail in the [Router Context](../router-context.md) section. Here's an example that uses React context and hooks for protecting authenticated routes in TanStack Router. See the entire working setup in the [Authenticated Routes example](https://github.com/TanStack/router/tree/main/examples/react/authenticated-routes). - \`src/routes/__root.tsx\` \`\`\`tsx import { createRootRouteWithContext } from '@tanstack/react-router' interface MyRouterContext { // The ReturnType of your useAuth hook or the value of your AuthContext auth: AuthState } export const Route = createRootRouteWithContext<MyRouterContext>()({ component: () => <Outlet />, }) \`\`\` - \`src/router.tsx\` \`\`\`tsx import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' export const router = createRouter({ routeTree, context: { // auth will initially be undefined // We'll be passing down the auth state from within a React component auth: undefined!, }, }) \`\`\` - \`src/App.tsx\` \`\`\`tsx import { RouterProvider } from '@tanstack/react-router' import { AuthProvider, useAuth } from './auth' import { router } from './router' function InnerApp() { const auth = useAuth() return <RouterProvider router={router} context={{ auth }} /> } function App() { return ( <AuthProvider> <InnerApp /> </AuthProvider> ) } \`\`\` Then in the authenticated route, you can check the auth state using the \`beforeLoad\` function, and **throw a \`redirect()\`** to your **Login route** if the user is not signed-in. - \`src/routes/dashboard.route.tsx\` \`\`\`tsx import { createFileRoute, redirect } from '@tanstack/react-router' export const Route = createFileRoute('/dashboard')({ beforeLoad: ({ context, location }) => { if (!context.auth.isAuthenticated) { throw redirect({ to: '/login', search: { redirect: location.href, }, }) } }, }) \`\`\` You can _optionally_, also use the [Non-Redirected Authentication](#non-redirected-authentication) approach to show a login form instead of calling a **redirect**. This approach can also be used in conjunction with Pathless or Layout Route to protect all routes under their parent route. # Automatic Code Splitting > [!TIP] > We'll be filling in this guide soon about the wonderful world of automatic code splitting with TanStack Router and the many customization options available to you. Stay tuned! <!-- Include the basic configuration details and the code splitting groupings available which were introduced in https://github.com/TanStack/router/pull/3355--> # Code Splitting Code splitting and lazy loading is a powerful technique for improving the bundle size and load performance of an application. - Reduces the amount of code that needs to be loaded on initial page load - Code is loaded on-demand when it is needed - Results in more chunks that are smaller in size that can be cached more easily by the browser. ## How does TanStack Router split code? TanStack Router separates code into two categories: - **Critical Route Configuration** - The code that is required to render the current route and kick off the data loading process as early as possible. - Path Parsing/Serialization - Search Param Validation - Loaders, Before Load - Route Context - Static Data - Links - Scripts - Styles - All other route configuration not listed below - **Non-Critical/Lazy Route Configuration** - The code that is not required to match the route, and can be loaded on-demand. - Route Component - Error Component - Pending Component - Not-found Component > 🧠 **Why is the loader not split?** > > - The loader is already an asynchronous boundary, so you pay double to both get the chunk _and_ wait for the loader to execute. > - Categorically, it is less likely to contribute to a large bundle size than a component. > - The loader is one of the most important preloadable assets for a route, especially if you're using a default preload intent, like hovering over a link, so it's important for the loader to be available without any additional async overhead. > > Knowing the disadvantages of splitting the loader, if you still want to go ahead with it, head over to the [Data Loader Splitting](#data-loader-splitting) section. ## Encapsulating a route's files into a directory Since TanStack Router's file-based routing system is designed to support both flat and nested file structures, it's possible to encapsulate a route's files into a single directory without any additional configuration. To encapsulate a route's files into a directory, move the route file itself into a \`.route\` file within a directory with the same name as the route file. For example, if you have a route file named \`posts.tsx\`, you would create a new directory named \`posts\` and move the \`posts.tsx\` file into that directory, renaming it to \`route.tsx\`. **Before** - \`posts.tsx\` **After** - \`posts\` - \`route.tsx\` ## Approaches to code splitting TanStack Router supports multiple approaches to code splitting. If you are using code-based routing, skip to the [Code-Based Splitting](#code-based-splitting) section. When you are using file-based routing, you can use the following approaches to code splitting: - [Using automatic code-splitting ✨](#using-automatic-code-splitting) - [Using the \`.lazy.tsx\` suffix](#using-the-lazytsx-suffix) - [Using Virtual Routes](#using-virtual-routes) ## Using automatic code-splitting✨ This is the easiest and most powerful way to code split your route files. When using the \`autoCodeSplitting\` feature, TanStack Router will automatically code split your route files based on the non-critical route configuration mentioned above. > [!IMPORTANT] > The automatic code-splitting feature is **ONLY** available when you are using file-based routing with one of our [supported bundlers](../../routing/file-based-routing.md#getting-started-with-file-based-routing). > This will **NOT** work if you are **only** using the CLI (\`@tanstack/router-cli\`). To enable automatic code-splitting, you just need to add the following to the configuration of your TanStack Router Bundler Plugin: \`\`\`ts // vite.config.ts import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { tanstackRouter } from '@tanstack/router-plugin/vite' export default defineConfig({ plugins: [ tanstackRouter({ // ... autoCodeSplitting: true, }), react(), // Make sure to add this plugin after the TanStack Router Bundler plugin ], }) \`\`\` That's it! TanStack Router will automatically code-split all your route files by their critical and non-critical route configurations. If you want more control over the code-splitting process, head over to the [Automatic Code Splitting](../automatic-code-splitting.md) guide to learn more about the options available. ## Using the \`.lazy.tsx\` suffix If you are not able to use the automatic code-splitting feature, you can still code-split your route files using the \`.lazy.tsx\` suffix. It is **as easy as moving your code into a separate file with a \`.lazy.tsx\` suffix** and using the \`createLazyFileRoute\` function instead of \`createFileRoute\`. > [!IMPORTANT] > The \`__root.tsx\` route file, using either \`createRootRoute\` or \`createRootRouteWithContext\`, does not support code splitting, since it's always rendered regardless of the current route. These are the only options that \`createLazyFileRoute\` support: | Export Name | Description | | ------------------- | --------------------------------------------------------------------- | | \`component\` | The component to render for the route. | | \`errorComponent\` | The component to render when an error occurs while loading the route. | | \`pendingComponent\` | The component to render while the route is loading. | | \`notFoundComponent\` | The component to render if a not-found error gets thrown. | ### Example code splitting with \`.lazy.tsx\` When you are using \`.lazy.tsx\` you can split your route into two files to enable code splitting: **Before (Single File)** \`\`\`tsx // src/routes/posts.tsx import { createFileRoute } from '@tanstack/react-router' import { fetchPosts } from './api' export const Route = createFileRoute('/posts')({ loader: fetchPosts, component: Posts, }) function Posts() { // ... } \`\`\` **After (Split into two files)** This file would contain the critical route configuration: \`\`\`tsx // src/routes/posts.tsx import { createFileRoute } from '@tanstack/react-router' import { fetchPosts } from './api' export const Route = createFileRoute('/posts')({ loader: fetchPosts, }) \`\`\` With the non-critical route configuration going into the file with the \`.lazy.tsx\` suffix: \`\`\`tsx // src/routes/posts.lazy.tsx import { createLazyFileRoute } from '@tanstack/react-router' export const Route = createLazyFileRoute('/posts')({ component: Posts, }) function Posts() { // ... } \`\`\` ## Using Virtual Routes You might run into a situation where you end up splitting out everything from a route file, leaving it empty! In this case, simply **delete the route file entirely**! A virtual route will automatically be generated for you to serve as an anchor for your code split files. This virtual route will live directly in the generated route tree file. **Before (Virtual Routes)** \`\`\`tsx // src/routes/posts.tsx import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/posts')({ // Hello? }) \`\`\` \`\`\`tsx // src/routes/posts.lazy.tsx import { createLazyFileRoute } from '@tanstack/react-router' export const Route = createLazyFileRoute('/posts')({ component: Posts, }) function Posts() { // ... } \`\`\` **After (Virtual Routes)** \`\`\`tsx // src/routes/posts.lazy.tsx import { createLazyFileRoute } from '@tanstack/react-router' export const Route = createLazyFileRoute('/posts')({ component: Posts, }) function Posts() { // ... } \`\`\` Tada! 🎉 ## Code-Based Splitting If you are using code-based routing, you can still code-split your routes using the \`Route.lazy()\` method and the \`createLazyRoute\` function. You'll need to split your route configuration into two parts: Create a lazy route using the \`createLazyRoute\` function. \`\`\`tsx // src/posts.lazy.tsx export const Route = createLazyRoute('/posts')({ component: MyComponent, }) function MyComponent() { return <div>My Component</div> } \`\`\` Then, call the \`.lazy\` method on the route definition in your \`app.tsx\` to import the lazy/code-split route with the non-critical route configuration. \`\`\`tsx // src/app.tsx const postsRoute = createRoute({ getParentRoute: () => rootRoute, path: '/posts', }).lazy(() => import('./posts.lazy').then((d) => d.Route)) \`\`\` ## Data Loader Splitting **Be warned!!!** Splitting a route loader is a dangerous game. It can be a powerful tool to reduce bundle size, but it comes with a cost as mentioned in the [How does TanStack Router split code?](#how-does-tanstack-router-split-code) section. You can code split your data loading logic using the Route's \`loader\` option. While this process makes it difficult to maintain type-safety with the parameters passed to your loader, you can always use the generic \`LoaderContext\` type to get you most of the way there: \`\`\`tsx import { lazyFn } from '@tanstack/react-router' const route = createRoute({ path: '/my-route', component: MyComponent, loader: lazyFn(() => import('./loader'), 'loader'), }) // In another file...a export const loader = async (context: LoaderContext) => { /// ... } \`\`\` If you are using file-based routing, you'll only be able to split your \`loader\` if you are using [Automatic Code Splitting](#using-automatic-code-splitting) with customized bundling options. ## Manually accessing Route APIs in other files with the \`getRouteApi\` helper As you might have guessed, placing your component code in a separate file than your route can make it difficult to consume the route itself. To help with this, TanStack Router exports a handy \`getRouteApi\` function that you can use to access a route's type-safe APIs in a file without importing the route itself. - \`my-route.tsx\` \`\`\`tsx import { createRoute } from '@tanstack/react-router' import { MyComponent } from './MyComponent' const route = createRoute({ path: '/my-route', loader: () => ({ foo: 'bar', }), component: MyComponent, }) \`\`\` - \`MyComponent.tsx\` \`\`\`tsx import { getRouteApi } from '@tanstack/react-router' const route = getRouteApi('/my-route') export function MyComponent() { const loaderData = route.useLoaderData() // ^? { foo: string } return <div>...</div> } \`\`\` The \`getRouteApi\` function is useful for accessing other type-safe APIs: - \`useLoaderData\` - \`useLoaderDeps\` - \`useMatch\` - \`useParams\` - \`useRouteContext\` - \`useSearch\` # Creating a Router ## The \`Router\` Class When you're ready to start using your router, you'll need to create a new \`Router\` instance. The router instance is the core brains of TanStack Router and is responsible for managing the route tree, matching routes, and coordinating navigations and route transitions. It also serves as a place to configure router-wide settings. \`\`\`tsx import { createRouter } from '@tanstack/react-router' const router = createRouter({ // ... }) \`\`\` ## Route Tree You'll probably notice quickly that the \`Router\` constructor requires a \`routeTree\` option. This is the route tree that the router will use to match routes and render components. Whether you used [file-based routing](../../routing/file-based-routing.md) or [code-based routing](../../routing/code-based-routing.md), you'll need to pass your route tree to the \`createRouter\` function: ### Filesystem Route Tree If you used our recommended file-based routing, then it's likely your generated route tree file was created at the default \`src/routeTree.gen.ts\` location. If you used a custom location, then you'll need to import your route tree from that location. \`\`\`tsx import { routeTree } from './routeTree.gen' \`\`\` ### Code-Based Route Tree If you used code-based routing, then you likely created your route tree manually using the root route's \`addChildren\` method: \`\`\`tsx const routeTree = rootRoute.addChildren([ // ... ]) \`\`\` ## Router Type Safety > [!IMPORTANT] > DO NOT SKIP THIS SECTION! ⚠️ TanStack Router provides amazing support for TypeScript, even for things you wouldn't expect like bare imports straight from the library! To make this possible, you must register your router's types using TypeScripts' [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) feature. This is done by extending the \`Register\` interface on \`@tanstack/react-router\` with a \`router\` property that has the type of your \`router\` instance: \`\`\`tsx declare module '@tanstack/react-router' { interface Register { // This infers the type of our router and registers it across your entire project router: typeof router } } \`\`\` With your router registered, you'll now get type-safety across your entire project for anything related to routing. ## 404 Not Found Route As promised in earlier guides, we'll now cover the \`notFoundRoute\` option. This option is used to configure a route that will render when no other suitable match is found. This is useful for rendering a 404 page or redirecting to a default route. If you are using either file-based or code-based routing, then you'll need to add a \`notFoundComponent\` key to \`createRootRoute\`: \`\`\`tsx export const Route = createRootRoute({ component: () => ( // ... ), notFoundComponent: () => <div>404 Not Found</div>, }); \`\`\` ## Other Options There are many other options that can be passed to the \`Router\` constructor. You can find a full list of them in the [API Reference](../../api/router/RouterOptionsType.md). # Custom Link While repeating yourself can be acceptable in many situations, you might find that you do it too often. At times, you may want to create cross-cutting components with additional behavior or styles. You might also consider using third-party libraries in combination with TanStack Router's type safety. ## \`createLink\` for cross-cutting concerns \`createLink\` creates a custom \`Link\` component with the same type parameters as \`Link\`. This means you can create your own component which provides the same type safety and typescript performance as \`Link\`. ### Basic example If you want to create a basic custom link component, you can do so with the following: [//]: # 'BasicExampleImplementation' \`\`\`tsx import * as React from 'react' import { createLink, LinkComponent } from '@tanstack/react-router' interface BasicLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> { // Add any additional props you want to pass to the anchor element } const BasicLinkComponent = React.forwardRef<HTMLAnchorElement, BasicLinkProps>( (props, ref) => { return ( <a ref={ref} {...props} className={'block px-3 py-2 text-blue-700'} /> ) }, ) const CreatedLinkComponent = createLink(BasicLinkComponent) export const CustomLink: LinkComponent<typeof BasicLinkComponent> = (props) => { return <CreatedLinkComponent preload={'intent'} {...props} /> } \`\`\` [//]: # 'BasicExampleImplementation' You can then use your newly created \`Link\` component as any other \`Link\` \`\`\`tsx <CustomLink to={'/dashboard/invoices/$invoiceId'} params={{ invoiceId: 0 }} /> \`\`\` [//]: # 'ExamplesUsingThirdPartyLibs' ## \`createLink\` with third party libraries Here are some examples of how you can use \`createLink\` with third-party libraries. ### React Aria Components example React Aria Components' [Link](https://react-spectrum.adobe.com/react-aria/Link.html) component does not support the standard \`onMouseEnter\` and \`onMouseLeave\` events. Therefore, you cannot use it directly with TanStack Router's \`preload (intent)\` prop. Explanation for this can be found here: - [https://react-spectrum.adobe.com/react-aria/interactions.html](https://react-spectrum.adobe.com/react-aria/interactions.html) - [https://react-spectrum.adobe.com/blog/building-a-button-part-2.html](https://react-spectrum.adobe.com/blog/building-a-button-part-2.html) It is possible to work around this by using the [useLink](https://react-spectrum.adobe.com/react-aria/useLink.html) hook from [React Aria Hooks](https://react-spectrum.adobe.com/react-aria/hooks.html) with a standard anchor element. \`\`\`tsx import * as React from 'react' import { createLink, LinkComponent } from '@tanstack/react-router' import { mergeProps, useFocusRing, useHover, useLink, useObjectRef, } from 'react-aria' import type { AriaLinkOptions } from 'react-aria' interface RACLinkProps extends Omit<AriaLinkOptions, 'href'> { children?: React.ReactNode } const RACLinkComponent = React.forwardRef<HTMLAnchorElement, RACLinkProps>( (props, forwardedRef) => { const ref = useObjectRef(forwardedRef) const { isPressed, linkProps } = useLink(props, ref) const { isHovered, hoverProps } = useHover(props) const { isFocusVisible, isFocused, focusProps } = useFocusRing(props) return ( <a {...mergeProps(linkProps, hoverProps, focusProps, props)} ref={ref} data-hovered={isHovered || undefined} data-pressed={isPressed || undefined} data-focus-visible={isFocusVisible || undefined} data-focused={isFocused || undefined} /> ) }, ) const CreatedLinkComponent = createLink(RACLinkComponent) export const CustomLink: LinkComponent<typeof RACLinkComponent> = (props) => { return <CreatedLinkComponent preload={'intent'} {...props} /> } \`\`\` ### Chakra UI example \`\`\`tsx import * as React from 'react' import { createLink, LinkComponent } from '@tanstack/react-router' import { Link } from '@chakra-ui/react' interface ChakraLinkProps extends Omit<React.ComponentPropsWithoutRef<typeof Link>, 'href'> { // Add any additional props you want to pass to the link } const ChakraLinkComponent = React.forwardRef< HTMLAnchorElement, ChakraLinkProps >((props, ref) => { return <Link ref={ref} {...props} /> }) const CreatedLinkComponent = createLink(ChakraLinkComponent) export const CustomLink: LinkComponent<typeof ChakraLinkComponent> = ( props, ) => { return ( <CreatedLinkComponent textDecoration={'underline'} _hover={{ textDecoration: 'none' }} _focus={{ textDecoration: 'none' }} preload={'intent'} {...props} /> ) } \`\`\` ### MUI example There is an [example](https://github.com/TanStack/router/tree/main/examples/react/start-material-ui) available which uses these patterns. #### \`Link\` If the MUI \`Link\` should simply behave like the router \`Link\`, it can be just wrapped with \`createLink\`: \`\`\`tsx import { createLink } from '@tanstack/react-router' import { Link } from '@mui/material' export const CustomLink = createLink(Link) \`\`\` If the \`Link\` should be customized this approach can be used: \`\`\`tsx import React from 'react' import { createLink } from '@tanstack/react-router' import { Link } from '@mui/material' import type { LinkProps } from '@mui/material' import type { LinkComponent } from '@tanstack/react-router' interface MUILinkProps extends LinkProps { // Add any additional props you want to pass to the Link } const MUILinkComponent = React.forwardRef<HTMLAnchorElement, MUILinkProps>( (props, ref) => <Link ref={ref} {...props} />, ) const CreatedLinkComponent = createLink(MUILinkComponent) export const CustomLink: LinkComponent<typeof MUILinkComponent> = (props) => { return <CreatedLinkComponent preload={'intent'} {...props} /> } // Can also be styled \`\`\` #### \`Button\` If a \`Button\` should be used as a router \`Link\`, the \`component\` should be set as \`a\`: \`\`\`tsx import React from 'react' import { createLink } from '@tanstack/react-router' import { Button } from '@mui/material' import type { ButtonProps } from '@mui/material' import type { LinkComponent } from '@tanstack/react-router' interface MUIButtonLinkProps extends ButtonProps<'a'> { // Add any additional props you want to pass to the Button } const MUIButtonLinkComponent = React.forwardRef< HTMLAnchorElement, MUIButtonLinkProps >((props, ref) => <Button ref={ref} component="a" {...props} />) const CreatedButtonLinkComponent = createLink(MUIButtonLinkComponent) export const CustomButtonLink: LinkComponent<typeof MUIButtonLinkComponent> = ( props, ) => { return <CreatedButtonLinkComponent preload={'intent'} {...props} /> } \`\`\` #### Usage with \`styled\` Any of these MUI approaches can then be used with \`styled\`: \`\`\`tsx import { css, styled } from '@mui/material' import { CustomLink } from './CustomLink' const StyledCustomLink = styled(CustomLink)( ({ theme }) => css\` color: \${theme.palette.common.white}; \`, ) \`\`\` ### Mantine example \`\`\`tsx import * as React from 'react' import { createLink, LinkComponent } from '@tanstack/react-router' import { Anchor, AnchorProps } from '@mantine/core' interface MantineAnchorProps extends Omit<AnchorProps, 'href'> { // Add any additional props you want to pass to the anchor } const MantineLinkComponent = React.forwardRef< HTMLAnchorElement, MantineAnchorProps >((props, ref) => { return <Anchor ref={ref} {...props} /> }) const CreatedLinkComponent = createLink(MantineLinkComponent) export const CustomLink: LinkComponent<typeof MantineLinkComponent> = ( props, ) => { return <CreatedLinkComponent preload="intent" {...props} /> } \`\`\` [//]: # 'ExamplesUsingThirdPartyLibs' # Custom Search Param Serialization By default, TanStack Router parses and serializes your URL Search Params automatically using \`JSON.stringify\` and \`JSON.parse\`. This process involves escaping and unescaping the search string, which is a common practice for URL search params, in addition to the serialization and deserialization of the search object. For instance, using the default configuration, if you have the following search object: \`\`\`tsx const search = { page: 1, sort: 'asc', filters: { author: 'tanner', min_words: 800 }, } \`\`\` It would be serialized and escaped into the following search string: \`\`\`txt ?page=1&sort=asc&filters=%7B%22author%22%3A%22tanner%22%2C%22min_words%22%3A800%7D \`\`\` We can implement the default behavior with the following code: \`\`\`tsx import { createRouter, parseSearchWith, stringifySearchWith, } from '@tanstack/react-router' const router = createRouter({ // ... parseSearch: parseSearchWith(JSON.parse), stringifySearch: stringifySearchWith(JSON.stringify), }) \`\`\` However, this default behavior may not be suitable for all use cases. For example, you may want to use a different serialization format, such as base64 encoding, or you may want to use a purpose-built serialization/deserialization library, like [query-string](https://github.com/sindresorhus/query-string), [JSURL2](https://github.com/wmertens/jsurl2), or [Zipson](https://jgranstrom.github.io/zipson/). This can be achieved by providing your own serialization and deserialization functions to the \`parseSearch\` and \`stringifySearch\` options in the [\`Router\`](../../api/router/RouterOptionsType.md#stringifysearch-method) configuration. When doing this, you can utilize TanStack Router's built-in helper functions, \`parseSearchWith\` and \`stringifySearchWith\`, to simplify the process. > [!TIP] > An important aspect of serialization and deserialization, is that you are able to get the same object back after deserialization. This is important because if the serialization and deserialization process is not done correctly, you may lose some information. For example, if you are using a library that does not support nested objects, you may lose the nested object when deserializing the search string. ![Diagram showing idempotent nature of URL search param serialization and deserialization](https://raw.githubusercontent.com/TanStack/router/main/docs/router/assets/search-serialization-deserialization-idempotency.jpg) Here are some examples of how you can customize the search param serialization in TanStack Router: ## Using Base64 It's common to base64 encode your search params to achieve maximum compatibility across browsers and URL unfurlers, etc. This can be done with the following code: \`\`\`tsx import { Router, parseSearchWith, stringifySearchWith, } from '@tanstack/react-router' const router = createRouter({ parseSearch: parseSearchWith((value) => JSON.parse(decodeFromBinary(value))), stringifySearch: stringifySearchWith((value) => encodeToBinary(JSON.stringify(value)), ), }) function decodeFromBinary(str: string): string { return decodeURIComponent( Array.prototype.map .call(atob(str), function (c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) }) .join(''), ) } function encodeToBinary(str: string): string { return btoa( encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { return String.fromCharCode(parseInt(p1, 16)) }), ) } \`\`\` > [⚠️ Why does this snippet not use atob/btoa?](#safe-binary-encodingdecoding) So, if we were to turn the previous object into a search string using this configuration, it would look like this: \`\`\`txt ?page=1&sort=asc&filters=eyJhdXRob3IiOiJ0YW5uZXIiLCJtaW5fd29yZHMiOjgwMH0%3D \`\`\` > [!WARNING] > If you are serializing user input into Base64, you run the risk of causing a collision with the URL deserialization. This can lead to unexpected behavior, such as the URL not being parsed correctly or being interpreted as a different value. To avoid this, you should encode the search params using a safe binary encoding/decoding method (see below). ## Using the query-string library The [query-string](https://github.com/sindresorhus/query-string) library is a popular for being able to reliably parse and stringify query strings. You can use it to customize the serialization format of your search params. This can be done with the following code: \`\`\`tsx import { createRouter } from '@tanstack/react-router' import qs from 'query-string' const router = createRouter({ // ... stringifySearch: stringifySearchWith((value) => qs.stringify(value, { // ...options }), ), parseSearch: parseSearchWith((value) => qs.parse(value, { // ...options }), ), }) \`\`\` So, if we were to turn the previous object into a search string using this configuration, it would look like this: \`\`\`txt ?page=1&sort=asc&filters=author%3Dtanner%26min_words%3D800 \`\`\` ## Using the JSURL2 library [JSURL2](https://github.com/wmertens/jsurl2) is a non-standard library that can compress URLs while still maintaining readability. This can be done with the following code: \`\`\`tsx import { Router, parseSearchWith, stringifySearchWith, } from '@tanstack/react-router' import { parse, stringify } from 'jsurl2' const router = createRouter({ // ... parseSearch: parseSearchWith(parse), stringifySearch: stringifySearchWith(stringify), }) \`\`\` So, if we were to turn the previous object into a search string using this configuration, it would look like this: \`\`\`txt ?page=1&sort=asc&filters=(author~tanner~min*_words~800)~ \`\`\` ## Using the Zipson library [Zipson](https://jgranstrom.github.io/zipson/) is a very user-friendly and performant JSON compression library (both in runtime performance and the resulting compression performance). To compress your search params with it (which requires escaping/unescaping and base64 encoding/decoding them as well), you can use the following code: \`\`\`tsx import { Router, parseSearchWith, stringifySearchWith, } from '@tanstack/react-router' import { stringify, parse } from 'zipson' const router = createRouter({ parseSearch: parseSearchWith((value) => parse(decodeFromBinary(value))), stringifySearch: stringifySearchWith((value) => encodeToBinary(stringify(value)), ), }) function decodeFromBinary(str: string): string { return decodeURIComponent( Array.prototype.map .call(atob(str), function (c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) }) .join(''), ) } function encodeToBinary(str: string): string { return btoa( encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { return String.fromCharCode(parseInt(p1, 16)) }), ) } \`\`\` > [⚠️ Why does this snippet not use atob/btoa?](#safe-binary-encodingdecoding) So, if we were to turn the previous object into a search string using this configuration, it would look like this: \`\`\`txt ?page=1&sort=asc&filters=JTdCJUMyJUE4YXV0aG9yJUMyJUE4JUMyJUE4dGFubmVyJUMyJUE4JUMyJUE4bWluX3dvcmRzJUMyJUE4JUMyJUEyQ3UlN0Q%3D \`\`\` <hr> ### Safe Binary Encoding/Decoding In the browser, the \`atob\` and \`btoa\` functions are not guaranteed to work properly with non-UTF8 characters. We recommend using these encoding/decoding utilities instead: To encode from a string to a binary string: \`\`\`typescript export function encodeToBinary(str: string): string { return btoa( encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { return String.fromCharCode(parseInt(p1, 16)) }), ) } \`\`\` To decode from a binary string to a string: \`\`\`typescript export function decodeFromBinary(str: string): string { return decodeURIComponent( Array.prototype.map .call(atob(str), function (c) { return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) }) .join(''), ) } \`\`\` # Data Loading Data loading is a common concern for web applications and is related to routing. When loading a page for your app, it's ideal if all of the page's async requirements are fetched and fulfilled as early as possible, in parallel. The router is the best place to coordinate these async dependencies as it's usually the only place in your app that knows where users are headed before content is rendered. You may be familiar with \`getServerSideProps\` from Next.js or \`loader\`s from Remix/React-Router. TanStack Router has similar functionality to preload/load assets on a per-route basis in parallel allowing it to render as quickly as possible as it fetches via suspense. Beyond these normal expectations of a router, TanStack Router goes above and beyond and provides **built-in SWR Caching**, a long-term in-memory caching layer for route loaders. This means that you can use TanStack Router to both preload data for your routes so they load instantaneously or temporarily cache route data for previously visited routes to use again later. ## The route loading lifecycle Every time a URL/history update is detected, the router executes the following sequence: - Route Matching (Top-Down) - \`route.params.parse\` - \`route.validateSearch\` - Route Pre-Loading (Serial) - \`route.beforeLoad\` - \`route.onError\` - \`route.errorComponent\` / \`parentRoute.errorComponent\` / \`router.defaultErrorComponent\` - Route Loading (Parallel) - \`route.component.preload?\` - \`route.loader\` - \`route.pendingComponent\` (Optional) - \`route.component\` - \`route.onError\` - \`route.errorComponent\` / \`parentRoute.errorComponent\` / \`router.defaultErrorComponent\` ## To Router Cache or not to Router Cache? There is a high possibility that TanStack's router cache will be a good fit for most smaller to medium size applications, but it's important to understand the tradeoffs of using it vs a more robust caching solution like TanStack Query: TanStack Router Cache Pros: - Built-in, easy to use, no extra dependencies - Handles deduping, preloading, loading, stale-while-revalidate, background refetching on a per-route basis - Coarse invalidation (invalidate all routes and cache at once) - Automatic garbage collection - Works great for apps that share little data between routes - "Just works" for SSR TanStack Router Cache Cons: - No persistence adapters/model - No shared caching/deduping between routes - No built-in mutation APIs (a basic \`useMutation\` hook is provided in many examples that may be sufficient for many use cases) - No built-in cache-level optimistic update APIs (you can still use ephemeral state from something like a \`useMutation\` hook to achieve this at the component level) > [!TIP] > If you know right away that you'd like to or need to use something more robust like TanStack Query, skip to the [External Data Loading](../external-data-loading.md) guide. ## Using the Router Cache The router cache is built-in and is as easy as returning data from any route's \`loader\` function. Let's learn how! ## Route \`loader\`s Route \`loader\` functions are called when a route match is loaded. They are called with a single parameter which is an object containing many helpful properties. We'll go over those in a bit, but first, let's look at an example of a route \`loader\` function: \`\`\`tsx // routes/posts.tsx export const Route = createFileRoute('/posts')({ loader: () => fetchPosts(), }) \`\`\` ## \`loader\` Parameters The \`loader\` function receives a single object with the following properties: - \`abortController\` - The route's abortController. Its signal is cancelled when the route is unloaded or when the Route is no longer relevant and the current invocation of the \`loader\` function becomes outdated. - \`cause\` - The cause of the current route match, either \`enter\` or \`stay\`. - \`context\` - The route's context object, which is a merged union of: - Parent route context - This route's context as provided by the \`beforeLoad\` option - \`deps\` - The object value returned from the \`Route.loaderDeps\` function. If \`Route.loaderDeps\` is not defined, an empty object will be provided instead. - \`location\` - The current location - \`params\` - The route's path params - \`parentMatchPromise\` - \`Promise<RouteMatch>\` (\`undefined\` for the root route) - \`preload\` - Boolean which is \`true\` when the route is being preloaded instead of loaded - \`route\` - The route itself Using these parameters, we can do a lot of cool things, but first, let's take a look at how we can control it and when the \`loader\` function is called. ## Consuming data from \`loader\`s To consume data from a \`loader\`, use the \`useLoaderData\` hook defined on your Route object. \`\`\`tsx const posts = Route.useLoaderData() \`\`\` If you don't have ready access to your route object (i.e. you're deep in the component tree for the current route), you can use \`getRouteApi\` to access the same hook (as well as the other hooks on the Route object). This should be preferred over importing the Route object, which is likely to create circular dependencies. \`\`\`tsx import { getRouteApi } from '@tanstack/react-router' // in your component const routeApi = getRouteApi('/posts') const data = routeApi.useLoaderData() \`\`\` ## Dependency-based Stale-While-Revalidate Caching TanStack Router provides a built-in Stale-While-Revalidate caching layer for route loaders that is keyed on the dependencies of a route: - The route's fully parsed pathname - e.g. \`/posts/1\` vs \`/posts/2\` - Any additional dependencies provided by the \`loaderDeps\` option - e.g. \`loaderDeps: ({ search: { pageIndex, pageSize } }) => ({ pageIndex, pageSize })\` Using these dependencies as keys, TanStack Router will cache the data returned from a route's \`loader\` function and use it to fulfill subsequent requests for the same route match. This means that if a route's data is already in the cache, it will be returned immediately, then **potentially** be refetched in the background depending on the "freshness" of the data. ### Key options To control router dependencies and "freshness", TanStack Router provides a plethora of options to control the keying and caching behavior of your route loaders. Let's take a look at them in the order that you are most likely to use them: - \`routeOptions.loaderDeps\` - A function that supplies you the search params for a router and returns an object of dependencies for use in your \`loader\` function. When these deps changed from navigation to navigation, it will cause the route to reload regardless of \`staleTime\`s. The deps are compared using a deep equality check. - \`routeOptions.staleTime\` - \`routerOptions.defaultStaleTime\` - The number of milliseconds that a route's data should be considered fresh when attempting to load. - \`routeOptions.preloadStaleTime\` - \`routerOptions.defaultPreloadStaleTime\` - The number of milliseconds that a route's data should be considered fresh attempting to preload. - \`routeOptions.gcTime\` - \`routerOptions.defaultGcTime\` - The number of milliseconds that a route's data should be kept in the cache before being garbage collected. - \`routeOptions.shouldReload\` - A function that receives the same \`beforeLoad\` and \`loaderContext\` parameters and returns a boolean indicating if the route should reload. This offers one more level of control over when a route should reload beyond \`staleTime\` and \`loaderDeps\` and can be used to implement patterns similar to Remix's \`shouldLoad\` option. ### ⚠️ Some Important Defaults - By default, the \`staleTime\` is set to \`0\`, meaning that the route's data will always be considered stale and will always be reloaded in the background when the route is rematched. - By default, a previously preloaded route is considered fresh for **30 seconds**. This means if a route is preloaded, then preloaded again within 30 seconds, the second preload will be ignored. This prevents unnecessary preloads from happening too frequently. **When a route is loaded normally, the standard \`staleTime\` is used.** - By default, the \`gcTime\` is set to **30 minutes**, meaning that any route data that has not been accessed in 30 minutes will be garbage collected and removed from the cache. - \`router.invalidate()\` will force all active routes to reload their loaders immediately and mark every cached route's data as stale. ### Using \`loaderDeps\` to access search params Imagine a \`/posts\` route supports some pagination via search params \`offset\` and \`limit\`. For the cache to uniquely store this data, we need to access these search params via the \`loaderDeps\` function. By explicitly identifying them, each route match for \`/posts\` with different \`offset\` and \`limit\` won't get mixed up! Once we have these deps in place, the route will always reload when the deps change. \`\`\`tsx // /routes/posts.tsx export const Route = createFileRoute('/posts')({ loaderDeps: ({ search: { offset, limit } }) => ({ offset, limit }), loader: ({ deps: { offset, limit } }) => fetchPosts({ offset, limit, }), }) \`\`\` ### Using \`staleTime\` to control how long data is considered fresh By default, \`staleTime\` for navigations is set to \`0\`ms (and 30 seconds for preloads) which means that the route's data will always be considered stale and will always be reloaded in the background when the route is matched and navigated to. **This is a good default for most use cases, but you may find that some route data is more static or potentially expensive to load.** In these cases, you can use the \`staleTime\` option to control how long the route's data is considered fresh for navigations. Let's take a look at an example: \`\`\`tsx // /routes/posts.tsx export const Route = createFileRoute('/posts')({ loader: () => fetchPosts(), // Consider the route's data fresh for 10 seconds staleTime: 10_000, }) \`\`\` By passing \`10_000\` to the \`staleTime\` option, we are telling the router to consider the route's data fresh for 10 seconds. This means that if the user navigates to \`/posts\` from \`/about\` within 10 seconds of the last loader result, the route's data will not be reloaded. If the user then navigates to \`/posts\` from \`/about\` after 10 seconds, the route's data will be reloaded **in the background**. ## Turning off stale-while-revalidate caching To disable stale-while-revalidate caching for a route, set the \`staleTime\` option to \`Infinity\`: \`\`\`tsx // /routes/posts.tsx export const Route = createFileRoute('/posts')({ loader: () => fetchPosts(), staleTime: Infinity, }) \`\`\` You can even turn this off for all routes by setting the \`defaultStaleTime\` option on the router: \`\`\`tsx const router = createRouter({ routeTree, defaultStaleTime: Infinity, }) \`\`\` ## Using \`shouldReload\` and \`gcTime\` to opt-out of caching Similar to Remix's default functionality, you may want to configure a route to only load on entry or when critical loader deps change. You can do this by using the \`gcTime\` option combined with the \`shouldReload\` option, which accepts either a \`boolean\` or a function that receives the same \`beforeLoad\` and \`loaderContext\` parameters and returns a boolean indicating if the route should reload. \`\`\`tsx // /routes/posts.tsx export const Route = createFileRoute('/posts')({ loaderDeps: ({ search: { offset, limit } }) => ({ offset, limit }), loader: ({ deps }) => fetchPosts(deps), // Do not cache this route's data after it's unloaded gcTime: 0, // Only reload the route when the user navigates to it or when deps change shouldReload: false, }) \`\`\` ### Opting out of caching while still preloading Even though you may opt-out of short-term caching for your route data, you can still get the benefits of preloading! With the above configuration, preloading will still "just work" with the default \`preloadGcTime\`. This means that if a route is preloaded, then navigated to, the route's data will be considered fresh and will not be reloaded. To opt out of preloading, don't turn it on via the \`routerOptions.defaultPreload\` or \`routeOptions.preload\` options. ## Passing all loader events to an external cache We break down this use case in the [External Data Loading](../external-data-loading.md) page, but if you'd like to use an external cache like TanStack Query, you can do so by passing all loader events to your external cache. As long as you are using the defaults, the only change you'll need to make is to set the \`defaultPreloadStaleTime\` option on the router to \`0\`: \`\`\`tsx const router = createRouter({ routeTree, defaultPreloadStaleTime: 0, }) \`\`\` This will ensure that every preload, load, and reload event will trigger your \`loader\` functions, which can then be handled and deduped by your external cache. ## Using Router Context The \`context\` argument passed to the \`loader\` function is an object containing a merged union of: - Parent route context - This route's context as provided by the \`beforeLoad\` option Starting at the very top of the router, you can pass an initial context to the router via the \`context\` option. This context will be available to all routes in the router and get copied and extended by each route as they are matched. This happens by passing a context to a route via the \`beforeLoad\` option. This context will be available to all the route's child routes. The resulting context will be available to the route's \`loader\` function. In this example, we'll create a function in our route context to fetch posts, then use it in our \`loader\` function. > 🧠 Context is a powerful tool for dependency injection. You can use it to inject services, hooks, and other objects into your router and routes. You can also