From e25315890dcf1548d9e1e786ac785d9432ddb0b8 Mon Sep 17 00:00:00 2001 From: Nate Bass Date: Sat, 26 Jul 2025 18:12:00 -0700 Subject: [PATCH 1/9] Initialize data dashboard iteration with a Tanstack React SPA app. --- .../data-dashboard-iteration-react/.gitignore | 10 + .../.vscode/launch.json | 53 + .../.vscode/settings.json | 55 + .../data-dashboard-iteration-react/GEMINI.md | 16463 ++++++++++++++++ .../data-dashboard-iteration-react/README.md | 320 + .../data-dashboard-iteration-react/biome.json | 40 + .../data-dashboard-iteration-react/index.html | 31 + .../package.json | 49 + .../pnpm-lock.yaml | 3136 +++ .../public/favicon.ico | Bin 0 -> 4286 bytes .../public/icon/search.svg | 6 + .../public/logo.svg | 8 + .../public/logo192.png | Bin 0 -> 2860 bytes .../public/logo512.png | Bin 0 -> 7837 bytes .../public/manifest.json | 25 + .../public/robots.txt | 3 + .../src/components/SearchBar.css | 72 + .../src/components/SearchBar.tsx | 97 + .../src/components/demo.FormComponents.tsx | 128 + .../district-selector/DistrictSelector.tsx | 82 + .../src/components/navbar-light/Navbar.tsx | 100 + .../src/components/navbar-primary/Navbar.tsx | 63 + .../src/data/demo-table-data.ts | 51 + .../src/hooks/demo.form-context.ts | 4 + .../src/hooks/demo.form.ts | 22 + .../apache-echarts/GaugeChart.tsx | 113 + .../apache-echarts/gauge-chart.css | 4 + .../integrations/tanstack-query/layout.tsx | 5 + .../tanstack-query/root-provider.tsx | 15 + .../src/logo.svg | 59 + .../src/main.tsx | 48 + .../src/reportWebVitals.ts | 13 + .../src/routeTree.gen.ts | 171 + .../src/routes/__root.tsx | 18 + .../src/routes/_home.tsx | 15 + .../src/routes/_home/demo.form.address.tsx | 200 + .../src/routes/_home/demo.form.simple.tsx | 65 + .../src/routes/_home/demo.table.tsx | 369 + .../src/routes/_home/demo.tanstack-query.tsx | 26 + .../src/routes/_home/index.tsx | 36 + .../src/styles.css | 20 + .../tsconfig.json | 50 + .../vite.config.ts | 23 + 43 files changed, 22068 insertions(+) create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/.gitignore create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/.vscode/launch.json create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/.vscode/settings.json create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/GEMINI.md create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/README.md create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/biome.json create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/index.html create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/package.json create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/pnpm-lock.yaml create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/favicon.ico create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/icon/search.svg create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/logo.svg create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/logo192.png create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/logo512.png create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/manifest.json create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/robots.txt create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/SearchBar.css create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/SearchBar.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/demo.FormComponents.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/district-selector/DistrictSelector.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/navbar-light/Navbar.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/navbar-primary/Navbar.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/data/demo-table-data.ts create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/hooks/demo.form-context.ts create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/hooks/demo.form.ts create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/integrations/apache-echarts/GaugeChart.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/integrations/apache-echarts/gauge-chart.css create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/integrations/tanstack-query/layout.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/integrations/tanstack-query/root-provider.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/logo.svg create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/main.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/reportWebVitals.ts create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/routeTree.gen.ts create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/routes/__root.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/routes/_home.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/routes/_home/demo.form.address.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/routes/_home/demo.form.simple.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/routes/_home/demo.table.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/routes/_home/demo.tanstack-query.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/routes/_home/index.tsx create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/styles.css create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/tsconfig.json create mode 100644 LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/vite.config.ts diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/.gitignore b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/.gitignore new file mode 100644 index 0000000..a520d5e --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/.gitignore @@ -0,0 +1,10 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local +count.txt +.env +.nitro +.tanstack +.idea diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/.vscode/launch.json b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/.vscode/launch.json new file mode 100644 index 0000000..26fb9a2 --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/.vscode/launch.json @@ -0,0 +1,53 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Launch server", + "request": "launch", + "runtimeArgs": ["run", "dev"], + "runtimeExecutable": "npm", + "skipFiles": ["/**"], + "type": "node", + "presentation": { + "hidden": true + } + }, + { + "type": "msedge", + "name": "edge", + "request": "launch", + "runtimeArgs": ["--remote-debugging-port=9222"], + "url": "http://localhost:3000", + "webRoot": "${workspaceFolder}", + "sourceMapPathOverrides": { + "/app/*": "${workspaceFolder}/*" + }, + "presentation": { + "hidden": true + } + }, + { + "name": "Launch Edge on Linux", + "type": "node", + "request": "launch", + "runtimeExecutable": "open", + "runtimeArgs": ["http://localhost:3000/"], + "presentation": { + "hidden": true + } + } + ], + "compounds": [ + { + "name": "Debug", + "configurations": ["Launch server", "edge"] + }, + { + "name": "Run", + "configurations": ["Launch server", "Launch Edge on Linux"] + } + ] +} diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/.vscode/settings.json b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/.vscode/settings.json new file mode 100644 index 0000000..be87e69 --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/.vscode/settings.json @@ -0,0 +1,55 @@ +{ + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + }, + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + }, + "[javascript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[javascriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[typescriptreact]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[json]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[jsonc]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[css]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "editor.codeActionsOnSave": { + "source.organizeImports.biome": "explicit" + }, + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#65c89b", + "activityBar.background": "#65c89b", + "activityBar.foreground": "#15202b", + "activityBar.inactiveForeground": "#15202b99", + "activityBarBadge.background": "#945bc4", + "activityBarBadge.foreground": "#e7e7e7", + "commandCenter.border": "#15202b99", + "sash.hoverBorder": "#65c89b", + "statusBar.background": "#42b883", + "statusBar.foreground": "#15202b", + "statusBarItem.hoverBackground": "#359268", + "statusBarItem.remoteBackground": "#42b883", + "statusBarItem.remoteForeground": "#15202b", + "titleBar.activeBackground": "#42b883", + "titleBar.activeForeground": "#15202b", + "titleBar.inactiveBackground": "#42b88399", + "titleBar.inactiveForeground": "#15202b99" + }, + "peacock.color": "#42b883" +} diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/GEMINI.md b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/GEMINI.md new file mode 100644 index 0000000..fcafcd3 --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/GEMINI.md @@ -0,0 +1,16463 @@ + + +<@tanstack/react-router_api> +Always Apply: false - This rule should only be applied when relevant files are open +Always apply this rule in these files: src/**/*.ts, src/**/*.tsx + +# ActiveLinkOptions type + +The `ActiveLinkOptions` type extends the [`LinkOptions`](../LinkOptionsType.md) type and +contains additional options that can be used to describe how a link should be styled when +it is active. + +```tsx +type ActiveLinkOptions = LinkOptions & { + activeProps?: + | React.AnchorHTMLAttributes + | (() => React.AnchorHTMLAttributes) + inactiveProps?: + | React.AnchorHTMLAttributes + | (() => React.AnchorHTMLAttributes) +} +``` + +## ActiveLinkOptions properties + +The `ActiveLinkOptions` object accepts/contains the following properties: + +### `activeProps` + +- `React.AnchorHTMLAttributes` +- Optional +- The props that will be applied to the anchor element when the link is active + +### `inactiveProps` + +- Type: `React.AnchorHTMLAttributes` +- Optional +- The props that will be applied to the anchor element when the link is inactive + +# AsyncRouteComponent type + +The `AsyncRouteComponent` type is used to describe a code-split route component that can +be preloaded using a `component.preload()` method. + +```tsx +type AsyncRouteComponent = SyncRouteComponent & { + preload?: () => Promise +} +``` + +# FileRoute class + +> [!CAUTION] +> This class has been deprecated and will be removed in the next major version of +> TanStack Router. +> Please use the [`createFileRoute`](../createFileRouteFunction.md) function instead. + +The `FileRoute` class is a factory that can be used to create a file-based route +instance. This route instance can then be used to automatically generate a route tree +with the `tsr generate` and `tsr watch` commands. + +## `FileRoute` constructor + +The `FileRoute` constructor accepts a single argument: the `path` of the file that the +route will be generated for. + +### Constructor options + +- Type: `string` literal +- Required, but **automatically inserted and updated by the `tsr generate` + and `tsr watch` commands**. +- The full path of the file that the route will be generated from. + +### Constructor returns + +- An instance of the `FileRoute` class that can be used to create a route. + +## `FileRoute` methods + +The `FileRoute` class implements the following method(s): + +### `.createRoute` method + +The `createRoute` method is a method that can be used to configure the file route +instance. It accepts a single argument: the `options` that will be used to configure the +file route instance. + +#### .createRoute options + +- Type: `Omit` +- [`RouteOptions`](../RouteOptionsType.md) +- Optional +- The same options that are available to the `Route` class, but with the + `getParentRoute`, `path`, and `id` options omitted since they are unnecessary for + file-based routing. + +#### .createRoute returns + +A [`Route`](../RouteType.md) instance that can be used to configure the route to be +inserted into the route-tree. + +> ⚠️ Note: For `tsr generate` and `tsr watch` to work properly, the file route instance +> must be exported from the file using the `Route` identifier. + +### Examples + +```tsx +import { FileRoute } from '@tanstack/react-router' + +export const Route = new FileRoute('/').createRoute({ + loader: () => { + return 'Hello World' + }, + component: IndexComponent, +}) + +function IndexComponent() { + const data = Route.useLoaderData() + return
{data}
+} +``` + +# LinkOptions type + +The `LinkOptions` type extends the [`NavigateOptions`](../NavigateOptionsType.md) type +and contains additional options that can be used by TanStack Router when handling actual +anchor element attributes. + +```tsx +type LinkOptions = NavigateOptions & { + target?: HTMLAnchorElement['target'] + activeOptions?: ActiveOptions + preload?: false | 'intent' + preloadDelay?: number + disabled?: boolean +} +``` + +## LinkOptions properties + +The `LinkOptions` object accepts/contains the following properties: + +### `target` + +- Type: `HTMLAnchorElement['target']` +- Optional +- The standard anchor tag target attribute + +### `activeOptions` + +- Type: `ActiveOptions` +- Optional +- The options that will be used to determine if the link is active + +### `preload` + +- Type: `false | 'intent' | 'viewport' | 'render'` +- Optional +- If set, the link's preloading strategy will be set to this value. +- See the [Preloading guide](../../../guide/preloading.md) for more information. + +### `preloadDelay` + +- Type: `number` +- Optional +- Delay intent preloading by this many milliseconds. If the intent exits before this + delay, the preload will be cancelled. + +### `disabled` + +- Type: `boolean` +- Optional +- If true, will render the link without the href attribute + +# LinkProps type + +The `LinkProps` type extends the [`ActiveLinkOptions`](../ActiveLinkOptionsType.md) and +`React.AnchorHTMLAttributes` types and contains additional props +specific to the `Link` component. + +```tsx +type LinkProps = ActiveLinkOptions & + Omit, 'children'> & { + children?: + | React.ReactNode + | ((state: { isActive: boolean }) => React.ReactNode) + } +``` + +## LinkProps properties + +- All of the props from [`ActiveLinkOptions`](../ActiveLinkOptionsType.md) +- All of the props from `React.AnchorHTMLAttributes` + +#### `children` + +- Type: `React.ReactNode | ((state: { isActive: boolean }) => React.ReactNode)` +- Optional +- The children that will be rendered inside of the anchor element. If a function is + provided, it will be called with an object that contains the `isActive` boolean value + that can be used to determine if the link is active. + +# MatchRouteOptions type + +The `MatchRouteOptions` type is used to describe the options that can be used when +matching a route. + +```tsx +interface MatchRouteOptions { + pending?: boolean + caseSensitive?: boolean + includeSearch?: boolean + fuzzy?: boolean +} +``` + +## MatchRouteOptions properties + +The `MatchRouteOptions` type has the following properties: + +### `pending` property + +- Type: `boolean` +- Optional +- If `true`, will match against pending location instead of the current location + +### `caseSensitive` property + +- Type: `boolean` +- Optional +- If `true`, will match against the current location with case sensitivity + +### `includeSearch` property + +- Type: `boolean` +- Optional +- If `true`, will match against the current location's search params using a deep + inclusive check. e.g. `{ a: 1 }` will match for a current location of `{ a: 1, b: 2 }` + +### `fuzzy` property + +- Type: `boolean` +- Optional +- If `true`, will match against the current location using a fuzzy match. e.g. `/posts` + will match for a current location of `/posts/123` + +# NavigateOptions type + +The `NavigateOptions` type is used to describe the options that can be used when +describing a navigation action in TanStack Router. + +```tsx +type NavigateOptions = ToOptions & { + replace?: boolean + resetScroll?: boolean + hashScrollIntoView?: boolean | ScrollIntoViewOptions + viewTransition?: boolean | ViewTransitionOptions + ignoreBlocker?: boolean + reloadDocument?: boolean + href?: string +} +``` + +## NavigateOptions properties + +The `NavigateOptions` object accepts the following properties: + +### `replace` + +- Type: `boolean` +- Optional +- Defaults to `false`. +- If `true`, the location will be committed to the browser history using + `history.replace` instead of `history.push`. + +### `resetScroll` + +- Type: `boolean` +- Optional +- Defaults to `true` so that the scroll position will be reset to 0,0 after the location + is committed to the browser history. +- If `false`, the scroll position will not be reset to 0,0 after the location is + committed to history. + +### `hashScrollIntoView` + +- Type: `boolean | ScrollIntoViewOptions` +- Optional +- Defaults to `true` so the element with an id matching the hash will be scrolled into + view after the location is committed to history. +- If `false`, the element with an id matching the hash will not be scrolled into view + after the location is committed to history. +- If an object is provided, it will be passed to the `scrollIntoView` method as options. +- See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) for + more information on `ScrollIntoViewOptions`. + +### `viewTransition` + +- Type: `boolean | ViewTransitionOptions` +- Optional +- Defaults to `false`. +- If `true`, navigation will be called using `document.startViewTransition()`. +- If [`ViewTransitionOptions`](../ViewTransitionOptionsType.md), route navigations will + be called using `document.startViewTransition({update, types})` where `types` will be + the strings array passed with `ViewTransitionOptions["types"]`. If the browser does not + support viewTransition types, the navigation will fall back to normal + `document.startTransition()`, same as if `true` was passed. +- If the browser does not support this api, this option will be ignored. +- +See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition) +for more information on how this function works. +- +See [Google](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#view-transition-types) +for more information on viewTransition types + +### `ignoreBlocker` + +- Type: `boolean` +- Optional +- Defaults to `false`. +- If `true`, navigation will ignore any blockers that might prevent it. + +### `reloadDocument` + +- Type: `boolean` +- Optional +- Defaults to `false`. +- If `true`, navigation to a route inside of router will trigger a full page load instead + of the traditional SPA navigation. + +### `href` + +- Type: `string` +- Optional +- This can be used instead of `to` to navigate to a fully built href, e.g. pointing to an + external target. + +- [`ToOptions`](../ToOptionsType.md) + +# NotFoundError + +The `NotFoundError` type is used to represent a not-found error in TanStack Router. + +```tsx +export type NotFoundError = { + global?: boolean + data?: any + throw?: boolean + routeId?: string +} +``` + +## NotFoundError properties + +The `NotFoundError` object accepts/contains the following properties: + +### `data` property + +- Type: `any` +- Optional +- Custom data that is passed into to `notFoundComponent` when the not-found error is + handled + +### `global` property + +- Type: `boolean` +- Optional - `default: false` +- If true, the not-found error will be handled by the `notFoundComponent` of the root + route instead of bubbling up from the route that threw it. This has the same behavior + as importing the root route and calling `RootRoute.notFound()`. + +### `route` property + +- Type: `string` +- Optional +- The ID of the route that will attempt to handle the not-found error. If the route does + not have a `notFoundComponent`, the error will bubble up to the parent route (and be + handled by the root route if necessary). By default, TanStack Router will attempt to + handle the not-found error with the route that threw it. + +### `throw` property + +- Type: `boolean` +- Optional - `default: false` +- If provided, will throw the not-found object instead of returning it. This can be + useful in places where `throwing` in a function might cause it to have a return type of + `never`. In that case, you can use `notFound({ throw: true })` to throw the not-found + object instead of returning it. + +# NotFoundRoute class + +> [!CAUTION] +> This class has been deprecated and will be removed in the next major version of +> TanStack Router. +> Please use the `notFoundComponent` route option that is present during route +> configuration. +> See the [Not Found Errors guide](../../../guide/not-found-errors.md) for more +> information. + +The `NotFoundRoute` class extends the `Route` class and can be used to create a not found +route instance. A not found route instance can be passed to the +`routerOptions.notFoundRoute` option to configure a default not-found/404 route for every +branch of the route tree. + +## Constructor options + +The `NotFoundRoute` constructor accepts an object as its only argument. + +- Type: + +```tsx +Omit< + RouteOptions, + | 'path' + | 'id' + | 'getParentRoute' + | 'caseSensitive' + | 'parseParams' + | 'stringifyParams' +> +``` + +- [RouteOptions](../RouteOptionsType.md) +- Required +- The options that will be used to configure the not found route instance. + +## Examples + +```tsx +import { NotFoundRoute, createRouter } from '@tanstack/react-router' +import { Route as rootRoute } from './routes/__root' +import { routeTree } from './routeTree.gen' + +const notFoundRoute = new NotFoundRoute({ + getParentRoute: () => rootRoute, + component: () =>
Not found!!!
, +}) + +const router = createRouter({ + routeTree, + notFoundRoute, +}) + +// ... other code +``` + +# ParsedHistoryState type + +The `ParsedHistoryState` type represents a parsed state object. Additionally to +`HistoryState`, it contains the index and the unique key of the route. + +```tsx +export type ParsedHistoryState = HistoryState & { + key?: string // TODO: Remove in v2 - use __TSR_key instead + __TSR_key?: string + __TSR_index: number +} +``` + +# ParsedLocation type + +The `ParsedLocation` type represents a parsed location in TanStack Router. It contains a +lot of useful information about the current location, including the pathname, search +params, hash, location state, and route masking information. + +```tsx +interface ParsedLocation { + href: string + pathname: string + search: TFullSearchSchema + searchStr: string + state: ParsedHistoryState + hash: string + maskedLocation?: ParsedLocation + unmaskOnReload?: boolean +} +``` + +# Redirect type + +The `Redirect` type is used to represent a redirect action in TanStack Router. + +```tsx +export type Redirect = { + statusCode?: number + throw?: any + headers?: HeadersInit +} & NavigateOptions +``` + +- [`NavigateOptions`](../NavigateOptionsType.md) + +## Redirect properties + +The `Redirect` object accepts/contains the following properties: + +### `statusCode` property + +- Type: `number` +- Optional +- The HTTP status code to use when redirecting + +### `throw` property + +- Type: `any` +- Optional +- If provided, will throw the redirect object instead of returning it. This can be useful + in places where `throwing` in a function might cause it to have a return type of + `never`. In that case, you can use `redirect({ throw: true })` to throw the redirect + object instead of returning it. + +### `headers` property + +- Type: `HeadersInit` +- Optional +- The HTTP headers to use when redirecting. + +# Register type + +This type is used to register a route tree with a router instance. Doing so unlocks the +full type safety of TanStack Router, including top-level exports from the +`@tanstack/react-router` package. + +```tsx +export type Register = { + // router: [Your router type here] +} +``` + +To register a route tree with a router instance, use declaration merging to add the type +of your router instance to the Register interface under the `router` property: + +## Examples + +```tsx +const router = createRouter({ + // ... +}) + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} +``` + +# RootRoute class + +> [!CAUTION] +> This class has been deprecated and will be removed in the next major version of +> TanStack Router. +> Please use the [`createRootRoute`](../createRootRouteFunction.md) function instead. + +The `RootRoute` class extends the `Route` class and can be used to create a root route +instance. A root route instance can then be used to create a route tree. + +## `RootRoute` constructor + +The `RootRoute` constructor accepts an object as its only argument. + +### Constructor options + +The options that will be used to configure the root route instance. + +- Type: + +```tsx +Omit< + RouteOptions, + | 'path' + | 'id' + | 'getParentRoute' + | 'caseSensitive' + | 'parseParams' + | 'stringifyParams' +> +``` + +- [`RouteOptions`](../RouteOptionsType.md) +- Optional + +## Constructor returns + +A new [`Route`](../RouteType.md) instance. + +## Examples + +```tsx +import { RootRoute, createRouter, Outlet } from '@tanstack/react-router' + +const rootRoute = new RootRoute({ + component: () => , + // ... root route options +}) + +const routeTree = rootRoute.addChildren([ + // ... other routes +]) + +const router = createRouter({ + routeTree, +}) +``` + +# RouteApi class + +> [!CAUTION] +> This class has been deprecated and will be removed in the next major version of +> TanStack Router. +> Please use the [`getRouteApi`](../getRouteApiFunction.md) function instead. + +The `RouteApi` class provides type-safe version of common hooks like `useParams`, +`useSearch`, `useRouteContext`, `useNavigate`, `useLoaderData`, and `useLoaderDeps` that +are pre-bound to a specific route ID and corresponding registered route types. + +## Constructor options + +The `RouteApi` constructor accepts a single argument: the `options` that will be used to +configure the `RouteApi` instance. + +### `opts.routeId` option + +- Type: `string` +- Required +- The route ID to which the `RouteApi` instance will be bound + +## Constructor returns + +- An instance of the [`RouteApi`](../RouteApiType.md) that is pre-bound to the route ID + that it was called with. + +## Examples + +```tsx +import { RouteApi } from '@tanstack/react-router' + +const routeApi = new RouteApi({ id: '/posts' }) + +export function PostsPage() { + const posts = routeApi.useLoaderData() + // ... +} +``` + +# RouteApi Type + +The `RouteApi` describes an instance that provides type-safe versions of common hooks +like `useParams`, `useSearch`, `useRouteContext`, `useNavigate`, `useLoaderData`, and +`useLoaderDeps` that are pre-bound to a specific route ID and corresponding registered +route types. + +## `RouteApi` properties and methods + +The `RouteApi` has the following properties and methods: + +### `useMatch` method + +```tsx + useMatch(opts?: { + select?: (match: TAllContext) => TSelected + }): TSelected +``` + +- A type-safe version of the [`useMatch`](../useMatchHook.md) hook that is pre-bound to + the route ID that the `RouteApi` instance was created with. +- Options + - `opts.select` + - Optional + - `(match: RouteMatch) => TSelected` + - If supplied, this function will be called with the route match and the return + value will be returned from `useMatch`. This value will also be used to + determine if the hook should re-render its parent component using shallow + equality checks. + - `opts.structuralSharing` + - Optional + - `boolean` + - Configures whether structural sharing is enabled for the value returned by + `select`. + - See the [Render Optimizations guide](../../../guide/render-optimizations.md) + for more information. +- Returns + - If a `select` function is provided, the return value of the `select` function. + - If no `select` function is provided, the `RouteMatch` object or a loosened version + of the `RouteMatch` object if `opts.strict` is `false`. + +### `useRouteContext` method + +```tsx + useRouteContext(opts?: { + select?: (search: TAllContext) => TSelected + }): TSelected +``` + +- A type-safe version of the [`useRouteContext`](../useRouteContextHook.md) hook that is + pre-bound to the route ID that the `RouteApi` instance was created with. +- Options + - `opts.select` + - Optional + - `(match: RouteContext) => TSelected` + - If supplied, this function will be called with the route match and the return + value will be returned from `useRouteContext`. This value will also be used to + determine if the hook should re-render its parent component using shallow + equality checks. +- Returns + - If a `select` function is provided, the return value of the `select` function. + - If no `select` function is provided, the `RouteContext` object or a loosened + version of the `RouteContext` object if `opts.strict` is `false`. + +### `useSearch` method + +```tsx + useSearch(opts?: { + select?: (search: TFullSearchSchema) => TSelected + }): TSelected +``` + +- A type-safe version of the [`useSearch`](../useSearchHook.md) hook that is pre-bound to + the route ID that the `RouteApi` instance was created with. +- Options + - `opts.select` + - Optional + - `(match: TFullSearchSchema) => TSelected` + - If supplied, this function will be called with the route match and the return + value will be returned from `useSearch`. This value will also be used to + determine if the hook should re-render its parent component using shallow + equality checks. + - `opts.structuralSharing` + - Optional + - `boolean` + - Configures whether structural sharing is enabled for the value returned by + `select`. + - See the [Render Optimizations guide](../../../guide/render-optimizations.md) + for more information. +- Returns + - If a `select` function is provided, the return value of the `select` function. + - If no `select` function is provided, the `TFullSearchSchema` object or a loosened + version of the `TFullSearchSchema` object if `opts.strict` is `false`. + +### `useParams` method + +```tsx + useParams(opts?: { + select?: (params: TAllParams) => TSelected + }): TSelected +``` + +- A type-safe version of the [`useParams`](../useParamsHook.md) hook that is pre-bound to + the route ID that the `RouteApi` instance was created with. +- Options + - `opts.select` + - Optional + - `(match: TAllParams) => TSelected` + - If supplied, this function will be called with the route match and the return + value will be returned from `useParams`. This value will also be used to + determine if the hook should re-render its parent component using shallow + equality checks. + - `opts.structuralSharing` + - Optional + - `boolean` + - Configures whether structural sharing is enabled for the value returned by + `select`. + - See the [Render Optimizations guide](../../../guide/render-optimizations.md) + for more information. +- Returns + - If a `select` function is provided, the return value of the `select` function. + - If no `select` function is provided, the `TAllParams` object or a loosened version + of the `TAllParams` object if `opts.strict` is `false`. + +### `useLoaderData` method + +```tsx + useLoaderData(opts?: { + select?: (search: TLoaderData) => TSelected + }): TSelected +``` + +- A type-safe version of the [`useLoaderData`](../useLoaderDataHook.md) hook that is + pre-bound to the route ID that the `RouteApi` instance was created with. +- Options + - `opts.select` + - Optional + - `(match: TLoaderData) => TSelected` + - If supplied, this function will be called with the route match and the return + value will be returned from `useLoaderData`. This value will also be used to + determine if the hook should re-render its parent component using shallow + equality checks. + - `opts.structuralSharing` + - Optional + - `boolean` + - Configures whether structural sharing is enabled for the value returned by + `select`. + - See the [Render Optimizations guide](../../../guide/render-optimizations.md) + for more information. +- Returns + - If a `select` function is provided, the return value of the `select` function. + - If no `select` function is provided, the `TLoaderData` object or a loosened version + of the `TLoaderData` object if `opts.strict` is `false`. + +### `useLoaderDeps` method + +```tsx + useLoaderDeps(opts?: { + select?: (search: TLoaderDeps) => TSelected + }): TSelected +``` + +- A type-safe version of the [`useLoaderDeps`](../useLoaderDepsHook.md) hook that is + pre-bound to the route ID that the `RouteApi` instance was created with. +- Options + - `opts.select` + - Optional + - `(match: TLoaderDeps) => TSelected` + - If supplied, this function will be called with the route match and the return + value will be returned from `useLoaderDeps`. + - `opts.structuralSharing` + - Optional + - `boolean` + - Configures whether structural sharing is enabled for the value returned by + `select`. + - See the [Render Optimizations guide](../../../guide/render-optimizations.md) + for more information. +- Returns + - If a `select` function is provided, the return value of the `select` function. + - If no `select` function is provided, the `TLoaderDeps` object. + +### `useNavigate` method + +```tsx + useNavigate(): // navigate function +``` + +- A type-safe version of [`useNavigate`](../useNavigateHook.md) that is pre-bound to the + route ID that the `RouteApi` instance was created with. + +# Route class + +> [!CAUTION] +> This class has been deprecated and will be removed in the next major version of +> TanStack Router. +> Please use the [`createRoute`](../createRouteFunction.md) function instead. + +The `Route` class implements the `RouteApi` class and can be used to create route +instances. A route instance can then be used to create a route tree. + +## `Route` constructor + +The `Route` constructor accepts an object as its only argument. + +### Constructor options + +- Type: [`RouteOptions`](../RouteOptionsType.md) +- Required +- The options that will be used to configure the route instance + +### Constructor returns + +A new [`Route`](../RouteType.md) instance. + +## Examples + +```tsx +import { Route } from '@tanstack/react-router' +import { rootRoute } from './__root' + +const indexRoute = new Route({ + getParentRoute: () => rootRoute, + path: '/', + loader: () => { + return 'Hello World' + }, + component: IndexComponent, +}) + +function IndexComponent() { + const data = indexRoute.useLoaderData() + return
{data}
+} +``` + +# RouteMask type + +The `RouteMask` type extends the [`ToOptions`](../ToOptionsType.md) type and has other +the necessary properties to create a route mask. + +## RouteMask properties + +The `RouteMask` type accepts an object with the following properties: + +### `...ToOptions` + +- Type: [`ToOptions`](../ToOptionsType.md) +- Required +- The options that will be used to configure the route mask + +### `options.routeTree` + +- Type: `TRouteTree` +- Required +- The route tree that this route mask will support + +### `options.unmaskOnReload` + +- Type: `boolean` +- Optional +- If `true`, the route mask will be removed when the page is reloaded + +# RouteMatch type + +The `RouteMatch` type represents a route match in TanStack Router. + +```tsx +interface RouteMatch { + id: string + routeId: string + pathname: string + params: Route['allParams'] + status: 'pending' | 'success' | 'error' + isFetching: boolean + showPending: boolean + error: unknown + paramsError: unknown + searchError: unknown + updatedAt: number + loadPromise?: Promise + loaderData?: Route['loaderData'] + context: Route['allContext'] + search: Route['fullSearchSchema'] + fetchedAt: number + abortController: AbortController + cause: 'enter' | 'stay' +} +``` + +# RouteOptions type + +The `RouteOptions` type is used to describe the options that can be used when creating a +route. + +## RouteOptions properties + +The `RouteOptions` type accepts an object with the following properties: + +### `getParentRoute` method + +- Type: `() => TParentRoute` +- Required +- A function that returns the parent route of the route being created. This is required + to provide full type safety to child route configurations and to ensure that the route + tree is built correctly. + +### `path` property + +- Type: `string` +- Required, unless an `id` is provided to configure the route as a pathless layout route +- The path segment that will be used to match the route. + +### `id` property + +- Type: `string` +- Optional, but required if a `path` is not provided +- The unique identifier for the route if it is to be configured as a pathless layout + route. If provided, the route will not match against the location pathname and its + routes will be flattened into its parent route for matching. + +### `component` property + +- Type: `RouteComponent` or `LazyRouteComponent` +- Optional - Defaults to `` +- The content to be rendered when the route is matched. + +### `errorComponent` property + +- Type: `RouteComponent` or `LazyRouteComponent` +- Optional - Defaults to `routerOptions.defaultErrorComponent` +- The content to be rendered when the route encounters an error. + +### `pendingComponent` property + +- Type: `RouteComponent` or `LazyRouteComponent` +- Optional - Defaults to `routerOptions.defaultPendingComponent` +- The content to be rendered if and when the route is pending and has reached its + pendingMs threshold. + +### `notFoundComponent` property + +- Type: `NotFoundRouteComponent` or `LazyRouteComponent` +- Optional - Defaults to `routerOptions.defaultNotFoundComponent` +- The content to be rendered when the route is not found. + +### `validateSearch` method + +- Type: `(rawSearchParams: unknown) => TSearchSchema` +- Optional +- A function that will be called when this route is matched and passed the raw search + params from the current location and return valid parsed search params. If this + function throws, the route will be put into an error state and the error will be thrown + during render. If this function does not throw, its return value will be used as the + route's search params and the return type will be inferred into the rest of the router. +- Optionally, the parameter type can be tagged with the `SearchSchemaInput` type like + this: `(searchParams: TSearchSchemaInput & SearchSchemaInput) => TSearchSchema`. If + this tag is present, `TSearchSchemaInput` will be used to type the `search` property of + `` and `navigate()` **instead of** `TSearchSchema`. The difference between + `TSearchSchemaInput` and `TSearchSchema` can be useful, for example, to express + optional search parameters. + +### `search.middlewares` property + +- Type: + `(({search: TSearchSchema, next: (newSearch: TSearchSchema) => TSearchSchema}) => TSearchSchema)[]` +- Optional +- Search middlewares are functions that transform the search parameters when generating + new links for a route or its descendants. +- A search middleware is passed in the current search (if it is the first middleware to + run) or is invoked by the previous middleware calling `next`. + +### `parseParams` method (⚠️ deprecated, use `params.parse` instead) + +- Type: `(rawParams: Record) => TParams` +- Optional +- A function that will be called when this route is matched and passed the raw params + from the current location and return valid parsed params. If this function throws, the + route will be put into an error state and the error will be thrown during render. If + this function does not throw, its return value will be used as the route's params and + the return type will be inferred into the rest of the router. + +### `stringifyParams` method (⚠️ deprecated, use `params.stringify` instead) + +- Type: `(params: TParams) => Record` +- Required if `parseParams` is provided +- A function that will be called when this route's parsed params are being used to build + a location. This function should return a valid object of `Record` + mapping. + +### `params.parse` method + +- Type: `(rawParams: Record) => TParams` +- Optional +- A function that will be called when this route is matched and passed the raw params + from the current location and return valid parsed params. If this function throws, the + route will be put into an error state and the error will be thrown during render. If + this function does not throw, its return value will be used as the route's params and + the return type will be inferred into the rest of the router. + +### `params.stringify` method + +- Type: `(params: TParams) => Record` +- A function that will be called when this route's parsed params are being used to build + a location. This function should return a valid object of `Record` + mapping. + +### `beforeLoad` method + +- Type: + +```tsx +type beforeLoad = ( + opts: RouteMatch & { + search: TFullSearchSchema + abortController: AbortController + preload: boolean + params: TAllParams + context: TParentContext + location: ParsedLocation + navigate: NavigateFn // @deprecated + buildLocation: BuildLocationFn + cause: 'enter' | 'stay' + }, +) => Promise | TRouteContext | void +``` + +- Optional +- [`ParsedLocation`](../ParsedLocationType.md) +- This async function is called before a route is loaded. If an error is thrown here, the + route's loader will not be called and the route will not render. If thrown during a + navigation, the navigation will be canceled and the error will be passed to the + `onError` function. If thrown during a preload event, the error will be logged to the + console and the preload will fail. +- If this function returns a promise, the route will be put into a pending state and + cause rendering to suspend until the promise resolves. If this route's pendingMs + threshold is reached, the `pendingComponent` will be shown until it resolves. If the + promise rejects, the route will be put into an error state and the error will be thrown + during render. +- If this function returns a `TRouteContext` object, that object will be merged into the + route's context and be made available in the `loader` and other related route + components/methods. +- It's common to use this function to check if a user is authenticated and redirect them + to a login page if they are not. To do this, you can either return or throw a + `redirect` object from this function. + +> 🚧 `opts.navigate` has been deprecated and will be removed in the next major release. +> Use `throw redirect({ to: '/somewhere' })` instead. Read more about the `redirect` +> function [here](../redirectFunction.md). + +### `loader` method + +- Type: + +```tsx +type loader = ( + opts: RouteMatch & { + abortController: AbortController + cause: 'preload' | 'enter' | 'stay' + context: TAllContext + deps: TLoaderDeps + location: ParsedLocation + params: TAllParams + preload: boolean + parentMatchPromise: Promise> + navigate: NavigateFn // @deprecated + route: AnyRoute + }, +) => Promise | TLoaderData | void +``` + +- Optional +- [`ParsedLocation`](../ParsedLocationType.md) +- This async function is called when a route is matched and passed the route's match + object. If an error is thrown here, the route will be put into an error state and the + error will be thrown during render. If thrown during a navigation, the navigation will + be canceled and the error will be passed to the `onError` function. If thrown during a + preload event, the error will be logged to the console and the preload will fail. +- If this function returns a promise, the route will be put into a pending state and + cause rendering to suspend until the promise resolves. If this route's pendingMs + threshold is reached, the `pendingComponent` will be shown until it resolves. If the + promise rejects, the route will be put into an error state and the error will be thrown + during render. +- If this function returns a `TLoaderData` object, that object will be stored on the + route match until the route match is no longer active. It can be accessed using the + `useLoaderData` hook in any component that is a child of the route match before another + `` is rendered. +- Deps must be returned by your `loaderDeps` function in order to appear. + +> 🚧 `opts.navigate` has been deprecated and will be removed in the next major release. +> Use `throw redirect({ to: '/somewhere' })` instead. Read more about the `redirect` +> function [here](../redirectFunction.md). + +### `loaderDeps` method + +- Type: + +```tsx +type loaderDeps = (opts: { search: TFullSearchSchema }) => Record +``` + +- Optional +- A function that will be called before this route is matched to provide additional + unique identification to the route match and serve as a dependency tracker for when the + match should be reloaded. It should return any serializable value that can uniquely + identify the route match from navigation to navigation. +- By default, path params are already used to uniquely identify a route match, so it's + unnecessary to return these here. +- If your route match relies on search params for unique identification, it's required + that you return them here so they can be made available in the `loader`'s `deps` + argument. + +### `staleTime` property + +- Type: `number` +- Optional +- Defaults to `routerOptions.defaultStaleTime`, which defaults to `0` +- The amount of time in milliseconds that a route match's loader data will be considered + fresh. If a route match is matched again within this time frame, its loader data will + not be reloaded. + +### `preloadStaleTime` property + +- Type: `number` +- Optional +- Defaults to `routerOptions.defaultPreloadStaleTime`, which defaults to `30_000` ms (30 + seconds) +- The amount of time in milliseconds that a route match's loader data will be considered + fresh when preloading. If a route match is preloaded again within this time frame, its + loader data will not be reloaded. If a route match is loaded (for navigation) within + this time frame, the normal `staleTime` is used instead. + +### `gcTime` property + +- Type: `number` +- Optional +- Defaults to `routerOptions.defaultGcTime`, which defaults to 30 minutes. +- The amount of time in milliseconds that a route match's loader data will be kept in + memory after a preload or it is no longer in use. + +### `shouldReload` property + +- Type: `boolean | ((args: LoaderArgs) => boolean)` +- Optional +- If `false` or returns `false`, the route match's loader data will not be reloaded on + subsequent matches. +- If `true` or returns `true`, the route match's loader data will be reloaded on + subsequent matches. +- If `undefined` or returns `undefined`, the route match's loader data will adhere to the + default stale-while-revalidate behavior. + +### `caseSensitive` property + +- Type: `boolean` +- Optional +- If `true`, this route will be matched as case-sensitive. + +### `wrapInSuspense` property + +- Type: `boolean` +- Optional +- If `true`, this route will be forcefully wrapped in a suspense boundary, regardless if + a reason is found to do so from inspecting its provided components. + +### `pendingMs` property + +- Type: `number` +- Optional +- Defaults to `routerOptions.defaultPendingMs`, which defaults to `1000` +- The threshold in milliseconds that a route must be pending before its + `pendingComponent` is shown. + +### `pendingMinMs` property + +- Type: `number` +- Optional +- Defaults to `routerOptions.defaultPendingMinMs` which defaults to `500` +- The minimum amount of time in milliseconds that the pending component will be shown for + if it is shown. This is useful to prevent the pending component from flashing on the + screen for a split second. + +### `preloadMaxAge` property + +- Type: `number` +- Optional +- Defaults to `30_000` ms (30 seconds) +- The maximum amount of time in milliseconds that a route's preloaded route data will be + cached for. If a route is not matched within this time frame, its loader data will be + discarded. + +### `preSearchFilters` property (⚠️ deprecated, use `search.middlewares` instead) + +- Type: `((search: TFullSearchSchema) => TFullSearchSchema)[]` +- Optional +- An array of functions that will be called when generating any new links to this route + or its grandchildren. +- Each function will be called with the current search params and should return a new + search params object that will be used to generate the link. +- It has a `pre` prefix because it is called before the user-provided function that is + passed to `navigate`/`Link` etc has a chance to modify the search params. + +### `postSearchFilters` property (⚠️ deprecated, use `search.middlewares` instead) + +- Type: `((search: TFullSearchSchema) => TFullSearchSchema)[]` +- Optional +- An array of functions that will be called when generating any new links to this route + or its grandchildren. +- Each function will be called with the current search params and should return a new + search params object that will be used to generate the link. +- It has a `post` prefix because it is called after the user-provided function that is + passed to `navigate`/`Link` etc has modified the search params. + +### `onError` property + +- Type: `(error: any) => void` +- Optional +- A function that will be called when an error is thrown during a navigation or preload + event. +- If this function throws a [`redirect`](../redirectFunction.md), then the router will + process and apply the redirect immediately. + +### `onEnter` property + +- Type: `(match: RouteMatch) => void` +- Optional +- A function that will be called when a route is matched and loaded after not being + matched in the previous location. + +### `onStay` property + +- Type: `(match: RouteMatch) => void` +- Optional +- A function that will be called when a route is matched and loaded after being matched + in the previous location. + +### `onLeave` property + +- Type: `(match: RouteMatch) => void` +- Optional +- A function that will be called when a route is no longer matched after being matched in + the previous location. + +### `onCatch` property + +- Type: `(error: Error, errorInfo: ErrorInfo) => void` +- Optional - Defaults to `routerOptions.defaultOnCatch` +- A function that will be called when errors are caught when the route encounters an + error. + +### `remountDeps` method + +- Type: + +```tsx +type remountDeps = (opts: RemountDepsOptions) => any + +interface RemountDepsOptions< + in out TRouteId, + in out TFullSearchSchema, + in out TAllParams, + in out TLoaderDeps, +> { + routeId: TRouteId + search: TFullSearchSchema + params: TAllParams + loaderDeps: TLoaderDeps +} +``` + +- Optional +- A function that will be called to determine whether a route component shall be + remounted after navigation. If this function returns a different value than previously, + it will remount. +- The return value needs to be JSON serializable. +- By default, a route component will not be remounted if it stays active after a + navigation. + +Example: +If you want to configure to remount a route component upon `params` change, use: + +```tsx +remountDeps: ({ params }) => params +``` + +# Route type + +The `Route` type is used to describe a route instance. + +## `Route` properties and methods + +An instance of the `Route` has the following properties and methods: + +### `.addChildren` method + +- Type: `(children: Route[]) => this` +- Adds child routes to the route instance and returns the route instance (but with + updated types to reflect the new children). + +### `.update` method + +- Type: `(options: Partial) => this` +- Updates the route instance with new options and returns the route instance (but with + updated types to reflect the new options). +- In some circumstances, it can be useful to update a route instance's options after it + has been created to avoid circular type references. +- ...`RouteApi` methods + +### `.lazy` method + +- Type: `(lazyImporter: () => Promise>) => this` +- Updates the route instance with a new lazy importer which will be resolved lazily when + loading the route. This can be useful for code splitting. + +### ...`RouteApi` methods + +- All of the methods from [`RouteApi`](../RouteApiType.md) are available. + +# Router Class + +> [!CAUTION] +> This class has been deprecated and will be removed in the next major version of +> TanStack Router. +> Please use the [`createRouter`](../createRouterFunction.md) function instead. + +The `Router` class is used to instantiate a new router instance. + +## `Router` constructor + +The `Router` constructor accepts a single argument: the `options` that will be used to +configure the router instance. + +### Constructor options + +- Type: [`RouterOptions`](../RouterOptionsType.md) +- Required +- The options that will be used to configure the router instance. + +### Constructor returns + +- An instance of the [`Router`](../RouterType.md). + +## Examples + +```tsx +import { Router, RouterProvider } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +const router = new Router({ + routeTree, + defaultPreload: 'intent', +}) + +export default function App() { + return +} +``` + +# RouterEvents type + +The `RouterEvents` type contains all of the events that the router can emit. Each +top-level key of this type, represents the name of an event that the router can emit. The +values of the keys are the event payloads. + +```tsx +type RouterEvents = { + onBeforeNavigate: { + type: 'onBeforeNavigate' + fromLocation?: ParsedLocation + toLocation: ParsedLocation + pathChanged: boolean + hrefChanged: boolean + } + onBeforeLoad: { + type: 'onBeforeLoad' + fromLocation?: ParsedLocation + toLocation: ParsedLocation + pathChanged: boolean + hrefChanged: boolean + } + onLoad: { + type: 'onLoad' + fromLocation?: ParsedLocation + toLocation: ParsedLocation + pathChanged: boolean + hrefChanged: boolean + } + onResolved: { + type: 'onResolved' + fromLocation?: ParsedLocation + toLocation: ParsedLocation + pathChanged: boolean + hrefChanged: boolean + } + onBeforeRouteMount: { + type: 'onBeforeRouteMount' + fromLocation?: ParsedLocation + toLocation: ParsedLocation + pathChanged: boolean + hrefChanged: boolean + } + onInjectedHtml: { + type: 'onInjectedHtml' + promise: Promise + } + onRendered: { + type: 'onRendered' + fromLocation?: ParsedLocation + toLocation: ParsedLocation + } +} +``` + +## RouterEvents properties + +Once an event is emitted, the following properties will be present on the event payload. + +### `type` property + +- Type: `onBeforeNavigate | onBeforeLoad | onLoad | onBeforeRouteMount | onResolved` +- The type of the event +- This is useful for discriminating between events in a listener function. + +### `fromLocation` property + +- Type: [`ParsedLocation`](../ParsedLocationType.md) +- The location that the router is transitioning from. + +### `toLocation` property + +- Type: [`ParsedLocation`](../ParsedLocationType.md) +- The location that the router is transitioning to. + +### `pathChanged` property + +- Type: `boolean` +- `true` if the path has changed between the `fromLocation` and `toLocation`. + +### `hrefChanged` property + +- Type: `boolean` +- `true` if the href has changed between the `fromLocation` and `toLocation`. + +## Example + +```tsx +import { createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +const router = createRouter({ routeTree }) + +const unsub = router.subscribe('onResolved', (evt) => { + // ... +}) +``` + +# RouterOptions + +The `RouterOptions` type contains all of the options that can be used to configure a +router instance. + +## RouterOptions properties + +The `RouterOptions` type accepts an object with the following properties and methods: + +### `routeTree` property + +- Type: `AnyRoute` +- Required +- The route tree that will be used to configure the router instance. + +### `history` property + +- Type: `RouterHistory` +- Optional +- The history object that will be used to manage the browser history. If not provided, a + new `createBrowserHistory` instance will be created and used. + +### `stringifySearch` method + +- Type: `(search: Record) => string` +- Optional +- A function that will be used to stringify search params when generating links. +- Defaults to `defaultStringifySearch`. + +### `parseSearch` method + +- Type: `(search: string) => Record` +- Optional +- A function that will be used to parse search params when parsing the current location. +- Defaults to `defaultParseSearch`. + +### `search.strict` property + +- Type: `boolean` +- Optional +- Defaults to `false` +- Configures how unknown search params (= not returned by any `validateSearch`) are + treated. +- If `false`, unknown search params will be kept. +- If `true`, unknown search params will be removed. + +### `defaultPreload` property + +- Type: `undefined | false | 'intent' | 'viewport' | 'render'` +- Optional +- Defaults to `false` +- If `false`, routes will not be preloaded by default in any way. +- If `'intent'`, routes will be preloaded by default when the user hovers over a link or + a `touchstart` event is detected on a ``. +- If `'viewport'`, routes will be preloaded by default when they are within the viewport + of the browser. +- If `'render'`, routes will be preloaded by default as soon as they are rendered in the + DOM. + +### `defaultPreloadDelay` property + +- Type: `number` +- Optional +- Defaults to `50` +- The delay in milliseconds that a route must be hovered over or touched before it is + preloaded. + +### `defaultComponent` property + +- Type: `RouteComponent` +- Optional +- Defaults to `Outlet` +- The default `component` a route should use if no component is provided. + +### `defaultErrorComponent` property + +- Type: `RouteComponent` +- Optional +- Defaults to `ErrorComponent` +- The default `errorComponent` a route should use if no error component is provided. + +### `defaultNotFoundComponent` property + +- Type: `NotFoundRouteComponent` +- Optional +- Defaults to `NotFound` +- The default `notFoundComponent` a route should use if no notFound component is + provided. + +### `defaultPendingComponent` property + +- Type: `RouteComponent` +- Optional +- The default `pendingComponent` a route should use if no pending component is provided. + +### `defaultPendingMs` property + +- Type: `number` +- Optional +- Defaults to `1000` +- The default `pendingMs` a route should use if no pendingMs is provided. + +### `defaultPendingMinMs` property + +- Type: `number` +- Optional +- Defaults to `500` +- The default `pendingMinMs` a route should use if no pendingMinMs is provided. + +### `defaultStaleTime` property + +- Type: `number` +- Optional +- Defaults to `0` +- The default `staleTime` a route should use if no staleTime is provided. + +### `defaultPreloadStaleTime` property + +- Type: `number` +- Optional +- Defaults to `30_000` ms (30 seconds) +- The default `preloadStaleTime` a route should use if no preloadStaleTime is provided. + +### `defaultPreloadGcTime` property + +- Type: `number` +- Optional +- Defaults to `routerOptions.defaultGcTime`, which defaults to 30 minutes. +- The default `preloadGcTime` a route should use if no preloadGcTime is provided. + +### `defaultGcTime` property + +- Type: `number` +- Optional +- Defaults to 30 minutes. +- The default `gcTime` a route should use if no gcTime is provided. + +### `defaultOnCatch` property + +- Type: `(error: Error, errorInfo: ErrorInfo) => void` +- Optional +- The default `onCatch` handler for errors caught by the Router ErrorBoundary + +### `defaultViewTransition` property + +- Type: `boolean | ViewTransitionOptions` +- Optional +- If `true`, route navigations will be called using `document.startViewTransition()`. +- If [`ViewTransitionOptions`](../ViewTransitionOptionsType.md), route navigations will + be called using `document.startViewTransition({update, types})` + where `types` will be the strings array passed with `ViewTransitionOptions["types"]`. + If the browser does not support viewTransition types, + the navigation will fall back to normal `document.startTransition()`, same as if `true` + was passed. +- If the browser does not support this api, this option will be ignored. +- +See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document/startViewTransition) +for more information on how this function works. +- +See [Google](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#view-transition-types) +for more information on viewTransition types + +### `defaultHashScrollIntoView` property + +- Type: `boolean | ScrollIntoViewOptions` +- Optional +- Defaults to `true` so the element with an id matching the hash will be scrolled into + view after the location is committed to history. +- If `false`, the element with an id matching the hash will not be scrolled into view + after the location is committed to history. +- If an object is provided, it will be passed to the `scrollIntoView` method as options. +- See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) for + more information on `ScrollIntoViewOptions`. + +### `caseSensitive` property + +- Type: `boolean` +- Optional +- Defaults to `false` +- If `true`, all routes will be matched as case-sensitive. + +### `basepath` property + +- Type: `string` +- Optional +- Defaults to `/` +- The basepath for then entire router. This is useful for mounting a router instance at a + subpath. + +### `context` property + +- Type: `any` +- Optional or required if the root route was created with [ + `createRootRouteWithContext()`](../createRootRouteWithContextFunction.md). +- The root context that will be provided to all routes in the route tree. This can be + used to provide a context to all routes in the tree without having to provide it to + each route individually. + +### `dehydrate` method + +- Type: `() => TDehydrated` +- Optional +- A function that will be called when the router is dehydrated. The return value of this + function will be serialized and stored in the router's dehydrated state. + +### `hydrate` method + +- Type: `(dehydrated: TDehydrated) => void` +- Optional +- A function that will be called when the router is hydrated. The return value of this + function will be serialized and stored in the router's dehydrated state. + +### `routeMasks` property + +- Type: `RouteMask[]` +- Optional +- An array of route masks that will be used to mask routes in the route tree. Route + masking is when you display a route at a different path than the one it is configured + to match, like a modal popup that when shared will unmask to the modal's content + instead of the modal's context. + +### `unmaskOnReload` property + +- Type: `boolean` +- Optional +- Defaults to `false` +- If `true`, route masks will, by default, be removed when the page is reloaded. This can + be overridden on a per-mask basis by setting the `unmaskOnReload` option on the mask, + or on a per-navigation basis by setting the `unmaskOnReload` option in the `Navigate` + options. + +### `Wrap` property + +- Type: `React.Component` +- Optional +- A component that will be used to wrap the entire router. This is useful for providing a + context to the entire router. Only non-DOM-rendering components like providers should + be used, anything else will cause a hydration error. + +**Example** + +```tsx +import { createRouter } from '@tanstack/react-router' + +const router = createRouter({ + // ... + Wrap: ({ children }) => { + return {children} + }, +}) +``` + +### `InnerWrap` property + +- Type: `React.Component` +- Optional +- A component that will be used to wrap the inner contents of the router. This is useful + for providing a context to the inner contents of the router where you also need access + to the router context and hooks. Only non-DOM-rendering components like providers + should be used, anything else will cause a hydration error. + +**Example** + +```tsx +import { createRouter } from '@tanstack/react-router' + +const router = createRouter({ + // ... + InnerWrap: ({ children }) => { + const routerState = useRouterState() + + return ( + + {children} + + ) + }, +}) +``` + +### `notFoundMode` property + +- Type: `'root' | 'fuzzy'` +- Optional +- Defaults to `'fuzzy'` +- This property controls how TanStack Router will handle scenarios where it cannot find a + route to match the current location. See + the [Not Found Errors guide](../../../guide/not-found-errors.md) for more information. + +### `notFoundRoute` property + +- **Deprecated** +- Type: `NotFoundRoute` +- Optional +- A route that will be used as the default not found route for every branch of the route + tree. This can be overridden on a per-branch basis by providing a not found route to + the `NotFoundRoute` option on the root route of the branch. + +### `trailingSlash` property + +- Type: `'always' | 'never' | 'preserve'` +- Optional +- Defaults to `never` +- Configures how trailing slashes are treated. `'always'` will add a trailing slash if + not present, `'never'` will remove the trailing slash if present and `'preserve'` will + not modify the trailing slash. + +### `pathParamsAllowedCharacters` property + +- Type: `Array<';' | ':' | '@' | '&' | '=' | '+' | '$' | ','>` +- Optional +- Configures which URI characters are allowed in path params that would ordinarily be + escaped by encodeURIComponent. + +### `defaultStructuralSharing` property + +- Type: `boolean` +- Optional +- Defaults to `false` +- Configures whether structural sharing is enabled by default for fine-grained selectors. +- See the [Render Optimizations guide](../../../guide/render-optimizations.md) for more + information. + +### `defaultRemountDeps` property + +- Type: + +```tsx +type defaultRemountDeps = (opts: RemountDepsOptions) => any + +interface RemountDepsOptions< + in out TRouteId, + in out TFullSearchSchema, + in out TAllParams, + in out TLoaderDeps, +> { + routeId: TRouteId + search: TFullSearchSchema + params: TAllParams + loaderDeps: TLoaderDeps +} +``` + +- Optional +- A default function that will be called to determine whether a route component shall be + remounted after navigation. If this function returns a different value than previously, + it will remount. +- The return value needs to be JSON serializable. +- By default, a route component will not be remounted if it stays active after a + navigation + +Example: +If you want to configure to remount all route components upon `params` change, use: + +```tsx +remountDeps: ({ params }) => params +``` + +# RouterState type + +The `RouterState` type represents shape of the internal state of the router. The Router's +internal state is useful, if you need to access certain internals of the router, such as +any pending matches, is the router in its loading state, etc. + +```tsx +type RouterState = { + status: 'pending' | 'idle' + isLoading: boolean + isTransitioning: boolean + matches: Array + pendingMatches: Array + location: ParsedLocation + resolvedLocation: ParsedLocation +} +``` + +## RouterState properties + +The `RouterState` type contains all of the properties that are available on the router +state. + +### `status` property + +- Type: `'pending' | 'idle'` +- The current status of the router. If the router is pending, it means that it is + currently loading a route or the router is still transitioning to the new route. + +### `isLoading` property + +- Type: `boolean` +- `true` if the router is currently loading a route or waiting for a route to finish + loading. + +### `isTransitioning` property + +- Type: `boolean` +- `true` if the router is currently transitioning to a new route. + +### `matches` property + +- Type: [`Array`](../RouteMatchType.md) +- An array of all of the route matches that have been resolved and are currently active. + +### `pendingMatches` property + +- Type: [`Array`](../RouteMatchType.md) +- An array of all of the route matches that are currently pending. + +### `location` property + +- Type: [`ParsedLocation`](../ParsedLocationType.md) +- The latest location that the router has parsed from the browser history. This location + may not be resolved and loaded yet. + +### `resolvedLocation` property + +- Type: [`ParsedLocation`](../ParsedLocationType.md) +- The location that the router has resolved and loaded. + +# Router type + +The `Router` type is used to describe a router instance. + +## `Router` properties and methods + +An instance of the `Router` has the following properties and methods: + +### `.update` method + +- Type: `(newOptions: RouterOptions) => void` +- Updates the router instance with new options. + +### `state` property + +- Type: [`RouterState`](../RouterStateType.md) +- The current state of the router. + +> ⚠️⚠️⚠️ **`router.state` is always up to date, but NOT REACTIVE. If you +use `router.state` in a component, the component will not re-render when the router state +changes. To get a reactive version of the router state, use +the [`useRouterState`](../useRouterStateHook.md) hook.** + +### `.subscribe` method + +- Type: + `(eventType: TType, fn: ListenerFn) => (event: RouterEvent) => void` +- Subscribes to a [`RouterEvent`](../RouterEventsType.md). +- Returns a function that can be used to unsubscribe from the event. +- The callback provided to the returned function will be called with the event that was + emitted. + +### `.matchRoutes` method + +- Type: + `(pathname: string, locationSearch: Record, opts?: { throwOnError?: boolean; }) => RouteMatch[]` +- Matches a pathname and search params against the router's route tree and returns an + array of route matches. +- If `opts.throwOnError` is `true`, any errors that occur during the matching process + will be thrown (in addition to being returned in the route match's `error` property). + +### `.cancelMatch` method + +- Type: `(matchId: string) => void` +- Cancels a route match that is currently pending by calling + `match.abortController.abort()`. + +### `.cancelMatches` method + +- Type: `() => void` +- Cancels all route matches that are currently pending by calling + `match.abortController.abort()` on each one. + +### `.buildLocation` method + +Builds a new parsed location object that can be used later to navigate to a new location. + +- Type: `(opts: BuildNextOptions) => ParsedLocation` +- Properties + - `from` + - Type: `string` + - Optional + - The path to navigate from. If not provided, the current path will be used. + - `to` + - Type: `string | number | null` + - Optional + - The path to navigate to. If `null`, the current path will be used. + - `params` + - Type: `true | Updater` + - Optional + - If `true`, the current params will be used. If a function is provided, it will + be called with the current params and the return value will be used. + - `search` + - Type: `true | Updater` + - Optional + - If `true`, the current search params will be used. If a function is provided, + it will be called with the current search params and the return value will be + used. + - `hash` + - Type: `true | Updater` + - Optional + - If `true`, the current hash will be used. If a function is provided, it will be + called with the current hash and the return value will be used. + - `state` + - Type: `true | NonNullableUpdater` + - Optional + - If `true`, the current state will be used. If a function is provided, it will + be called with the current state and the return value will be used. + - `mask` + - Type: `object` + - Optional + - Contains all of the same BuildNextOptions, with the addition of + `unmaskOnReload`. + - `unmaskOnReload` + - Type: `boolean` + - Optional + - If `true`, the route mask will be removed when the page is reloaded. This + can be overridden on a per-navigation basis by setting the `unmaskOnReload` + option in the `Navigate` options. + +### `.commitLocation` method + +Commits a new location object to the browser history. + +- Type + ```tsx + type commitLocation = ( + location: ParsedLocation & { + replace?: boolean + resetScroll?: boolean + hashScrollIntoView?: boolean | ScrollIntoViewOptions + ignoreBlocker?: boolean + }, + ) => Promise + ``` +- Properties + - `location` + - Type: [`ParsedLocation`](../ParsedLocationType.md) + - Required + - The location to commit to the browser history. + - `replace` + - Type: `boolean` + - Optional + - Defaults to `false`. + - If `true`, the location will be committed to the browser history using + `history.replace` instead of `history.push`. + - `resetScroll` + - Type: `boolean` + - Optional + - Defaults to `true` so that the scroll position will be reset to 0,0 after the + location is committed to the browser history. + - If `false`, the scroll position will not be reset to 0,0 after the location is + committed to history. + - `hashScrollIntoView` + - Type: `boolean | ScrollIntoViewOptions` + - Optional + - Defaults to `true` so the element with an id matching the hash will be scrolled + into view after the location is committed to history. + - If `false`, the element with an id matching the hash will not be scrolled into + view after the location is committed to history. + - If an object is provided, it will be passed to the `scrollIntoView` method as + options. + - + See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) + for more information on `ScrollIntoViewOptions`. + - `ignoreBlocker` + - Type: `boolean` + - Optional + - Defaults to `false`. + - If `true`, navigation will ignore any blockers that might prevent it. + +### `.navigate` method + +Navigates to a new location. + +- Type + ```tsx + type navigate = (options: NavigateOptions) => Promise + ``` + +### `.invalidate` method + +Invalidates route matches by forcing their `beforeLoad` and `load` functions to be called +again. + +- Type: + `(opts?: {filter?: (d: MakeRouteMatchUnion) => boolean, sync?: boolean, forcePending?: boolean }) => Promise` +- This is useful any time your loader data might be out of date or stale. For example, if + you have a route that displays a list of posts, and you have a loader function that + fetches the list of posts from an API, you might want to invalidate the route matches + for that route any time a new post is created so that the list of posts is always + up-to-date. +- if `filter` is not supplied, all matches will be invalidated +- if `filter` is supplied, only matches for which `filter` returns `true` will be + invalidated. +- if `sync` is true, the promise returned by this function will only resolve once all + loaders have finished. +- if `forcePending` is true, the invalidated matches will be put into `'pending'` state + regardless whether they are in `'error'` state or not. +- You might also want to invalidate the Router if you imperatively `reset` the router's + `CatchBoundary` to trigger loaders again. + +### `.clearCache` method + +Remove cached route matches. + +- Type: `(opts?: {filter?: (d: MakeRouteMatchUnion) => boolean}) => void` +- if `filter` is not supplied, all cached matches will be removed +- if `filter` is supplied, only matches for which `filter` returns `true` will be + removed. + +### `.load` method + +Loads all of the currently matched route matches and resolves when they are all loaded +and ready to be rendered. + +> ⚠️⚠️⚠️ **`router.load()` respects `route.staleTime` and will not forcefully reload a +route match if it is still fresh. If you need to forcefully reload a route match, +use `router.invalidate()` instead.** + +- Type: `(opts?: {sync?: boolean}) => Promise` +- if `sync` is true, the promise returned by this function will only resolve once all + loaders have finished. +- The most common use case for this method is to call it when doing SSR to ensure that + all of the critical data for the current route is loaded before attempting to stream or + render the application to the client. + +### `.preloadRoute` method + +Preloads all of the matches that match the provided `NavigateOptions`. + +> ⚠️⚠️⚠️ **Preloaded route matches are not stored long-term in the router state. They are +only stored until the next attempted navigation action.** + +- Type: `(opts?: NavigateOptions) => Promise` +- Properties + - `opts` + - Type: `NavigateOptions` + - Optional, defaults to the current location. + - The options that will be used to determine which route matches to preload. +- Returns + - A promise that resolves with an array of all of the route matches that were + preloaded. + +### `.loadRouteChunk` method + +Loads the JS chunk of the route. + +- Type: `(route: AnyRoute) => Promise` + +### `.matchRoute` method + +Matches a pathname and search params against the router's route tree and returns a route +match's params or false if no match was found. + +- Type: + `(dest: ToOptions, matchOpts?: MatchRouteOptions) => RouteMatch['params'] | false` +- Properties + - `dest` + - Type: `ToOptions` + - Required + - The destination to match against. + - `matchOpts` + - Type: `MatchRouteOptions` + - Optional + - Options that will be used to match the destination. +- Returns + - A route match's params if a match was found. + - `false` if no match was found. + +### `.dehydrate` method + +Dehydrates the router's critical state into a serializable object that can be sent to the +client in an initial request. + +- Type: `() => DehydratedRouter` +- Returns + - A serializable object that contains the router's critical state. + +### `.hydrate` method + +Hydrates the router's critical state from a serializable object that was sent from the +server in an initial request. + +- Type: `(dehydrated: DehydratedRouter) => void` +- Properties + - `dehydrated` + - Type: `DehydratedRouter` + - Required + - The dehydrated router state that was sent from the server. + +# ToMaskOptions type + +The `ToMaskOptions` type extends the [`ToOptions`](../ToOptionsType.md) type and +describes additional options available when using route masks. + +```tsx +type ToMaskOptions = ToOptions & { + unmaskOnReload?: boolean +} +``` + +- [`ToOptions`](../ToOptionsType.md) + +# ToOptions type + +The `ToOptions` type contains several properties that can be used to describe a router +destination. + +```tsx +type ToOptions = { + from?: ValidRoutePath | string + to?: ValidRoutePath | string + hash?: true | string | ((prev?: string) => string) + state?: true | HistoryState | ((prev: HistoryState) => HistoryState) +} & SearchParamOptions & + PathParamOptions + +type SearchParamOptions = { + search?: true | TToSearch | ((prev: TFromSearch) => TToSearch) +} + +type PathParamOptions = { + path?: true | Record | ((prev: TFromParams) => TToParams) +} +``` + +# UseMatchRouteOptions type + +The `UseMatchRouteOptions` type extends the [`ToOptions`](../ToOptionsType.md) type and +describes additional options available when using the [ +`useMatchRoute`](../useMatchRouteHook.md) hook. + +```tsx +export type UseMatchRouteOptions = ToOptions & MatchRouteOptions +``` + +- [`ToOptions`](../ToOptionsType.md) +- [`MatchRouteOptions`](../MatchRouteOptionsType.md) + +# ViewTransitionOptions type + +The `ViewTransitionOptions` type is used to define a +[viewTransition type](https://developer.chrome.com/docs/web-platform/view-transitions/same-document#view-transition-types). + +```tsx +interface ViewTransitionOptions { + types: Array +} +``` + +## ViewTransitionOptions properties + +The `ViewTransitionOptions` type accepts an object with a single property: + +### `types` property + +- Type: `Array` +- Required +- The types array that will be passed to the + `document.startViewTransition({update, types}) call`; + +# Await component + +The `Await` component is a component that suspends until the provided promise is resolved +or rejected. +This is only necessary for React 18. +If you are using React 19, you can use the `use()` hook instead. + +## Await props + +The `Await` component accepts the following props: + +### `props.promise` prop + +- Type: `Promise` +- Required +- The promise to await. + +### `props.children` prop + +- Type: `(result: T) => React.ReactNode` +- Required +- A function that will be called with the resolved value of the promise. + +## Await returns + +- Throws an error if the promise is rejected. +- Suspends (throws a promise) if the promise is pending. +- Returns the resolved value of a deferred promise if the promise is resolved using + `props.children` as the render function. + +## Examples + +```tsx +import { Await } from '@tanstack/react-router' + +function Component() { + const { deferredPromise } = route.useLoaderData() + + return ( + + {(data) =>
{JSON.stringify(data)}
} +
+ ) +} +``` + +# CatchBoundary component + +The `CatchBoundary` component is a component that catches errors thrown by its children, +renders an error component and optionally calls the `onCatch` callback. It also accepts a +`getResetKey` function that can be used to declaratively reset the component's state when +the key changes. + +## CatchBoundary props + +The `CatchBoundary` component accepts the following props: + +### `props.getResetKey` prop + +- Type: `() => string` +- Required +- A function that returns a string that will be used to reset the component's state when + the key changes. + +### `props.children` prop + +- Type: `React.ReactNode` +- Required +- The component's children to render when there is no error + +### `props.errorComponent` prop + +- Type: `React.ReactNode` +- Optional - [`default: ErrorComponent`](../errorComponentComponent.md) +- The component to render when there is an error. + +### `props.onCatch` prop + +- Type: `(error: any) => void` +- Optional +- A callback that will be called with the error that was thrown by the component's + children. + +## CatchBoundary returns + +- Returns the component's children if there is no error. +- Returns the `errorComponent` if there is an error. + +## Examples + +```tsx +import { CatchBoundary } from '@tanstack/react-router' + +function Component() { + return ( + 'reset'} + onCatch={(error) => console.error(error)} + > +
My Component
+
+ ) +} +``` + +# CatchNotFound Component + +The `CatchNotFound` component is a component that catches not-found errors thrown by its +children, renders a fallback component and optionally calls the `onCatch` callback. It +resets when the pathname changes. + +## CatchNotFound props + +The `CatchNotFound` component accepts the following props: + +### `props.children` prop + +- Type: `React.ReactNode` +- Required +- The component's children to render when there is no error + +### `props.fallback` prop + +- Type: `(error: NotFoundError) => React.ReactElement` +- Optional +- The component to render when there is an error + +### `props.onCatch` prop + +- Type: `(error: any) => void` +- Optional +- A callback that will be called with the error that was thrown by the component's + children + +## CatchNotFound returns + +- Returns the component's children if there is no error. +- Returns the `fallback` if there is an error. + +## Examples + +```tsx +import { CatchNotFound } from '@tanstack/react-router' + +function Component() { + return ( +

Not found error! {JSON.stringify(error)}

} + > + +
+ ) +} +``` + +# ClientOnly Component + +The `ClientOnly` component is used to render a components only in the client, without +breaking the server-side rendering due to hydration errors. It accepts a `fallback` prop +that will be rendered if the JS is not yet loaded in the client. + +## Props + +The `ClientOnly` component accepts the following props: + +### `props.fallback` prop + +The fallback component to render if the JS is not yet loaded in the client. + +### `props.children` prop + +The component to render if the JS is loaded in the client. + +## Returns + +- Returns the component's children if the JS is loaded in the client. +- Returns the `fallback` component if the JS is not yet loaded in the client. + +## Examples + +```tsx +// src/routes/dashboard.tsx +import { ClientOnly, createFileRoute } from '@tanstack/react-router' +import { + Charts, + FallbackCharts, +} from './charts-that-break-server-side-rendering' + +export const Route = createFileRoute('/dashboard')({ + component: Dashboard, + // ... other route options +}) + +function Dashboard() { + return ( +
+

Dashboard

+ }> + + +
+ ) +} +``` + +# createFileRoute function + +The `createFileRoute` function is a factory that can be used to create a file-based route +instance. This route instance can then be used to automatically generate a route tree +with the `tsr generate` and `tsr watch` commands. + +## createFileRoute options + +The `createFileRoute` function accepts a single argument of type `string` that represents +the `path` of the file that the route will be generated from. + +### `path` option + +- Type: `string` literal +- Required, but **automatically inserted and updated by the `tsr generate` + and `tsr watch` commands** +- The full path of the file that the route will be generated from + +## createFileRoute returns + +A new function that accepts a single argument of type [ +`RouteOptions`](../RouteOptionsType.md) that will be used to configure the file [ +`Route`](../RouteType.md) instance. + +> ⚠️ Note: For `tsr generate` and `tsr watch` to work properly, the file route instance +> must be exported from the file using the `Route` identifier. + +## Examples + +```tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + loader: () => { + return 'Hello World' + }, + component: IndexComponent, +}) + +function IndexComponent() { + const data = Route.useLoaderData() + return
{data}
+} +``` + +# createLazyFileRoute function + +The `createLazyFileRoute` function is used for creating a partial file-based route route +instance that is lazily loaded when matched. This route instance can only be used to +configure +the [non-critical properties](../../../guide/code-splitting.md#how-does-tanstack-router-split-code) +of the route, such as `component`, `pendingComponent`, `errorComponent`, and the +`notFoundComponent`. + +## createLazyFileRoute options + +The `createLazyFileRoute` function accepts a single argument of type `string` that +represents the `path` of the file that the route will be generated from. + +### `path` + +- Type: `string` +- Required, but **automatically inserted and updated by the `tsr generate` + and `tsr watch` commands** +- The full path of the file that the route will be generated from. + +### createLazyFileRoute returns + +A new function that accepts a single argument of partial of the type [ +`RouteOptions`](../RouteOptionsType.md) that will be used to configure the file [ +`Route`](../RouteType.md) instance. + +- Type: + +```tsx +Pick< + RouteOptions, + 'component' | 'pendingComponent' | 'errorComponent' | 'notFoundComponent' +> +``` + +- [`RouteOptions`](../RouteOptionsType.md) + +> ⚠️ Note: For `tsr generate` and `tsr watch` to work properly, the file route instance +> must be exported from the file using the `Route` identifier. + +### Examples + +```tsx +import { createLazyFileRoute } from '@tanstack/react-router' + +export const Route = createLazyFileRoute('/')({ + component: IndexComponent, +}) + +function IndexComponent() { + const data = Route.useLoaderData() + return
{data}
+} +``` + +# createLazyRoute function + +The `createLazyRoute` function is used for creating a partial code-based route route +instance that is lazily loaded when matched. This route instance can only be used to +configure +the [non-critical properties](../../../guide/code-splitting.md#how-does-tanstack-router-split-code) +of the route, such as `component`, `pendingComponent`, `errorComponent`, and the +`notFoundComponent`. + +## createLazyRoute options + +The `createLazyRoute` function accepts a single argument of type `string` that represents +the `id` of the route. + +### `id` + +- Type: `string` +- Required +- The route id of the route. + +### createLazyRoute returns + +A new function that accepts a single argument of partial of the type [ +`RouteOptions`](../RouteOptionsType.md) that will be used to configure the file [ +`Route`](../RouteType.md) instance. + +- Type: + +```tsx +Pick< + RouteOptions, + 'component' | 'pendingComponent' | 'errorComponent' | 'notFoundComponent' +> +``` + +- [`RouteOptions`](../RouteOptionsType.md) + +> ⚠️ Note: This route instance must be manually lazily loaded against its critical route +> instance using the `lazy` method returned by the `createRoute` function. + +### Examples + +```tsx +// src/route-pages/index.tsx +import { createLazyRoute } from '@tanstack/react-router' + +export const Route = createLazyRoute('/')({ + component: IndexComponent, +}) + +function IndexComponent() { + const data = Route.useLoaderData() + return
{data}
+} + +// src/routeTree.tsx +import { + createRootRouteWithContext, + createRoute, + Outlet, +} from '@tanstack/react-router' + +interface MyRouterContext { + foo: string +} + +const rootRoute = createRootRouteWithContext()({ + component: () => , +}) + +const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', +}).lazy(() => import('./route-pages/index').then((d) => d.Route)) + +export const routeTree = rootRoute.addChildren([indexRoute]) +``` + +# createRootRoute function + +The `createRootRoute` function returns a new root route instance. A root route instance +can then be used to create a route-tree. + +## createRootRoute options + +The options that will be used to configure the root route instance. + +- Type: + +```tsx +Omit< + RouteOptions, + | 'path' + | 'id' + | 'getParentRoute' + | 'caseSensitive' + | 'parseParams' + | 'stringifyParams' +> +``` + +- [`RouteOptions`](../RouteOptionsType.md) +- Optional + +## createRootRoute returns + +A new [`Route`](../RouteType.md) instance. + +## Examples + +```tsx +import { createRootRoute, createRouter, Outlet } from '@tanstack/react-router' + +const rootRoute = createRootRoute({ + component: () => , + // ... root route options +}) + +const routeTree = rootRoute.addChildren([ + // ... other routes +]) + +const router = createRouter({ + routeTree, +}) +``` + +# createRootRouteWithContext function + +The `createRootRouteWithContext` function is a helper function that can be used to create +a root route instance that requires a context type to be fulfilled when the router is +created. + +## createRootRouteWithContext generics + +The `createRootRouteWithContext` function accepts a single generic argument: + +### `TRouterContext` generic + +- Type: `TRouterContext` +- Optional, **but recommended**. +- The context type that will be required to be fulfilled when the router is created + +## createRootRouteWithContext returns + +- A factory function that can be used to create a new [ + `createRootRoute`](../createRootRouteFunction.md) instance. +- It accepts a single argument, the same as the [ + `createRootRoute`](../createRootRouteFunction.md) function. + +## Examples + +```tsx +import { + createRootRouteWithContext, + createRouter, +} from '@tanstack/react-router' +import { QueryClient } from '@tanstack/react-query' + +interface MyRouterContext { + queryClient: QueryClient +} + +const rootRoute = createRootRouteWithContext()({ + component: () => , + // ... root route options +}) + +const routeTree = rootRoute.addChildren([ + // ... other routes +]) + +const queryClient = new QueryClient() + +const router = createRouter({ + routeTree, + context: { + queryClient, + }, +}) +``` + +# createRoute function + +The `createRoute` function implements returns a [`Route`](../RouteType.md) instance. A +route instance can then be passed to a root route's children to create a route tree, +which is then passed to the router. + +## createRoute options + +- Type: [`RouteOptions`](../RouteOptionsType.md) +- Required +- The options that will be used to configure the route instance + +## createRoute returns + +A new [`Route`](../RouteType.md) instance. + +## Examples + +```tsx +import { createRoute } from '@tanstack/react-router' +import { rootRoute } from './__root' + +const Route = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + loader: () => { + return 'Hello World' + }, + component: IndexComponent, +}) + +function IndexComponent() { + const data = Route.useLoaderData() + return
{data}
+} +``` + +# createRouteMask function + +The `createRouteMask` function is a helper function that can be used to create a route +mask configuration that can be passed to the `RouterOptions.routeMasks` option. + +## createRouteMask options + +- Type: [`RouteMask`](../RouteMaskType.md) +- Required +- The options that will be used to configure the route mask + +## createRouteMask returns + +- A object with the type signature of [`RouteMask`](../RouteMaskType.md) that can be + passed to the `RouterOptions.routeMasks` option. + +## Examples + +```tsx +import { createRouteMask, createRouter } from '@tanstack/react-router' + +const photoModalToPhotoMask = createRouteMask({ + routeTree, + from: '/photos/$photoId/modal', + to: '/photos/$photoId', + params: true, +}) + +// Set up a Router instance +const router = createRouter({ + routeTree, + routeMasks: [photoModalToPhotoMask], +}) +``` + +# createRouter function + +The `createRouter` function accepts a [`RouterOptions`](../RouterOptionsType.md) object +and creates a new [`Router`](../RouterClass.md) instance. + +## createRouter options + +- Type: [`RouterOptions`](../RouterOptionsType.md) +- Required +- The options that will be used to configure the router instance. + +## createRouter returns + +- An instance of the [`Router`](../RouterType.md). + +## Examples + +```tsx +import { createRouter, RouterProvider } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +const router = createRouter({ + routeTree, + defaultPreload: 'intent', +}) + +export default function App() { + return +} +``` + +# DefaultGlobalNotFound component + +The `DefaultGlobalNotFound` component is a component that renders "Not Found" on the root +route when there is no other route that matches and a `notFoundComponent` is not +provided. + +## DefaultGlobalNotFound returns + +```tsx +

Not Found

+``` + +# defer function + +> [!CAUTION] +> You don't need to call `defer` manually anymore, Promises are handled automatically +> now. + +The `defer` function wraps a promise with a deferred state object that can be used to +inspect the promise's state. This deferred promise can then be passed to the [ +`useAwaited`](../useAwaitedHook.md) hook or the [``](../awaitComponent.md) +component for suspending until the promise is resolved or rejected. + +The `defer` function accepts a single argument, the `promise` to wrap with a deferred +state object. + +## defer options + +- Type: `Promise` +- Required +- The promise to wrap with a deferred state object. + +## defer returns + +- A promise that can be passed to the [`useAwaited`](../useAwaitedHook.md) hook or the [ + ``](../awaitComponent.md) component. + +## Examples + +```tsx +import { defer } from '@tanstack/react-router' + +const route = createRoute({ + loader: () => { + const deferredPromise = defer(fetch('/api/data')) + return { deferredPromise } + }, + component: MyComponent, +}) + +function MyComponent() { + const { deferredPromise } = Route.useLoaderData() + + const data = useAwaited({ promise: deferredPromise }) + + // or + + return ( + + {(data) =>
{JSON.stringify(data)}
} +
+ ) +} +``` + +# ErrorComponent component + +The `ErrorComponent` component is a component that renders an error message and +optionally the error's message. + +## ErrorComponent props + +The `ErrorComponent` component accepts the following props: + +### `props.error` prop + +- Type: `TError` (defaults to `Error`) +- The error that was thrown by the component's children + +### `props.info` prop + +- Type: `{ componentStack: string }` +- Optional +- Additional information about where the error was thrown, such as the React component + stack trace. + +### `props.reset` prop + +- Type: `() => void` +- A function to programmatically reset the error state + +## ErrorComponent returns + +- Returns a formatted error message with the error's message if it exists. +- The error message can be toggled by clicking the "Show Error" button. +- By default, the error message will be shown in development. + +# getRouteApi function + +The `getRouteApi` function provides type-safe version of common hooks like `useParams`, +`useSearch`, `useRouteContext`, `useNavigate`, `useLoaderData`, and `useLoaderDeps` that +are pre-bound to a specific route ID and corresponding registered route types. + +## getRouteApi options + +The `getRouteApi` function accepts a single argument, a `routeId` string literal. + +### `routeId` option + +- Type: `string` +- Required +- The route ID to which the [`RouteApi`](../RouteApiClass.md) instance will be bound + +## getRouteApi returns + +- An instance of the [`RouteApi`](../RouteApiType.md) that is pre-bound to the route ID + that the `getRouteApi` function was called with. + +## Examples + +```tsx +import { getRouteApi } from '@tanstack/react-router' + +const routeApi = getRouteApi('/posts') + +export function PostsPage() { + const posts = routeApi.useLoaderData() + // ... +} +``` + +# HistoryState interface + +The `HistoryState` interface is an interface exported by the `history` package that +describes the shape of the state object that can be used in conjunction with the +`history` package and the `window.location` API. + +You can extend this interface to add additional properties to the state object across +your application. + +```tsx +// src/main.tsx +declare module '@tanstack/react-router' { + // ... + + interface HistoryState { + additionalRequiredProperty: number + additionalProperty?: string + } +} +``` + +# isNotFound function + +The `isNotFound` function can be used to determine if an object is a [ +`NotFoundError`](../NotFoundErrorType.md) object. + +## isNotFound options + +The `isNotFound` function accepts a single argument, an `input`. + +### `input` option + +- Type: `unknown` +- Required +- An object to check if it is a [`NotFoundError`](../NotFoundErrorType.md). + +## isNotFound returns + +- Type: `boolean` +- `true` if the object is a [`NotFoundError`](../NotFoundErrorType.md). +- `false` if the object is not a [`NotFoundError`](../NotFoundErrorType.md). + +## Examples + +```tsx +import { isNotFound } from '@tanstack/react-router' + +function somewhere(obj: unknown) { + if (isNotFound(obj)) { + // ... + } +} +``` + +# isRedirect function + +The `isRedirect` function can be used to determine if an object is a redirect object. + +## isRedirect options + +The `isRedirect` function accepts a single argument, an `input`. + +#### `input` + +- Type: `unknown` +- Required +- An object to check if it is a redirect object + +## isRedirect returns + +- Type: `boolean` +- `true` if the object is a redirect object +- `false` if the object is not a redirect object + +## Examples + +```tsx +import { isRedirect } from '@tanstack/react-router' + +function somewhere(obj: unknown) { + if (isRedirect(obj)) { + // ... + } +} +``` + +# lazyRouteComponent function + +> [!IMPORTANT] +> If you are using file-based routing, it's recommended to use the `createLazyFileRoute` +> function instead. + +The `lazyRouteComponent` function can be used to create a one-off code-split route +component that can be preloaded using a `component.preload()` method. + +## lazyRouteComponent options + +The `lazyRouteComponent` function accepts two arguments: + +### `importer` option + +- Type: `() => Promise` +- Required +- A function that returns a promise that resolves to an object that contains the + component to be loaded. + +### `exportName` option + +- Type: `string` +- Optional +- The name of the component to be loaded from the imported object. Defaults to + `'default'`. + +## lazyRouteComponent returns + +- A `React.lazy` component that can be preloaded using a `component.preload()` method. + +## Examples + +```tsx +import { lazyRouteComponent } from '@tanstack/react-router' + +const route = createRoute({ + path: '/posts/$postId', + component: lazyRouteComponent(() => import('./Post')), // default export +}) + +// or + +const route = createRoute({ + path: '/posts/$postId', + component: lazyRouteComponent( + () => import('./Post'), + 'PostByIdPageComponent', // named export + ), +}) +``` + +# Link component + +The `Link` component is a component that can be used to create a link that can be used to +navigate to a new location. This includes changes to the pathname, search params, hash, +and location state. + +## Link props + +The `Link` component accepts the following props: + +### `...props` + +- Type: `LinkProps & React.RefAttributes` +- [`LinkProps`](../LinkPropsType.md) + +## Link returns + +An anchor element that can be used to navigate to a new location. + +## Examples + +```tsx +import { Link } from '@tanstack/react-router' + +function Component() { + return ( + ({ ...prev, foo: 'bar' })} + > + Click me + + ) +} +``` + +# Link options + +`linkOptions` is a function which type checks an object literal with the intention of +being used for `Link`, `navigate` or `redirect` + +## linkOptions props + +The `linkOptions` accepts the following option: + +### `...props` + +- Type: `LinkProps & React.RefAttributes` +- [`LinkProps`](../LinkPropsType.md) + +## `linkOptions` returns + +An object literal with the exact type inferred from the input + +## Examples + +```tsx +const userLinkOptions = linkOptions({ + to: '/dashboard/users/user', + search: { + usersView: { + sortBy: 'email', + filterBy: 'filter', + }, + userId: 0, + }, +}) + +function DashboardComponent() { + return +} +``` + +# MatchRoute component + +A component version of the `useMatchRoute` hook. It accepts the same options as the +`useMatchRoute` with additional props to aid in conditional rendering. + +## MatchRoute props + +The `MatchRoute` component accepts the same options as the `useMatchRoute` hook with +additional props to aid in conditional rendering. + +### `...props` prop + +- Type: [`UseMatchRouteOptions`](../UseMatchRouteOptionsType.md) + +### `children` prop + +- Optional +- `React.ReactNode` + - The component that will be rendered if the route is matched. +- `((params: TParams | false) => React.ReactNode)` + - A function that will be called with the matched route's params or `false` if no + route was matched. This can be useful for components that need to always render, + but render different props based on a route match or not. + +## MatchRoute returns + +Either the `children` prop or the return value of the `children` function. + +## Examples + +```tsx +import { MatchRoute } from '@tanstack/react-router' + +function Component() { + return ( +
+ + {(match) => } + +
+ ) +} +``` + +# Navigate component + +The `Navigate` component is a component that can be used to navigate to a new location +when rendered. This includes changes to the pathname, search params, hash, and location +state. The underlying navigation will happen inside of a `useEffect` hook when +successfully rendered. + +## Navigate props + +The `Navigate` component accepts the following props: + +### `...options` + +- Type: [`NavigateOptions`](../NavigateOptionsType.md) + +## Navigate returns + +- `null` + +# notFound function + +The `notFound` function returns a new `NotFoundError` object that can be either returned +or thrown from places like a Route's `beforeLoad` or `loader` callbacks to trigger the +`notFoundComponent`. + +## notFound options + +The `notFound` function accepts a single optional argument, the `options` to create the +not-found error object. + +- Type: [`Partial`](../NotFoundErrorType.md) +- Optional + +## notFound returns + +- If the `throw` property is `true` in the `options` object, the `NotFoundError` object + will be thrown from within the function call. +- If the `throw` property is `false | undefined` in the `options` object, the + `NotFoundError` object will be returned. + +## Examples + +```tsx +import { notFound, createFileRoute, rootRouteId } from '@tanstack/react-router' + +const Route = new createFileRoute('/posts/$postId')({ + // throwing a not-found object + loader: ({ context: { post } }) => { + if (!post) { + throw notFound() + } + }, + // or if you want to show a not-found on the whole page + loader: ({ context: { team } }) => { + if (!team) { + throw notFound({ routeId: rootRouteId }) + } + }, + // ... other route options +}) +``` + +# Outlet component + +The `Outlet` component is a component that can be used to render the next child route of +a parent route. + +## Outlet props + +The `Outlet` component does not accept any props. + +## Outlet returns + +- If matched, the child route match's `component`/`errorComponent`/`pendingComponent`/ + `notFoundComponent`. +- If not matched, `null`. + +# redirect function + +The `redirect` function returns a new `Redirect` object that can be either returned or +thrown from places like a Route's `beforeLoad` or `loader` callbacks to trigger +_redirect_ to a new location. + +## redirect options + +The `redirect` function accepts a single argument, the `options` to determine the +redirect behavior. + +- Type: [`Redirect`](../RedirectType.md) +- Required + +## redirect returns + +- If the `throw` property is `true` in the `options` object, the `Redirect` object will + be thrown from within the function call. +- If the `throw` property is `false | undefined` in the `options` object, the `Redirect` + object will be returned. + +## Examples + +```tsx +import { redirect } from '@tanstack/react-router' + +const route = createRoute({ + // throwing a redirect object + loader: () => { + if (!user) { + throw redirect({ + to: '/login', + }) + } + }, + // or forcing `redirect` to throw itself + loader: () => { + if (!user) { + redirect({ + to: '/login', + throw: true, + }) + } + }, + // ... other route options +}) +``` + +# Search middleware to retain search params + +`retainSearchParams` is a search middleware that allows to keep search params. + +## retainSearchParams props + +The `retainSearchParams` either accepts `true` or a list of keys of those search params +that shall be retained. +If `true` is passed in, all search params will be retained. + +## Examples + +```tsx +import { z } from 'zod' +import { createRootRoute, retainSearchParams } from '@tanstack/react-router' +import { zodValidator } from '@tanstack/zod-adapter' + +const searchSchema = z.object({ + rootValue: z.string().optional(), +}) + +export const Route = createRootRoute({ + validateSearch: zodValidator(searchSchema), + search: { + middlewares: [retainSearchParams(['rootValue'])], + }, +}) +``` + +```tsx +import { z } from 'zod' +import { createFileRoute, retainSearchParams } from '@tanstack/react-router' +import { zodValidator } from '@tanstack/zod-adapter' + +const searchSchema = z.object({ + one: z.string().optional(), + two: z.string().optional(), +}) + +export const Route = createFileRoute('/')({ + validateSearch: zodValidator(searchSchema), + search: { + middlewares: [retainSearchParams(true)], + }, +}) +``` + +# rootRouteWithContext function + +> [!CAUTION] +> This function is deprecated and will be removed in the next major version of TanStack +> Router. +> Please use the [`createRootRouteWithContext`](../createRootRouteWithContextFunction.md) +> function instead. + +The `rootRouteWithContext` function is a helper function that can be used to create a +root route instance that requires a context type to be fulfilled when the router is +created. + +## rootRouteWithContext generics + +The `rootRouteWithContext` function accepts a single generic argument: + +### `TRouterContext` generic + +- Type: `TRouterContext` +- Optional, **but recommended**. +- The context type that will be required to be fulfilled when the router is created + +## rootRouteWithContext returns + +- A factory function that can be used to create a new [ + `createRootRoute`](../createRootRouteFunction.md) instance. +- It accepts a single argument, the same as the [ + `createRootRoute`](../createRootRouteFunction.md) function. + +## Examples + +```tsx +import { rootRouteWithContext, createRouter } from '@tanstack/react-router' +import { QueryClient } from '@tanstack/react-query' + +interface MyRouterContext { + queryClient: QueryClient +} + +const rootRoute = rootRouteWithContext()({ + component: () => , + // ... root route options +}) + +const routeTree = rootRoute.addChildren([ + // ... other routes +]) + +const queryClient = new QueryClient() + +const router = createRouter({ + routeTree, + context: { + queryClient, + }, +}) +``` + +# Search middleware to strip search params + +`stripSearchParams` is a search middleware that allows to remove search params. + +## stripSearchParams props + +`stripSearchParams` accepts one of the following inputs: + +- `true`: if the search schema has no required params, `true` can be used to strip all + search params +- a list of keys of those search params that shall be removed; only keys of optional + search params are allowed. +- an object that conforms to the partial input search schema. The search params are + compared against the values of this object; if the value is deeply equal, it will be + removed. This is especially useful to strip out default search params. + +## Examples + +```tsx +import { z } from 'zod' +import { createFileRoute, stripSearchParams } from '@tanstack/react-router' +import { zodValidator } from '@tanstack/zod-adapter' + +const defaultValues = { + one: 'abc', + two: 'xyz', +} + +const searchSchema = z.object({ + one: z.string().default(defaultValues.one), + two: z.string().default(defaultValues.two), +}) + +export const Route = createFileRoute('/')({ + validateSearch: zodValidator(searchSchema), + search: { + // strip default values + middlewares: [stripSearchParams(defaultValues)], + }, +}) +``` + +```tsx +import { z } from 'zod' +import { createRootRoute, stripSearchParams } from '@tanstack/react-router' +import { zodValidator } from '@tanstack/zod-adapter' + +const searchSchema = z.object({ + hello: z.string().default('world'), + requiredParam: z.string(), +}) + +export const Route = createRootRoute({ + validateSearch: zodValidator(searchSchema), + search: { + // always remove `hello` + middlewares: [stripSearchParams(['hello'])], + }, +}) +``` + +```tsx +import { z } from 'zod' +import { createFileRoute, stripSearchParams } from '@tanstack/react-router' +import { zodValidator } from '@tanstack/zod-adapter' + +const searchSchema = z.object({ + one: z.string().default('abc'), + two: z.string().default('xyz'), +}) + +export const Route = createFileRoute('/')({ + validateSearch: zodValidator(searchSchema), + search: { + // remove all search params + middlewares: [stripSearchParams(true)], + }, +}) +``` + +# useAwaited hook + +The `useAwaited` method is a hook that suspends until the provided promise is resolved or +rejected. + +## useAwaited options + +The `useAwaited` hook accepts a single argument, an `options` object. + +### `options.promise` option + +- Type: `Promise` +- Required +- The deferred promise to await. + +## useAwaited returns + +- Throws an error if the promise is rejected. +- Suspends (throws a promise) if the promise is pending. +- Returns the resolved value of a deferred promise if the promise is resolved. + +## Examples + +```tsx +import { useAwaited } from '@tanstack/react-router' + +function Component() { + const { deferredPromise } = route.useLoaderData() + + const data = useAwaited({ promise: myDeferredPromise }) + // ... +} +``` + +# useBlocker hook + +The `useBlocker` method is a hook +that [blocks navigation](../../../guide/navigation-blocking.md) when a condition is met. + +> ⚠️ The following new `useBlocker` API is currently _experimental_. + +## useBlocker options + +The `useBlocker` hook accepts a single _required_ argument, an option object: + +### `options.shouldBlockFn` option + +- Required +- Type: `ShouldBlockFn` +- This function should return a `boolean` or a `Promise` that tells the blocker + if it should block the current navigation +- The function has the argument of type `ShouldBlockFnArgs` passed to it, which tells you + information about the current and next route and the action performed +- Think of this function as telling the router if it should block the navigation, so + returning `true` mean that it should block the navigation and `false` meaning that it + should be allowed + +```ts +interface ShouldBlockFnLocation<...> { + routeId: TRouteId + fullPath: TFullPath + pathname: string + params: TAllParams + search: TFullSearchSchema +} + +type ShouldBlockFnArgs = { + current: ShouldBlockFnLocation + next: ShouldBlockFnLocation + action: HistoryAction +} +``` + +### `options.disabled` option + +- Optional - defaults to `false` +- Type: `boolean` +- Specifies if the blocker should be entirely disabled or not + +### `options.enableBeforeUnload` option + +- Optional - defaults to `true` +- Type: `boolean | (() => boolean)` +- Tell the blocker to sometimes or always block the browser `beforeUnload` event or not + +### `options.withResolver` option + +- Optional - defaults to `false` +- Type: `boolean` +- Specify if the resolver returned by the hook should be used or whether your + `shouldBlockFn` function itself resolves the blocking + +### `options.blockerFn` option (⚠️ deprecated) + +- Optional +- Type: `BlockerFn` +- The function that returns a `boolean` or `Promise` indicating whether to allow + navigation. + +### `options.condition` option (⚠️ deprecated) + +- Optional - defaults to `true` +- Type: `boolean` +- A navigation attempt is blocked when this condition is `true`. + +## useBlocker returns + +An object with the controls to allow manual blocking and unblocking of navigation. + +- `status` - A string literal that can be either `'blocked'` or `'idle'` +- `next` - When status is `blocked`, a type narrrowable object that contains information + about the next location +- `current` - When status is `blocked`, a type narrrowable object that contains + information about the current location +- `action` - When status is `blocked`, a `HistoryAction` string that shows the action + that triggered the navigation +- `proceed` - When status is `blocked`, a function that allows navigation to continue +- `reset` - When status is `blocked`, a function that cancels navigation (`status` will + be reset to `'idle'`) + +or + +`void` when `withResolver` is `false` + +## Examples + +Two common use cases for the `useBlocker` hook are: + +### Basic usage + +```tsx +import { useBlocker } from '@tanstack/react-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = useState(false) + + useBlocker({ + shouldBlockFn: () => formIsDirty, + }) + + // ... +} +``` + +### Custom UI + +```tsx +import { useBlocker } from '@tanstack/react-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = useState(false) + + const { proceed, reset, status, next } = useBlocker({ + shouldBlockFn: () => formIsDirty, + withResolver: true, + }) + + // ... + + return ( + <> + {/* ... */} + {status === 'blocked' && ( +
+

You are navigating to {next.pathname}

+

Are you sure you want to leave?

+ + +
+ )} + +} +``` + +### Conditional blocking + +```tsx +import { useBlocker } from '@tanstack/react-router' + +function MyComponent() { + const { proceed, reset, status } = useBlocker({ + shouldBlockFn: ({ next }) => { + return !next.pathname.includes('step/') + }, + withResolver: true, + }) + + // ... + + return ( + <> + {/* ... */} + {status === 'blocked' && ( +
+

Are you sure you want to leave?

+ + +
+ )} + + ) +} +``` + +### Without resolver + +```tsx +import { useBlocker } from '@tanstack/react-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = useState(false) + + useBlocker({ + shouldBlockFn: ({ next }) => { + if (next.pathname.includes('step/')) { + return false + } + + const shouldLeave = confirm('Are you sure you want to leave?') + return !shouldLeave + }, + }) + + // ... +} +``` + +### Type narrowing + +```tsx +import { useBlocker } from '@tanstack/react-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = useState(false) + + // block going from editor-1 to /foo/123?hello=world + const { proceed, reset, status } = useBlocker({ + shouldBlockFn: ({ current, next }) => { + if ( + current.routeId === '/editor-1' && + next.fullPath === '/foo/$id' && + next.params.id === '123' && + next.search.hello === 'world' + ) { + return true + } + return false + }, + enableBeforeUnload: false, + withResolver: true, + }) + + // ... +} +``` + +# useCanGoBack hook + +The `useCanGoBack` hook returns a boolean representing if the router history can safely +go back without exiting the application. + +> ⚠️ The following new `useCanGoBack` API is currently _experimental_. + +## useCanGoBack returns + +- If the router history is not at index `0`, `true`. +- If the router history is at index `0`, `false`. + +## Limitations + +The router history index is reset after a navigation with [ +`reloadDocument`](../NavigateOptionsType.md#reloaddocument) set as `true`. This causes +the router history to consider the new location as the initial one and will cause +`useCanGoBack` to return `false`. + +## Examples + +### Showing a back button + +```tsx +import { useRouter, useCanGoBack } from '@tanstack/react-router' + +function Component() { + const router = useRouter() + const canGoBack = useCanGoBack() + + return ( +
+ {canGoBack ? ( + + ) : null} + + {/* ... */} +
+ ) +} +``` + +# useChildMatches hook + +The `useChildMatches` hook returns all of the child [`RouteMatch`](../RouteMatchType.md) +objects from the closest match down to the leaf-most match. **It does not include the +current match, which can be obtained using the `useMatch` hook.** + +> [!IMPORTANT] +> If the router has pending matches and they are showing their pending component +> fallbacks, `router.state.pendingMatches` will used instead of `router.state.matches`. + +## useChildMatches options + +The `useChildMatches` hook accepts a single _optional_ argument, an `options` object. + +### `opts.select` option + +- Optional +- `(matches: RouteMatch[]) => TSelected` +- If supplied, this function will be called with the route matches and the return value + will be returned from `useChildMatches`. This value will also be used to determine if + the hook should re-render its parent component using shallow equality checks. + +### `opts.structuralSharing` option + +- Type: `boolean` +- Optional +- Configures whether structural sharing is enabled for the value returned by `select`. +- See the [Render Optimizations guide](../../../guide/render-optimizations.md) for more + information. + +## useChildMatches returns + +- If a `select` function is provided, the return value of the `select` function. +- If no `select` function is provided, an array of [`RouteMatch`](../RouteMatchType.md) + objects. + +## Examples + +```tsx +import { useChildMatches } from '@tanstack/react-router' + +function Component() { + const childMatches = useChildMatches() + // ... +} +``` + +# useLinkProps hook + +The `useLinkProps` hook that takes an object as its argument and returns a +`React.AnchorHTMLAttributes` props object. These props can then be +safely applied to an anchor element to create a link that can be used to navigate to the +new location. This includes changes to the pathname, search params, hash, and location +state. + +## useLinkProps options + +```tsx +type UseLinkPropsOptions = ActiveLinkOptions & + React.AnchorHTMLAttributes +``` + +- [`ActiveLinkOptions`](../ActiveLinkOptionsType.md) +- The `useLinkProps` options are used to build a [`LinkProps`](../LinkPropsType.md) + object. +- It also extends the `React.AnchorHTMLAttributes` type, so that any + additional props that are passed to the `useLinkProps` hook will be merged with the [ + `LinkProps`](../LinkPropsType.md) object. + +## useLinkProps returns + +- A `React.AnchorHTMLAttributes` object that can be applied to an + anchor element to create a link that can be used to navigate to the new location + +# useLoaderData hook + +The `useLoaderData` hook returns the loader data from the closest [ +`RouteMatch`](../RouteMatchType.md) in the component tree. + +## useLoaderData options + +The `useLoaderData` hook accepts an `options` object. + +### `opts.from` option + +- Type: `string` +- The route id of the closest parent match +- Optional, but recommended for full type safety. +- If `opts.strict` is `true`, TypeScript will warn for this option if it is not provided. +- If `opts.strict` is `false`, TypeScript will provided loosened types for the returned + loader data. + +### `opts.strict` option + +- Type: `boolean` +- Optional - `default: true` +- If `false`, the `opts.from` option will be ignored and types will be loosened to to + reflect the shared types of all possible loader data. + +### `opts.select` option + +- Optional +- `(loaderData: TLoaderData) => TSelected` +- If supplied, this function will be called with the loader data and the return value + will be returned from `useLoaderData`. This value will also be used to determine if the + hook should re-render its parent component using shallow equality checks. + +### `opts.structuralSharing` option + +- Type: `boolean` +- Optional +- Configures whether structural sharing is enabled for the value returned by `select`. +- See the [Render Optimizations guide](../../../guide/render-optimizations.md) for more + information. + +## useLoaderData returns + +- If a `select` function is provided, the return value of the `select` function. +- If no `select` function is provided, the loader data or a loosened version of the + loader data if `opts.strict` is `false`. + +## Examples + +```tsx +import { useLoaderData } from '@tanstack/react-router' + +function Component() { + const loaderData = useLoaderData({ from: '/posts/$postId' }) + // ^? { postId: string, body: string, ... } + // ... +} +``` + +# useLoaderDeps hook + +The `useLoaderDeps` hook is a hook that returns an object with the dependencies that are +used to trigger the `loader` for a given route. + +## useLoaderDepsHook options + +The `useLoaderDepsHook` hook accepts an `options` object. + +### `opts.from` option + +- Type: `string` +- Required +- The RouteID or path to get the loader dependencies from. + +### `opts.select` option + +- Type: `(deps: TLoaderDeps) => TSelected` +- Optional +- If supplied, this function will be called with the loader dependencies object and the + return value will be returned from `useLoaderDeps`. + +### `opts.structuralSharing` option + +- Type: `boolean` +- Optional +- Configures whether structural sharing is enabled for the value returned by `select`. +- See the [Render Optimizations guide](../../../guide/render-optimizations.md) for more + information. + +## useLoaderDeps returns + +- An object of the loader dependencies or `TSelected` if a `select` function is provided. + +## Examples + +```tsx +import { useLoaderDeps } from '@tanstack/react-router' + +const routeApi = getRouteApi('/posts/$postId') + +function Component() { + const deps = useLoaderDeps({ from: '/posts/$postId' }) + + // OR + + const routeDeps = routeApi.useLoaderDeps() + + // OR + + const postId = useLoaderDeps({ + from: '/posts', + select: (deps) => deps.view, + }) + + // ... +} +``` + +# useLocation hook + +The `useLocation` method is a hook that returns the current [ +`location`](../ParsedLocationType.md) object. This hook is useful for when you want to +perform some side effect whenever the current location changes. + +## useLocation options + +The `useLocation` hook accepts an optional `options` object. + +### `opts.select` option + +- Type: `(state: ParsedLocationType) => TSelected` +- Optional +- If supplied, this function will be called with the [ + `location`](../ParsedLocationType.md) object and the return value will be returned from + `useLocation`. + +## useLocation returns + +- The current [`location`](../ParsedLocationType.md) object or `TSelected` if a `select` + function is provided. + +## Examples + +```tsx +import { useLocation } from '@tanstack/react-router' + +function Component() { + const location = useLocation() + // ^ ParsedLocation + + // OR + + const pathname = useLocation({ + select: (location) => location.pathname, + }) + // ^ string + + // ... +} +``` + +# useMatch hook + +The `useMatch` hook returns a [`RouteMatch`](../RouteMatchType.md) in the component tree. +The raw route match contains all of the information about a route match in the router and +also powers many other hooks under the hood like `useParams`, `useLoaderData`, +`useRouteContext`, and `useSearch`. + +## useMatch options + +The `useMatch` hook accepts a single argument, an `options` object. + +### `opts.from` option + +- Type: `string` +- The route id of a match +- Optional, but recommended for full type safety. +- If `opts.strict` is `true`, `from` is required and TypeScript will warn for this option + if it is not provided. +- If `opts.strict` is `false`, `from` must not be set and TypeScript will provided + loosened types for the returned [`RouteMatch`](../RouteMatchType.md). + +### `opts.strict` option + +- Type: `boolean` +- Optional +- `default: true` +- If `false`, the `opts.from` must not be set and types will be loosened to + `Partial` to reflect the shared types of all matches. + +### `opts.select` option + +- Optional +- `(match: RouteMatch) => TSelected` +- If supplied, this function will be called with the route match and the return value + will be returned from `useMatch`. This value will also be used to determine if the hook + should re-render its parent component using shallow equality checks. + +### `opts.structuralSharing` option + +- Type: `boolean` +- Optional +- Configures whether structural sharing is enabled for the value returned by `select`. +- See the [Render Optimizations guide](../../../guide/render-optimizations.md) for more + information. + +### `opts.shouldThrow` option + +- Type: `boolean` +- Optional +- `default: true` +- If `false`,`useMatch` will not throw an invariant exception in case a match was not + found in the currently rendered matches; in this case, it will return `undefined`. + +## useMatch returns + +- If a `select` function is provided, the return value of the `select` function. +- If no `select` function is provided, the [`RouteMatch`](../RouteMatchType.md) object or + a loosened version of the `RouteMatch` object if `opts.strict` is `false`. + +## Examples + +### Accessing a route match + +```tsx +import { useMatch } from '@tanstack/react-router' + +function Component() { + const match = useMatch({ from: '/posts/$postId' }) + // ^? strict match for RouteMatch + // ... +} +``` + +### Accessing the root route's match + +```tsx +import { + useMatch, + rootRouteId, // <<<< use this token! +} from '@tanstack/react-router' + +function Component() { + const match = useMatch({ from: rootRouteId }) + // ^? strict match for RouteMatch + // ... +} +``` + +### Checking if a specific route is currently rendered + +```tsx +import { useMatch } from '@tanstack/react-router' + +function Component() { + const match = useMatch({ from: '/posts', shouldThrow: false }) + // ^? RouteMatch | undefined + if (match !== undefined) { + // ... + } +} +``` + +# useMatchRoute hook + +The `useMatchRoute` hook is a hook that returns a `matchRoute` function that can be used +to match a route against either the current or pending location. + +## useMatchRoute returns + +- A `matchRoute` function that can be used to match a route against either the current or + pending location. + +## matchRoute function + +The `matchRoute` function is a function that can be used to match a route against either +the current or pending location. + +### matchRoute function options + +The `matchRoute` function accepts a single argument, an `options` object. + +- Type: [`UseMatchRouteOptions`](../UseMatchRouteOptionsType.md) + +### matchRoute function returns + +- The matched route's params or `false` if no route was matched + +## Examples + +```tsx +import { useMatchRoute } from '@tanstack/react-router' + +// Current location: /posts/123 +function Component() { + const matchRoute = useMatchRoute() + const params = matchRoute({ to: '/posts/$postId' }) + // ^ { postId: '123' } +} + +// Current location: /posts/123 +function Component() { + const matchRoute = useMatchRoute() + const params = matchRoute({ to: '/posts' }) + // ^ false +} + +// Current location: /posts/123 +function Component() { + const matchRoute = useMatchRoute() + const params = matchRoute({ to: '/posts', fuzzy: true }) + // ^ {} +} + +// Current location: /posts +// Pending location: /posts/123 +function Component() { + const matchRoute = useMatchRoute() + const params = matchRoute({ to: '/posts/$postId', pending: true }) + // ^ { postId: '123' } +} + +// Current location: /posts/123/foo/456 +function Component() { + const matchRoute = useMatchRoute() + const params = matchRoute({ to: '/posts/$postId/foo/$fooId' }) + // ^ { postId: '123', fooId: '456' } +} + +// Current location: /posts/123/foo/456 +function Component() { + const matchRoute = useMatchRoute() + const params = matchRoute({ + to: '/posts/$postId/foo/$fooId', + params: { postId: '123' }, + }) + // ^ { postId: '123', fooId: '456' } +} + +// Current location: /posts/123/foo/456 +function Component() { + const matchRoute = useMatchRoute() + const params = matchRoute({ + to: '/posts/$postId/foo/$fooId', + params: { postId: '789' }, + }) + // ^ false +} + +// Current location: /posts/123/foo/456 +function Component() { + const matchRoute = useMatchRoute() + const params = matchRoute({ + to: '/posts/$postId/foo/$fooId', + params: { fooId: '456' }, + }) + // ^ { postId: '123', fooId: '456' } +} + +// Current location: /posts/123/foo/456 +function Component() { + const matchRoute = useMatchRoute() + const params = matchRoute({ + to: '/posts/$postId/foo/$fooId', + params: { postId: '123', fooId: '456' }, + }) + // ^ { postId: '123', fooId: '456' } +} + +// Current location: /posts/123/foo/456 +function Component() { + const matchRoute = useMatchRoute() + const params = matchRoute({ + to: '/posts/$postId/foo/$fooId', + params: { postId: '789', fooId: '456' }, + }) + // ^ false +} +``` + +# useMatches hook + +The `useMatches` hook returns all of the [`RouteMatch`](../RouteMatchType.md) objects +from the router **regardless of its callers position in the React component tree**. + +> [!TIP] +> If you only want the parent or child matches, then you can use the [ +`useParentMatches`](../useParentMatchesHook.md) or the [ +`useChildMatches`](../useChildMatchesHook.md) based on the selection you need. + +## useMatches options + +The `useMatches` hook accepts a single _optional_ argument, an `options` object. + +### `opts.select` option + +- Optional +- `(matches: RouteMatch[]) => TSelected` +- If supplied, this function will be called with the route matches and the return value + will be returned from `useMatches`. This value will also be used to determine if the + hook should re-render its parent component using shallow equality checks. + +### `opts.structuralSharing` option + +- Type: `boolean` +- Optional +- Configures whether structural sharing is enabled for the value returned by `select`. +- See the [Render Optimizations guide](../../../guide/render-optimizations.md) for more + information. + +## useMatches returns + +- If a `select` function is provided, the return value of the `select` function. +- If no `select` function is provided, an array of [`RouteMatch`](../RouteMatchType.md) + objects. + +## Examples + +```tsx +import { useMatches } from '@tanstack/react-router' + +function Component() { + const matches = useMatches() + // ^? [RouteMatch, RouteMatch, ...] + // ... +} +``` + +# useNavigate hook + +The `useNavigate` hook is a hook that returns a `navigate` function that can be used to +navigate to a new location. This includes changes to the pathname, search params, hash, +and location state. + +## useNavigate options + +The `useNavigate` hook accepts a single argument, an `options` object. + +### `opts.from` option + +- Type: `string` +- Optional +- Description: The location to navigate from. This is useful when you want to navigate to + a new location from a specific location, rather than the current location. + +## useNavigate returns + +- A `navigate` function that can be used to navigate to a new location. + +## navigate function + +The `navigate` function is a function that can be used to navigate to a new location. + +### navigate function options + +The `navigate` function accepts a single argument, an `options` object. + +- Type: [`NavigateOptions`](../NavigateOptionsType.md) + +### navigate function returns + +- A `Promise` that resolves when the navigation is complete + +## Examples + +```tsx +import { useNavigate } from '@tanstack/react-router' + +function PostsPage() { + const navigate = useNavigate({ from: '/posts' }) + const handleClick = () => navigate({ search: { page: 2 } }) + // ... +} + +function Component() { + const navigate = useNavigate() + return ( +
+ + + + +
+ ) +} +``` + +# useParams hook + +The `useParams` method returns all of the path parameters that were parsed for the +closest match and all of its parent matches. + +## useParams options + +The `useParams` hook accepts an optional `options` object. + +### `opts.strict` option + +- Type: `boolean` +- Optional - `default: true` +- If `false`, the `opts.from` option will be ignored and types will be loosened to + `Partial` to reflect the shared types of all params. + +### `opts.shouldThrow` option + +- Type: `boolean` +- Optional +- `default: true` +- If `false`,`useParams` will not throw an invariant exception in case a match was not + found in the currently rendered matches; in this case, it will return `undefined`. + +### `opts.select` option + +- Optional +- `(params: AllParams) => TSelected` +- If supplied, this function will be called with the params object and the return value + will be returned from `useParams`. This value will also be used to determine if the + hook should re-render its parent component using shallow equality checks. + +### `opts.structuralSharing` option + +- Type: `boolean` +- Optional +- Configures whether structural sharing is enabled for the value returned by `select`. +- See the [Render Optimizations guide](../../../guide/render-optimizations.md) for more + information. + +## useParams returns + +- An object of of the match's and parent match path params or `TSelected` if a `select` + function is provided. + +## Examples + +```tsx +import { useParams } from '@tanstack/react-router' + +const routeApi = getRouteApi('/posts/$postId') + +function Component() { + const params = useParams({ from: '/posts/$postId' }) + + // OR + + const routeParams = routeApi.useParams() + + // OR + + const postId = useParams({ + from: '/posts/$postId', + select: (params) => params.postId, + }) + + // OR + + const looseParams = useParams({ strict: false }) + + // ... +} +``` + +# useParentMatches hook + +The `useParentMatches` hook returns all of the parent [ +`RouteMatch`](../RouteMatchType.md) objects from the root down to the immediate parent of +the current match in context. **It does not include the current match, which can be +obtained using the `useMatch` hook.** + +> [!IMPORTANT] +> If the router has pending matches and they are showing their pending component +> fallbacks, `router.state.pendingMatches` will used instead of `router.state.matches`. + +## useParentMatches options + +The `useParentMatches` hook accepts an optional `options` object. + +### `opts.select` option + +- Optional +- `(matches: RouteMatch[]) => TSelected` +- If supplied, this function will be called with the route matches and the return value + will be returned from `useParentMatches`. This value will also be used to determine if + the hook should re-render its parent component using shallow equality checks. + +### `opts.structuralSharing` option + +- Type: `boolean` +- Optional +- Configures whether structural sharing is enabled for the value returned by `select`. +- See the [Render Optimizations guide](../../../guide/render-optimizations.md) for more + information. + +## useParentMatches returns + +- If a `select` function is provided, the return value of the `select` function. +- If no `select` function is provided, an array of [`RouteMatch`](../RouteMatchType.md) + objects. + +## Examples + +```tsx +import { useParentMatches } from '@tanstack/react-router' + +function Component() { + const parentMatches = useParentMatches() + // ^ [RouteMatch, RouteMatch, ...] +} +``` + +# useRouteContext hook + +The `useRouteContext` method is a hook that returns the current context for the current +route. This hook is useful for accessing the current route context in a component. + +## useRouteContext options + +The `useRouteContext` hook accepts an `options` object. + +### `opts.from` option + +- Type: `string` +- Required +- The RouteID to match the route context from. + +### `opts.select` option + +- Type: `(context: RouteContext) => TSelected` +- Optional +- If supplied, this function will be called with the route context object and the return + value will be returned from `useRouteContext`. + +## useRouteContext returns + +- The current context for the current route or `TSelected` if a `select` function is + provided. + +## Examples + +```tsx +import { useRouteContext } from '@tanstack/react-router' + +function Component() { + const context = useRouteContext({ from: '/posts/$postId' }) + // ^ RouteContext + + // OR + + const selected = useRouteContext({ + from: '/posts/$postId', + select: (context) => context.postId, + }) + // ^ string + + // ... +} +``` + +# useRouter hook + +The `useRouter` method is a hook that returns the current instance of [ +`Router`](../RouterType.md) from context. This hook is useful for accessing the router +instance in a component. + +## useRouter returns + +- The current [`Router`](../RouterType.md) instance. + +> ⚠️⚠️⚠️ **`router.state` is always up to date, but NOT REACTIVE. If you +use `router.state` in a component, the component will not re-render when the router state +changes. To get a reactive version of the router state, use +the [`useRouterState`](../useRouterStateHook.md) hook.** + +## Examples + +```tsx +import { useRouter } from '@tanstack/react-router' + +function Component() { + const router = useRouter() + // ^ Router + + // ... +} +``` + +# useRouterState hook + +The `useRouterState` method is a hook that returns the current internal state of the +router. This hook is useful for accessing the current state of the router in a component. + +> [!TIP] +> If you want to access the current location or the current matches, you should try out +> the [`useLocation`](../useLocationHook.md) and [`useMatches`](../useMatchesHook.md) hooks +> first. These hooks are designed to be more ergonomic and easier to use than accessing the +> router state directly. + +## useRouterState options + +The `useRouterState` hook accepts an optional `options` object. + +### `opts.select` option + +- Type: `(state: RouterState) => TSelected` +- Optional +- If supplied, this function will be called with the [ + `RouterState`](../RouterStateType.md) object and the return value will be returned from + `useRouterState`. + +### `opts.structuralSharing` option + +- Type: `boolean` +- Optional +- Configures whether structural sharing is enabled for the value returned by `select`. +- See the [Render Optimizations guide](../../../guide/render-optimizations.md) for more + information. + +## useRouterState returns + +- The current [`RouterState`](../RouterStateType.md) object or `TSelected` if a `select` + function is provided. + +## Examples + +```tsx +import { useRouterState } from '@tanstack/react-router' + +function Component() { + const state = useRouterState() + // ^ RouterState + + // OR + + const selected = useRouterState({ + select: (state) => state.location, + }) + // ^ ParsedLocation + + // ... +} +``` + +# useSearch hook + +The `useSearch` method is a hook that returns the current search query parameters as an +object for the current location. This hook is useful for accessing the current search +string and query parameters in a component. + +## useSearch options + +The `useSearch` hook accepts an `options` object. + +### `opts.from` option + +- Type: `string` +- Required +- The RouteID to match the search query parameters from. + +### `opts.shouldThrow` option + +- Type: `boolean` +- Optional +- `default: true` +- If `false`,`useSearch` will not throw an invariant exception in case a match was not + found in the currently rendered matches; in this case, it will return `undefined`. + +### `opts.select` option + +- Type: `(search: SelectedSearchSchema) => TSelected` +- Optional +- If supplied, this function will be called with the search object and the return value + will be returned from `useSearch`. + +### `opts.structuralSharing` option + +- Type: `boolean` +- Optional +- Configures whether structural sharing is enabled for the value returned by `select`. +- See the [Render Optimizations guide](../../../guide/render-optimizations.md) for more + information. + +### `opts.strict` option + +- Type: `boolean` +- Optional - `default: true` +- If `false`, the `opts.from` option will be ignored and types will be loosened to + `Partial` to reflect the shared types of all search query parameters. + +## useSearch returns + +- If `opts.from` is provided, an object of the search query parameters for the current + location or `TSelected` if a `select` function is provided. +- If `opts.strict` is `false`, an object of the search query parameters for the current + location or `TSelected` if a `select` function is provided. + +## Examples + +```tsx +import { useSearch } from '@tanstack/react-router' + +function Component() { + const search = useSearch({ from: '/posts/$postId' }) + // ^ FullSearchSchema + + // OR + + const selected = useSearch({ + from: '/posts/$postId', + select: (search) => search.postView, + }) + // ^ string + + // OR + + const looseSearch = useSearch({ strict: false }) + // ^ Partial + + // ... +} +``` + + + +<@tanstack/react-router_guide> +Always Apply: false - This rule should only be applied when relevant files are open +Always apply this rule in these files: src/**/*.ts, src/**/*.tsx + +# 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 `` that would normally render the child routes: + +```tsx +// src/routes/_authenticated.tsx +export const Route = createFileRoute('/_authenticated')({ + component: () => { + if (!isAuthenticated()) { + return + } + + return + }, +}) +``` + +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 `` 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 `` 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()({ + component: () => , +}) +``` + +- `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 +} + +function App() { + return ( + + + + ) +} +``` + +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! + + + +# 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
My Component
+} +``` + +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
...
+} +``` + +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: () =>
404 Not Found
, +}); +``` + +## 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 { + // Add any additional props you want to pass to the anchor element +} + +const BasicLinkComponent = React.forwardRef( + (props, ref) => { + return ( + + ) + }, +) + +const CreatedLinkComponent = createLink(BasicLinkComponent) + +export const CustomLink: LinkComponent = (props) => { + return +} +``` + +[//]: # 'BasicExampleImplementation' + +You can then use your newly created `Link` component as any other `Link` + +```tsx + +``` + +[//]: # '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 { + children?: React.ReactNode +} + +const RACLinkComponent = React.forwardRef( + (props, forwardedRef) => { + const ref = useObjectRef(forwardedRef) + + const { isPressed, linkProps } = useLink(props, ref) + const { isHovered, hoverProps } = useHover(props) + const { isFocusVisible, isFocused, focusProps } = useFocusRing(props) + + return ( + + ) + }, +) + +const CreatedLinkComponent = createLink(RACLinkComponent) + +export const CustomLink: LinkComponent = (props) => { + return +} +``` + +### 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, 'href'> { + // Add any additional props you want to pass to the link +} + +const ChakraLinkComponent = React.forwardRef< + HTMLAnchorElement, + ChakraLinkProps +>((props, ref) => { + return +}) + +const CreatedLinkComponent = createLink(ChakraLinkComponent) + +export const CustomLink: LinkComponent = ( + props, +) => { + return ( + + ) +} +``` + +### 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( + (props, ref) => , +) + +const CreatedLinkComponent = createLink(MUILinkComponent) + +export const CustomLink: LinkComponent = (props) => { + return +} + +// 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) => + + ) + }, +}) +``` + +If the error was the result of a route load, you should instead call +`router.invalidate()`, which will coordinate both a router reload and an error boundary +reset: + +```tsx +// routes/posts.tsx +export const Route = createFileRoute('/posts')({ + loader: () => fetchPosts(), + errorComponent: ({ error, reset }) => { + const router = useRouter() + + return ( +
+ {error.message} + +
+ ) + }, +}) +``` + +### Using the default `ErrorComponent` + +TanStack Router provides a default `ErrorComponent` that is rendered when an error occurs +during the route loading or rendering lifecycle. If you choose to override your routes' +error components, it's still wise to always fall back to rendering any uncaught errors +with the default `ErrorComponent`: + +```tsx +// routes/posts.tsx +import { createFileRoute, ErrorComponent } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts')({ + loader: () => fetchPosts(), + errorComponent: ({ error }) => { + if (error instanceof MyCustomError) { + // Render a custom error message + return
{error.message}
+ } + + // Fallback to the default ErrorComponent + return + }, +}) +``` + +# Data Mutations + +Since TanStack router does not store or cache data, it's role in data mutation is slim to +none outside of reacting to potential URL side-effects from external mutation events. +That said, we've compiled a list of mutation-related features you might find useful and +libraries that implement them. + +Look for and use mutation utilities that support: + +- Handling and caching submission state +- Providing both local and global optimistic UI support +- Built-in hooks to wire up invalidation (or automatically support it) +- Handling multiple in-flight mutations at once +- Organizing mutation state as a globally accessible resource +- Submission state history and garbage collection + +Some suggested libraries: + +- [TanStack Query](https://tanstack.com/query/latest/docs/react/guides/mutations) +- [SWR](https://swr.vercel.app/) +- [RTK Query](https://redux-toolkit.js.org/rtk-query/overview) +- [urql](https://formidable.com/open-source/urql/) +- [Relay](https://relay.dev/) +- [Apollo](https://www.apollographql.com/docs/react/) + +Or, even... + +- [Zustand](https://zustand-demo.pmnd.rs/) +- [Jotai](https://jotai.org/) +- [Recoil](https://recoiljs.org/) +- [Redux](https://redux.js.org/) + +Similar to data fetching, mutation state isn't a one-size-fits-all solution, so you'll +need to pick a solution that fits your needs and your team's needs. We recommend trying +out a few different solutions and seeing what works best for you. + +> ⚠️ Still here? Submission state is an interesting topic when it comes to persistence. +> Do you keep every mutation around forever? How do you know when to get rid of it? What if +> the user navigates away from the screen and then back? Let's dig in! + +## Invalidating TanStack Router after a mutation + +TanStack Router comes with short-term caching built-in. So even though we're not storing +any data after a route match is unmounted, there is a high probability that if any +mutations are made related to the data stored in the Router, the current route matches' +data could become stale. + +When mutations related to loader data are made, we can use `router.invalidate` to force +the router to reload all of the current route matches: + +```tsx +const router = useRouter() + +const addTodo = async (todo: Todo) => { + try { + await api.addTodo() + router.invalidate() + } catch { + // + } +} +``` + +Invalidating all of the current route matches happens in the background, so existing data +will continue to be served until the new data is ready, just as if you were navigating to +a new route. + +If you want to await the invalidation until all loaders have finished, pass +`{sync: true}` into `router.invalidate`: + +```tsx +const router = useRouter() + +const addTodo = async (todo: Todo) => { + try { + await api.addTodo() + await router.invalidate({ sync: true }) + } catch { + // + } +} +``` + +## Long-term mutation State + +Regardless of the mutation library used, mutations often create state related to their +submission. While most mutations are set-and-forget, some mutation states are more +long-lived, either to support optimistic UI or to provide feedback to the user about the +status of their submissions. Most state managers will correctly keep this submission +state around and expose it to make it possible to show UI elements like loading spinners, +success messages, error messages, etc. + +Let's consider the following interactions: + +- User navigates to the `/posts/123/edit` screen to edit a post +- User edits the `123` post and upon success, sees a success message below the editor + that the post was updated +- User navigates to the `/posts` screen +- User navigates back to the `/posts/123/edit` screen again + +Without notifying your mutation management library about the route change, it's possible +that your submission state could still be around and your user would still see the **" +Post updated successfully"** message when they return to the previous screen. This is not +ideal. Obviously, our intent wasn't to keep this mutation state around forever, right?! + +## Using mutation keys + +Hopefully and hypothetically, the easiest way is for your mutation library to support a +keying mechanism that will allow your mutations's state to be reset when the key changes: + +```tsx +const routeApi = getRouteApi('/room/$roomId/chat') + +function ChatRoom() { + const { roomId } = routeApi.useParams() + + const sendMessageMutation = useCoolMutation({ + fn: sendMessage, + // Clear the mutation state when the roomId changes + // including any submission state + key: ['sendMessage', roomId], + }) + + // Fire off a bunch of messages + const test = () => { + sendMessageMutation.mutate({ roomId, message: 'Hello!' }) + sendMessageMutation.mutate({ roomId, message: 'How are you?' }) + sendMessageMutation.mutate({ roomId, message: 'Goodbye!' }) + } + + return ( + <> + {sendMessageMutation.submissions.map((submission) => { + return ( +
+
{submission.status}
+
{submission.message}
+
+ ) + })} + + ) +} +``` + +## Using the `router.subscribe` method + +For libraries that don't have a keying mechanism, we'll likely need to manually reset the +mutation state when the user navigates away from the screen. To solve this, we can use +TanStack Router's `invalidate` and `subscribe` method to clear mutation states when the +user is no longer in need of them. + +The `router.subscribe` method is a function that subscribes a callback to various router +events. The event in particular that we'll use here is the `onResolved` event. It's +important to understand that this event is fired when the location path is _changed (not +just reloaded) and has finally resolved_. + +This is a great place to reset your old mutation states. Here's an example: + +```tsx +const router = createRouter() +const coolMutationCache = createCoolMutationCache() + +const unsubscribeFn = router.subscribe('onResolved', () => { + // Reset mutation states when the route changes + coolMutationCache.clear() +}) +``` + +# Deferred Data Loading + +TanStack Router is designed to run loaders in parallel and wait for all of them to +resolve before rendering the next route. This is great most of the time, but +occasionally, you may want to show the user something sooner while the rest of the data +loads in the background. + +Deferred data loading is a pattern that allows the router to render the next location's +critical data/markup while slower, non-critical route data is resolved in the background. +This process works on both the client and server (via streaming) and is a great way to +improve the perceived performance of your application. + +If you are using a library like [TanStack Query](https://react-query.tanstack.com) or any +other data fetching library, then deferred data loading works a bit differently. Skip +ahead to +the [Deferred Data Loading with External Libraries](#deferred-data-loading-with-external-libraries) +section for more information. + +## Deferred Data Loading with `Await` + +To defer slow or non-critical data, return an **unawaited/unresolved** promise anywhere +in your loader response: + +```tsx +// src/routes/posts.$postId.tsx +import { createFileRoute, defer } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts/$postId')({ + loader: async () => { + // Fetch some slower data, but do not await it + const slowDataPromise = fetchSlowData() + + // Fetch and await some data that resolves quickly + const fastData = await fetchFastData() + + return { + fastData, + deferredSlowData: slowDataPromise, + } + }, +}) +``` + +As soon as any awaited promises are resolved, the next route will begin rendering while +the deferred promises continue to resolve. + +In the component, deferred promises can be resolved and utilized using the `Await` +component: + +```tsx +// src/routes/posts.$postId.tsx +import { createFileRoute, Await } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts/$postId')({ + // ... + component: PostIdComponent, +}) + +function PostIdComponent() { + const { deferredSlowData, fastData } = Route.useLoaderData() + + // do something with fastData + + return ( + Loading...}> + {(data) => { + return
{data}
+ }} +
+ ) +} +``` + +> [!TIP] +> If your component is code-split, you can use +> the [getRouteApi function](../code-splitting.md#manually-accessing-route-apis-in-other-files-with-the-getrouteapi-helper) +> to avoid having to import the `Route` configuration to get access to the typed +`useLoaderData()` hook. + +The `Await` component resolves the promise by triggering the nearest suspense boundary +until it is resolved, after which it renders the component's `children` as a function +with the resolved data. + +If the promise is rejected, the `Await` component will throw the serialized error, which +can be caught by the nearest error boundary. + +[//]: # 'DeferredWithAwaitFinalTip' + +> [!TIP] +> In React 19, you can use the `use()` hook instead of `Await` + +[//]: # 'DeferredWithAwaitFinalTip' + +## Deferred Data Loading with External libraries + +When your strategy for fetching information for the route relies +on [External Data Loading](../external-data-loading.md) with an external library +like [TanStack Query](https://tanstack.com/query), deferred data loading works a bit +differently, as the library handles the data fetching and caching for you outside of +TanStack Router. + +So, instead of using `defer` and `Await`, you'll instead want to use the Route's `loader` +to kick off the data fetching and then use the library's hooks to access the data in your +components. + +```tsx +// src/routes/posts.$postId.tsx +import { createFileRoute } from '@tanstack/react-router' +import { slowDataOptions, fastDataOptions } from '~/api/query-options' + +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ context: { queryClient } }) => { + // Kick off the fetching of some slower data, but do not await it + queryClient.prefetchQuery(slowDataOptions()) + + // Fetch and await some data that resolves quickly + await queryClient.ensureQueryData(fastDataOptions()) + }, +}) +``` + +Then in your component, you can use the library's hooks to access the data: + +```tsx +// src/routes/posts.$postId.tsx +import { createFileRoute } from '@tanstack/react-router' +import { useSuspenseQuery } from '@tanstack/react-query' +import { slowDataOptions, fastDataOptions } from '~/api/query-options' + +export const Route = createFileRoute('/posts/$postId')({ + // ... + component: PostIdComponent, +}) + +function PostIdComponent() { + const fastData = useSuspenseQuery(fastDataOptions()) + + // do something with fastData + + return ( + Loading...}> + + + ) +} + +function SlowDataComponent() { + const data = useSuspenseQuery(slowDataOptions()) + + return
{data}
+} +``` + +## Caching and Invalidation + +Streamed promises follow the same lifecycle as the loader data they are associated with. +They can even be preloaded! + +[//]: # 'SSRContent' + +## SSR & Streaming Deferred Data + +**Streaming requires a server that supports it and for TanStack Router to be configured +to use it properly.** + +Please read the entire [Streaming SSR Guide](../ssr.md#streaming-ssr) for step by step +instructions on how to set up your server for streaming. + +## SSR Streaming Lifecycle + +The following is a high-level overview of how deferred data streaming works with TanStack +Router: + +- Server + - Promises are marked and tracked as they are returned from route loaders + - All loaders resolve and any deferred promises are serialized and embedded into the + html + - The route begins to render + - Deferred promises rendered with the `` component trigger suspense + boundaries, allowing the server to stream html up to that point +- Client + - The client receives the initial html from the server + - `` components suspend with placeholder promises while they wait for their + data to resolve on the server +- Server + - As deferred promises resolve, their results (or errors) are serialized and streamed + to the client via an inline script tag + - The resolved `` components and their suspense boundaries are resolved and + their resulting HTML is streamed to the client along with their dehydrated data +- Client + - The suspended placeholder promises within `` are resolved with the streamed + data/error responses and either render the result or throw the error to the nearest + error boundary + +[//]: # 'SSRContent' + +# Document Head Management + +Document head management is the process of managing the head, title, meta, link, and +script tags of a document and TanStack Router provides a robust way to manage the +document head for full-stack applications that use Start and for single-page applications +that use `@tanstack/react-router`. It provides: + +- Automatic deduping of `title` and `meta` tags +- Automatic loading/unloading of tags based on route visibility +- A composable way to merge `title` and `meta` tags from nested routes + +For full-stack applications that use Start, and even for single-page applications that +use `@tanstack/react-router`, managing the document head is a crucial part of any +application for the following reasons: + +- SEO +- Social media sharing +- Analytics +- CSS and JS loading/unloading + +To manage the document head, it's required that you render both the `` and +`` components and use the `routeOptions.head` property to manage the head of a +route, which returns an object with `title`, `meta`, `links`, `styles`, and `scripts` +properties. + +## Managing the Document Head + +```tsx +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { + name: 'description', + content: 'My App is a web application', + }, + { + title: 'My App', + }, + ], + links: [ + { + rel: 'icon', + href: '/favicon.ico', + }, + ], + styles: [ + { + media: 'all and (max-width: 500px)', + children: `p { + color: blue; + background-color: yellow; + }` + } + ] + scripts: [ + { + src: 'https://www.google-analytics.com/analytics.js', + }, + ], + }), +}) +``` + +### Deduping + +Out of the box, TanStack Router will dedupe `title` and `meta` tags, preferring the * +*last** occurrence of each tag found in nested routes. + +- `title` tags defined in nested routes will override a `title` tag defined in a parent + route (but you can compose them together, which is covered in a future section of this + guide) +- `meta` tags with the same `name` or `property` will be overridden by the last + occurrence of that tag found in nested routes + +### `` + +The `` component is **required** to render the head, title, meta, link, +and head-related script tags of a document. + +It should be **rendered either in the `` tag of your root layout or as high up in +the component tree as possible** if your application doesn't or can't manage the `` +tag. + +### Start/Full-Stack Applications + +```tsx +import { HeadContent } from '@tanstack/react-router' + +export const Route = createRootRoute({ + component: () => ( + + + + + + + + + ), +}) +``` + +### Single-Page Applications + +First, remove the `` tag from the the index.html if you have set any. + +```tsx +import { HeadContent } from '@tanstack/react-router' + +const rootRoute = createRootRoute({ + component: () => ( + <> + <HeadContent /> + <Outlet /> + </> + ), +}) +``` + +## Managing Body Scripts + +In addition to scripts that can be rendered in the `<head>` tag, you can also render +scripts in the `<body>` tag using the `routeOptions.scripts` property. This is useful for +loading scripts (even inline scripts) that require the DOM to be loaded, but before the +main entry point of your application (which includes hydration if you're using Start or a +full-stack implementation of TanStack Router). + +To do this, you must: + +- Use the `scripts` property of the `routeOptions` object +- [Render the `<Scripts />` component](#scripts) + +```tsx +export const Route = createRootRoute({ + scripts: [ + { + children: 'console.log("Hello, world!")', + }, + ], +}) +``` + +### `<Scripts />` + +The `<Scripts />` component is **required** to render the body scripts of a document. It +should be rendered either in the `<body>` tag of your root layout or as high up in the +component tree as possible if your application doesn't or can't manage the `<body>` tag. + +### Example + +```tsx +import { createFileRoute, Scripts } from '@tanstack/react-router' +export const Router = createFileRoute('/')({ + component: () => ( + <html> + <head /> + <body> + <Outlet /> + <Scripts /> + </body> + </html> + ), +}) +``` + +```tsx +import { Scripts, createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute({ + component: () => ( + <> + <Outlet /> + <Scripts /> + </> + ), +}) +``` + +# External Data Loading + +> [!IMPORTANT] +> This guide is geared towards external state management libraries and their integration +> with TanStack Router for data fetching, ssr, hydration/dehydration and streaming. If you +> haven't read the standard [Data Loading](../data-loading.md) guide, please do so first. + +## To **Store** or to **Coordinate**? + +While Router is very capable of storing and managing most data needs out of the box, +sometimes you just might want something more robust! + +Router is designed to be a perfect **coordinator** for external data fetching and caching +libraries. This means that you can use any data fetching/caching library you want, and +the router will coordinate the loading of your data in a way that aligns with your users' +navigation and expectations of freshness. + +## What data fetching libraries are supported? + +Any data fetching library that supports asynchronous promises can be used with TanStack +Router. This includes: + +- [TanStack Query](https://tanstack.com/query/latest/docs/react/overview) +- [SWR](https://swr.vercel.app/) +- [RTK Query](https://redux-toolkit.js.org/rtk-query/overview) +- [urql](https://formidable.com/open-source/urql/) +- [Relay](https://relay.dev/) +- [Apollo](https://www.apollographql.com/docs/react/) + +Or, even... + +- [Zustand](https://zustand-demo.pmnd.rs/) +- [Jotai](https://jotai.org/) +- [Recoil](https://recoiljs.org/) +- [Redux](https://redux.js.org/) + +Literally any library that **can return a promise and read/write data** can be +integrated. + +## Using Loaders to ensure data is loaded + +The easiest way to use integrate and external caching/data library into Router is to use +`route.loader`s to ensure that the data required inside of a route has been loaded and is +ready to be displayed. + +> ⚠️ BUT WHY? It's very important to preload your critical render data in the loader for +> a few reasons: +> +> - No "flash of loading" states +> - No waterfall data fetching, caused by component based fetching +> - Better for SEO. If your data is available at render time, it will be indexed by + search engines. + +Here is a naive illustration (don't do this) of using a Route's `loader` option to seed +the cache for some data: + +```tsx +// src/routes/posts.tsx +let postsCache = [] + +export const Route = createFileRoute('/posts')({ + loader: async () => { + postsCache = await fetchPosts() + }, + component: () => { + return ( + <div> + {postsCache.map((post) => ( + <Post key={post.id} post={post} /> + ))} + </div> + ) + }, +}) +``` + +This example is **obviously flawed**, but illustrates the point that you can use a +route's `loader` option to seed your cache with data. Let's take a look at a more +realistic example using TanStack Query. + +- Replace `fetchPosts` with your preferred data fetching library's prefetching API +- Replace `postsCache` with your preferred data fetching library's read-or-fetch API or + hook + +## A more realistic example using TanStack Query + +Let's take a look at a more realistic example using TanStack Query. + +```tsx +// src/routes/posts.tsx +const postsQueryOptions = queryOptions({ + queryKey: ['posts'], + queryFn: () => fetchPosts(), +}) + +export const Route = createFileRoute('/posts')({ + // Use the `loader` option to ensure that the data is loaded + loader: () => queryClient.ensureQueryData(postsQueryOptions), + component: () => { + // Read the data from the cache and subscribe to updates + const { + data: { posts }, + } = useSuspenseQuery(postsQueryOptions) + + return ( + <div> + {posts.map((post) => ( + <Post key={post.id} post={post} /> + ))} + </div> + ) + }, +}) +``` + +### Error handling with TanStack Query + +When an error occurs while using `suspense` with `Tanstack Query`, you'll need to let +queries know that you want to try again when re-rendering. This can be done by using the +`reset` function provided by the `useQueryErrorResetBoundary` hook. We can invoke this +function in an effect as soon as the error component mounts. This will make sure that the +query is reset and will try to fetch data again when the route component is rendered +again. This will also cover cases where users navigate away from our route instead of +clicking the `retry` button. + +```tsx +export const Route = createFileRoute('/')({ + loader: () => queryClient.ensureQueryData(postsQueryOptions), + errorComponent: ({ error, reset }) => { + const router = useRouter() + const queryErrorResetBoundary = useQueryErrorResetBoundary() + + useEffect(() => { + // Reset the query error boundary + queryErrorResetBoundary.reset() + }, [queryErrorResetBoundary]) + + return ( + <div> + {error.message} + <button + onClick={() => { + // Invalidate the route to reload the loader, and reset any router error boundaries + router.invalidate() + }} + > + retry + </button> + </div> + ) + }, +}) +``` + +## SSR Dehydration/Hydration + +Tools that are able can integrate with TanStack Router's convenient Dehydration/Hydration +APIs to shuttle dehydrated data between the server and client and rehydrate it where +needed. Let's go over how to do this with both 3rd party critical data and 3rd party +deferred data. + +## Critical Dehydration/Hydration + +**For critical data needed for the first render/paint**, TanStack Router supports * +*`dehydrate` and `hydrate`** options when configuring the `Router`. These callbacks are +functions that are automatically called on the server and client when the router +dehydrates and hydrates normally and allow you to augment the dehydrated data with your +own data. + +The `dehydrate` function can return any serializable JSON data which will get merged and +injected into the dehydrated payload that is sent to the client. + +For example, let's dehydrate and hydrate a TanStack Query `QueryClient` so that our data +we fetched on the server will be available for hydration on the client. + +```tsx +// src/router.tsx + +export function createRouter() { + // Make sure you create your loader client or similar data + // stores inside of your `createRouter` function. This ensures + // that your data stores are unique to each request and + // always present on both server and client. + const queryClient = new QueryClient() + + return createRouter({ + routeTree, + // Optionally provide your loaderClient to the router context for + // convenience (you can provide anything you want to the router + // context!) + context: { + queryClient, + }, + // On the server, dehydrate the loader client so the router + // can serialize it and send it to the client for us + dehydrate: () => { + return { + queryClientState: dehydrate(queryClient), + } + }, + // On the client, hydrate the loader client with the data + // we dehydrated on the server + hydrate: (dehydrated) => { + hydrate(queryClient, dehydrated.queryClientState) + }, + // Optionally, we can use `Wrap` to wrap our router in the loader client provider + Wrap: ({ children }) => { + return ( + <QueryClientProvider client={queryClient}> + {children} + </QueryClientProvider> + ) + }, + }) +} +``` + +# History Types + +While it's not required to know the `@tanstack/history` API itself to use TanStack +Router, it's a good idea to understand how it works. Under the hood, TanStack Router +requires and uses a `history` abstraction to manage the routing history. + +If you don't create a history instance, a browser-oriented instance of this API is +created for you when the router is initialized. If you need a special history API type, +You can use the `@tanstack/history` package to create your own: + +- `createBrowserHistory`: The default history type. +- `createHashHistory`: A history type that uses a hash to track history. +- `createMemoryHistory`: A history type that keeps the history in memory. + +Once you have a history instance, you can pass it to the `Router` constructor: + +```ts +import { createMemoryHistory, createRouter } from '@tanstack/react-router' + +const memoryHistory = createMemoryHistory({ + initialEntries: ['/'], // Pass your initial url +}) + +const router = createRouter({ routeTree, history: memoryHistory }) +``` + +## Browser Routing + +The `createBrowserHistory` is the default history type. It uses the browser's history API +to manage the browser history. + +## Hash Routing + +Hash routing can be helpful if your server doesn't support rewrites to index.html for +HTTP requests (among other environments that don't have a server). + +```ts +import { createHashHistory, createRouter } from '@tanstack/react-router' + +const hashHistory = createHashHistory() + +const router = createRouter({ routeTree, history: hashHistory }) +``` + +## Memory Routing + +Memory routing is useful in environments that are not a browser or when you do not want +components to interact with the URL. + +```ts +import { createMemoryHistory, createRouter } from '@tanstack/react-router' + +const memoryHistory = createMemoryHistory({ + initialEntries: ['/'], // Pass your initial url +}) + +const router = createRouter({ routeTree, history: memoryHistory }) +``` + +Refer to the [SSR Guide](../ssr.md#server-history) for usage on the server for +server-side rendering. + +# Link Options + +You may want to reuse options that are intended to be passed to `Link`, `redirect` or +`navigate`. In which case you may decide an object literal is a good way to represent +options passed to `Link`. + +```tsx +const dashboardLinkOptions = { + to: '/dashboard', + search: { search: '' }, +} + +function DashboardComponent() { + return <Link {...dashboardLinkOptions} /> +} +``` + +There are a few problems here. `dashboardLinkOptions.to` is inferred as `string` which by +default will resolve to every route when passed to `Link`, `navigate` or `redirect` (this +particular issue could be fixed by `as const`). The other issue here is we do not know +`dashboardLinkOptions` even passes the type checker until it is spread into `Link`. We +could very easily create incorrect navigation options and only when the options are +spread into `Link` do we know there is a type error. + +### Using `linkOptions` function to create re-usable options + +`linkOptions` is a function which type checks an object literal and returns the inferred +input as is. This provides type safety on options exactly like `Link` before it is used +allowing for easier maintenance and re-usability. Our above example using `linkOptions` +looks like this: + +```tsx +const dashboardLinkOptions = linkOptions({ + to: '/dashboard', + search: { search: '' }, +}) + +function DashboardComponent() { + return <Link {...dashboardLinkOptions} /> +} +``` + +This allows eager type checking of `dashboardLinkOptions` which can then be re-used +anywhere + +```tsx +const dashboardLinkOptions = linkOptions({ + to: '/dashboard', + search: { search: '' }, +}) + +export const Route = createFileRoute('/dashboard')({ + component: DashboardComponent, + validateSearch: (input) => ({ search: input.search }), + beforeLoad: () => { + // can used in redirect + throw redirect(dashboardLinkOptions) + }, +}) + +function DashboardComponent() { + const navigate = useNavigate() + + return ( + <div> + {/** can be used in navigate */} + <button onClick={() => navigate(dashboardLinkOptions)} /> + + {/** can be used in Link */} + <Link {...dashboardLinkOptions} /> + </div> + ) +} +``` + +### An array of `linkOptions` + +When creating navigation you might loop over an array to construct a navigation bar. In +which case `linkOptions` can be used to type check an array of object literals which are +intended for `Link` props + +```tsx +const options = linkOptions([ + { + to: '/dashboard', + label: 'Summary', + activeOptions: { exact: true }, + }, + { + to: '/dashboard/invoices', + label: 'Invoices', + }, + { + to: '/dashboard/users', + label: 'Users', + }, +]) + +function DashboardComponent() { + return ( + <> + <div className="flex items-center border-b"> + <h2 className="text-xl p-2">Dashboard</h2> + </div> + + <div className="flex flex-wrap divide-x"> + {options.map((option) => { + return ( + <Link + {...option} + key={option.to} + activeProps={{ className: `font-bold` }} + className="p-2" + > + {option.label} + </Link> + ) + })} + </div> + <hr /> + + <Outlet /> + </> + ) +} +``` + +The input of `linkOptions` is inferred and returned, as shown with the use of `label` as +this does not exist on `Link` props + +# Navigation Blocking + +Navigation blocking is a way to prevent navigation from happening. This is typical if a +user attempts to navigate while they: + +- Have unsaved changes +- Are in the middle of a form +- Are in the middle of a payment + +In these situations, a prompt or custom UI should be shown to the user to confirm they +want to navigate away. + +- If the user confirms, navigation will continue as normal +- If the user cancels, all pending navigations will be blocked + +## How does navigation blocking work? + +Navigation blocking adds one or more layers of "blockers" to the entire underlying +history API. If any blockers are present, navigation will be paused via one of the +following ways: + +- Custom UI + - If the navigation is triggered by something we control at the router level, we can + allow you to perform any task or show any UI you'd like to the user to confirm the + action. Each blocker's `blocker` function will be asynchronously and sequentially + executed. If any blocker function resolves or returns `true`, the navigation will + be allowed and all other blockers will continue to do the same until all blockers + have been allowed to proceed. If any single blocker resolves or returns `false`, + the navigation will be canceled and the rest of the `blocker` functions will be + ignored. +- The `onbeforeunload` event + - For page events that we cannot control directly, we rely on the browser's + `onbeforeunload` event. If the user attempts to close the tab or window, refresh, + or "unload" the page assets in any way, the browser's generic "Are you sure you + want to leave?" dialog will be shown. If the user confirms, all blockers will be + bypassed and the page will unload. If the user cancels, the unload will be + cancelled, and the page will remain as is. + +## How do I use navigation blocking? + +There are 2 ways to use navigation blocking: + +- Hook/logical-based blocking +- Component-based blocking + +## Hook/logical-based blocking + +Let's imagine we want to prevent navigation if a form is dirty. We can do this by using +the `useBlocker` hook: + +[//]: # 'HookBasedBlockingExample' + +```tsx +import { useBlocker } from '@tanstack/react-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = useState(false) + + useBlocker({ + shouldBlockFn: () => { + if (!formIsDirty) return false + + const shouldLeave = confirm('Are you sure you want to leave?') + return !shouldLeave + }, + }) + + // ... +} +``` + +[//]: # 'HookBasedBlockingExample' + +`shouldBlockFn` gives you type safe access to the `current` and `next` location: + +```tsx +import { useBlocker } from '@tanstack/react-router' + +function MyComponent() { + // always block going from /foo to /bar/123?hello=world + const { proceed, reset, status } = useBlocker({ + shouldBlockFn: ({ current, next }) => { + return ( + current.routeId === '/foo' && + next.fullPath === '/bar/$id' && + next.params.id === 123 && + next.search.hello === 'world' + ) + }, + withResolver: true, + }) + + // ... +} +``` + +Note that even if `shouldBlockFn` returns `false`, the browser's `beforeunload` event may +still be triggered on page reloads or tab closing. To gain control over this, you can use +the `enableBeforeUnload` option to conditionally register the `beforeunload` handler: + +[//]: # 'HookBasedBlockingExample' + +```tsx +import { useBlocker } from '@tanstack/react-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = useState(false) + + useBlocker({ + {/* ... */} + enableBeforeUnload: formIsDirty, // or () => formIsDirty + }) + + // ... +} +``` + +You can find more information about the `useBlocker` hook in +the [API reference](../../api/router/useBlockerHook.md). + +## Component-based blocking + +In addition to logical/hook based blocking, you can use the `Block` component to achieve +similar results: + +[//]: # 'ComponentBasedBlockingExample' + +```tsx +import { Block } from '@tanstack/react-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = useState(false) + + return ( + <Block + shouldBlockFn={() => { + if (!formIsDirty) return false + + const shouldLeave = confirm('Are you sure you want to leave?') + return !shouldLeave + }} + enableBeforeUnload={formIsDirty} + /> + ) + + // OR + + return ( + <Block + shouldBlockFn={() => formIsDirty} + enableBeforeUnload={formIsDirty} + withResolver + > + {({ status, proceed, reset }) => <>{/* ... */}</>} + </Block> + ) +} +``` + +[//]: # 'ComponentBasedBlockingExample' + +## How can I show a custom UI? + +In most cases, using `window.confirm` in the `shouldBlockFn` function with +`withResolver: false` in the hook is enough since it will clearly show the user that the +navigation is being blocked and resolve the blocking based on their response. + +However, in some situations, you might want to show a custom UI that is intentionally +less disruptive and more integrated with your app's design. + +**Note:** The return value of `shouldBlockFn` does not resolve the blocking if +`withResolver` is `true`. + +### Hook/logical-based custom UI with resolver + +[//]: # 'HookBasedCustomUIBlockingWithResolverExample' + +```tsx +import { useBlocker } from '@tanstack/react-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = useState(false) + + const { proceed, reset, status } = useBlocker({ + shouldBlockFn: () => formIsDirty, + withResolver: true, + }) + + // ... + + return ( + <> + {/* ... */} + {status === 'blocked' && ( + <div> + <p>Are you sure you want to leave?</p> + <button onClick={proceed}>Yes</button> + <button onClick={reset}>No</button> + </div> + )} + </> +} +``` + +[//]: # 'HookBasedCustomUIBlockingWithResolverExample' + +### Hook/logical-based custom UI without resolver + +[//]: # 'HookBasedCustomUIBlockingWithoutResolverExample' + +```tsx +import { useBlocker } from '@tanstack/react-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = useState(false) + + useBlocker({ + shouldBlockFn: () => { + if (!formIsDirty) { + return false + } + + const shouldBlock = new Promise<boolean>((resolve) => { + // Using a modal manager of your choice + modals.open({ + title: 'Are you sure you want to leave?', + children: ( + <SaveBlocker + confirm={() => { + modals.closeAll() + resolve(false) + }} + reject={() => { + modals.closeAll() + resolve(true) + }} + /> + ), + onClose: () => resolve(true), + }) + }) + return shouldBlock + }, + }) + + // ... +} +``` + +[//]: # 'HookBasedCustomUIBlockingWithoutResolverExample' + +### Component-based custom UI + +Similarly to the hook, the `Block` component returns the same state and functions as +render props: + +[//]: # 'ComponentBasedCustomUIBlockingExample' + +```tsx +import { Block } from '@tanstack/react-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = useState(false) + + return ( + <Block shouldBlockFn={() => formIsDirty} withResolver> + {({ status, proceed, reset }) => ( + <> + {/* ... */} + {status === 'blocked' && ( + <div> + <p>Are you sure you want to leave?</p> + <button onClick={proceed}>Yes</button> + <button onClick={reset}>No</button> + </div> + )} + </> + )} + </Block> + ) +} +``` + +[//]: # 'ComponentBasedCustomUIBlockingExample' + +# Navigation + +## Everything is Relative + +Believe it or not, every navigation within an app is **relative**, even if you aren't +using explicit relative path syntax (`../../somewhere`). Any time a link is clicked or an +imperative navigation call is made, you will always have an **origin** path and a * +*destination** path which means you are navigating **from** one route **to** another +route. + +TanStack Router keeps this constant concept of relative navigation in mind for every +navigation, so you'll constantly see two properties in the API: + +- `from` - The origin route path +- `to` - The destination route path + +> ⚠️ If a `from` route path isn't provided the router will assume you are navigating from +> the root `/` route and only auto-complete absolute paths. After all, you need to know +> where you are from in order to know where you're going 😉. + +## Shared Navigation API + +Every navigation and route matching API in TanStack Router uses the same core interface +with minor differences depending on the API. This means that you can learn navigation and +route matching once and use the same syntax and concepts across the library. + +### `ToOptions` Interface + +This is the core `ToOptions` interface that is used in every navigation and route +matching API: + +```ts +type ToOptions< + TRouteTree extends AnyRoute = AnyRoute, + TFrom extends RoutePaths<TRouteTree> | string = string, + TTo extends string = '', +> = { + // `from` is an optional route ID or path. If it is not supplied, only absolute paths will be auto-completed and type-safe. It's common to supply the route.fullPath of the origin route you are rendering from for convenience. If you don't know the origin route, leave this empty and work with absolute paths or unsafe relative paths. + from: string + // `to` can be an absolute route path or a relative path from the `from` option to a valid route path. ⚠️ Do not interpolate path params, hash or search params into the `to` options. Use the `params`, `search`, and `hash` options instead. + to: string + // `params` is either an object of path params to interpolate into the `to` option or a function that supplies the previous params and allows you to return new ones. This is the only way to interpolate dynamic parameters into the final URL. Depending on the `from` and `to` route, you may need to supply none, some or all of the path params. TypeScript will notify you of the required params if there are any. + params: + | Record<string, unknown> + | ((prevParams: Record<string, unknown>) => Record<string, unknown>) + // `search` is either an object of query params or a function that supplies the previous search and allows you to return new ones. Depending on the `from` and `to` route, you may need to supply none, some or all of the query params. TypeScript will notify you of the required search params if there are any. + search: + | Record<string, unknown> + | ((prevSearch: Record<string, unknown>) => Record<string, unknown>) + // `hash` is either a string or a function that supplies the previous hash and allows you to return a new one. + hash?: string | ((prevHash: string) => string) + // `state` is either an object of state or a function that supplies the previous state and allows you to return a new one. State is stored in the history API and can be useful for passing data between routes that you do not want to permanently store in URL search params. + state?: + | Record<string, any> + | ((prevState: Record<string, unknown>) => Record<string, unknown>) +} +``` + +> 🧠 Every route object has a `to` property, which can be used as the `to` for any +> navigation or route matching API. Where possible, this will allow you to avoid plain +> strings and use type-safe route references instead: + +```tsx +import { Route as aboutRoute } from './routes/about.tsx' + +function Comp() { + return <Link to={aboutRoute.to}>About</Link> +} +``` + +### `NavigateOptions` Interface + +This is the core `NavigateOptions` interface that extends `ToOptions`. Any API that is +actually performing a navigation will use this interface: + +```ts +export type NavigateOptions< + TRouteTree extends AnyRoute = AnyRoute, + TFrom extends RoutePaths<TRouteTree> | string = string, + TTo extends string = '', +> = ToOptions<TRouteTree, TFrom, TTo> & { + // `replace` is a boolean that determines whether the navigation should replace the current history entry or push a new one. + replace?: boolean + // `resetScroll` is a boolean that determines whether scroll position will be reset to 0,0 after the location is committed to browser history. + resetScroll?: boolean + // `hashScrollIntoView` is a boolean or object that determines whether an id matching the hash will be scrolled into view after the location is committed to history. + hashScrollIntoView?: boolean | ScrollIntoViewOptions + // `viewTransition` is either a boolean or function that determines if and how the browser will call document.startViewTransition() when navigating. + viewTransition?: boolean | ViewTransitionOptions + // `ignoreBlocker` is a boolean that determines if navigation should ignore any blockers that might prevent it. + ignoreBlocker?: boolean + // `reloadDocument` is a boolean that determines if navigation to a route inside of router will trigger a full page load instead of the traditional SPA navigation. + reloadDocument?: boolean + // `href` is a string that can be used in place of `to` to navigate to a full built href, e.g. pointing to an external target. + href?: string +} +``` + +### `LinkOptions` Interface + +Anywhere an actual `<a>` tag the `LinkOptions` interface which extends `NavigateOptions` +will be available: + +```tsx +export type LinkOptions< + TRouteTree extends AnyRoute = AnyRoute, + TFrom extends RoutePaths<TRouteTree> | string = string, + TTo extends string = '', +> = NavigateOptions<TRouteTree, TFrom, TTo> & { + // The standard anchor tag target attribute + target?: HTMLAnchorElement['target'] + // Defaults to `{ exact: false, includeHash: false }` + activeOptions?: { + exact?: boolean + includeHash?: boolean + includeSearch?: boolean + explicitUndefined?: boolean + } + // If set, will preload the linked route on hover and cache it for this many milliseconds in hopes that the user will eventually navigate there. + preload?: false | 'intent' + // Delay intent preloading by this many milliseconds. If the intent exits before this delay, the preload will be cancelled. + preloadDelay?: number + // If true, will render the link without the href attribute + disabled?: boolean +} +``` + +## Navigation API + +With relative navigation and all of the interfaces in mind now, let's talk about the +different flavors of navigation API at your disposal: + +- The `<Link>` component + - Generates an actual `<a>` tag with a valid `href` which can be click or even + cmd/ctrl + clicked to open in a new tab +- The `useNavigate()` hook + - When possible, `Link` component should be used for navigation, but sometimes you + need to navigate imperatively as a result of a side-effect. `useNavigate` returns a + function that can be called to perform an immediate client-side navigation. +- The `<Navigate>` component + - Renders nothing and performs an immediate client-side navigation. +- The `Router.navigate()` method + - This is the most powerful navigation API in TanStack Router. Similar to + `useNavigate`, it imperatively navigates, but is available everywhere you have + access to your router. + +⚠️ None of these APIs are a replacement for server-side redirects. If you need to +redirect a user immediately from one route to another before mounting your application, +use a server-side redirect instead of a client-side navigation. + +## `<Link>` Component + +The `Link` component is the most common way to navigate within an app. It renders an +actual `<a>` tag with a valid `href` attribute which can be clicked or even cmd/ctrl + +clicked to open in a new tab. It also supports any normal `<a>` attributes including +`target` to open links in new windows, etc. + +In addition to the [`LinkOptions`](#linkoptions-interface) interface, the `Link` +component also supports the following props: + +```tsx +export type LinkProps< + TFrom extends RoutePaths<RegisteredRouter['routeTree']> | string = string, + TTo extends string = '', +> = LinkOptions<RegisteredRouter['routeTree'], TFrom, TTo> & { + // A function that returns additional props for the `active` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated) + activeProps?: + | FrameworkHTMLAnchorTagAttributes + | (() => FrameworkHTMLAnchorAttributes) + // A function that returns additional props for the `inactive` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated) + inactiveProps?: + | FrameworkHTMLAnchorAttributes + | (() => FrameworkHTMLAnchorAttributes) +} +``` + +### Absolute Links + +Let's make a simple static link! + +```tsx +import { Link } from '@tanstack/react-router' + +const link = <Link to="/about">About</Link> +``` + +### Dynamic Links + +Dynamic links are links that have dynamic segments in them. For example, a link to a blog +post might look like this: + +```tsx +const link = ( + <Link + to="/blog/post/$postId" + params={{ + postId: 'my-first-blog-post', + }} + > + Blog Post + </Link> +) +``` + +Keep in mind that normally dynamic segment params are `string` values, but they can also +be any other type that you parse them to in your route options. Either way, the type will +be checked at compile time to ensure that you are passing the correct type. + +### Relative Links + +By default, all links are absolute unless a `from` route path is provided. This means +that the above link will always navigate to the `/about` route regardless of what route +you are currently on. + +If you want to make a link that is relative to the current route, you can provide a +`from` route path: + +```tsx +const postIdRoute = createRoute({ + path: '/blog/post/$postId', +}) + +const link = ( + <Link from={postIdRoute.fullPath} to="../categories"> + Categories + </Link> +) +``` + +As seen above, it's common to provide the `route.fullPath` as the `from` route path. This +is because the `route.fullPath` is a reference that will update if you refactor your +application. However, sometimes it's not possible to import the route directly, in which +case it's fine to provide the route path directly as a string. It will still get +type-checked as per usual! + +### Search Param Links + +Search params are a great way to provide additional context to a route. For example, you +might want to provide a search query to a search page: + +```tsx +const link = ( + <Link + to="/search" + search={{ + query: 'tanstack', + }} + > + Search + </Link> +) +``` + +It's also common to want to update a single search param without supplying any other +information about the existing route. For example, you might want to update the page +number of a search result: + +```tsx +const link = ( + <Link + to="." + search={(prev) => ({ + ...prev, + page: prev.page + 1, + })} + > + Next Page + </Link> +) +``` + +### Search Param Type Safety + +Search params are a highly dynamic state management mechanism, so it's important to +ensure that you are passing the correct types to your search params. We'll see in a later +section in detail how to validate and ensure search params typesafety, among other great +features! + +### Hash Links + +Hash links are a great way to link to a specific section of a page. For example, you +might want to link to a specific section of a blog post: + +```tsx +const link = ( + <Link + to="/blog/post/$postId" + params={{ + postId: 'my-first-blog-post', + }} + hash="section-1" + > + Section 1 + </Link> +) +``` + +### Navigating with Optional Parameters + +Optional path parameters provide flexible navigation patterns where you can include or +omit parameters as needed. Optional parameters use the `{-$paramName}` syntax and offer +fine-grained control over URL structure. + +#### Parameter Inheritance vs Removal + +When navigating with optional parameters, you have two main strategies: + +**Inheriting Current Parameters** +Use `params: {}` to inherit all current route parameters: + +```tsx +// Inherits current route parameters +<Link to="/posts/{-$category}" params={{}}> + All Posts +</Link> +``` + +**Removing Parameters** +Set parameters to `undefined` to explicitly remove them: + +```tsx +// Removes the category parameter +<Link to="/posts/{-$category}" params={{ category: undefined }}> + All Posts +</Link> +``` + +#### Basic Optional Parameter Navigation + +```tsx +// Navigate with optional parameter +<Link + to="/posts/{-$category}" + params={{ category: 'tech' }} +> + Tech Posts +</Link> + +// Navigate without optional parameter +<Link + to="/posts/{-$category}" + params={{ category: undefined }} +> + All Posts +</Link> + +// Navigate using parameter inheritance +<Link + to="/posts/{-$category}" + params={{}} +> + Current Category +</Link> +``` + +#### Function-Style Parameter Updates + +Function-style parameter updates are particularly useful with optional parameters: + +```tsx +// Remove a parameter using function syntax +<Link + to="/posts/{-$category}" + params={(prev) => ({ ...prev, category: undefined })} +> + Clear Category +</Link> + +// Update a parameter while keeping others +<Link + to="/articles/{-$category}/{-$slug}" + params={(prev) => ({ ...prev, category: 'news' })} +> + News Articles +</Link> + +// Conditionally set parameters +<Link + to="/posts/{-$category}" + params={(prev) => ({ + ...prev, + category: someCondition ? 'tech' : undefined + })} +> + Conditional Category +</Link> +``` + +#### Multiple Optional Parameters + +When working with multiple optional parameters, you can mix and match which ones to +include: + +```tsx +// Navigate with some optional parameters +<Link + to="/posts/{-$category}/{-$slug}" + params={{ category: 'tech', slug: undefined }} +> + Tech Posts +</Link> + +// Remove all optional parameters +<Link + to="/posts/{-$category}/{-$slug}" + params={{ category: undefined, slug: undefined }} +> + All Posts +</Link> + +// Set multiple parameters +<Link + to="/posts/{-$category}/{-$slug}" + params={{ category: 'tech', slug: 'react-tips' }} +> + Specific Post +</Link> +``` + +#### Mixed Required and Optional Parameters + +Optional parameters work seamlessly with required parameters: + +```tsx +// Required 'id', optional 'tab' +<Link + to="/users/$id/{-$tab}" + params={{ id: '123', tab: 'settings' }} +> + User Settings +</Link> + +// Remove optional parameter while keeping required +<Link + to="/users/$id/{-$tab}" + params={{ id: '123', tab: undefined }} +> + User Profile +</Link> + +// Use function style with mixed parameters +<Link + to="/users/$id/{-$tab}" + params={(prev) => ({ ...prev, tab: 'notifications' })} +> + User Notifications +</Link> +``` + +#### Advanced Optional Parameter Patterns + +**Prefix and Suffix Parameters** +Optional parameters with prefix/suffix work with navigation: + +```tsx +// Navigate to file with optional name +<Link + to="/files/prefix{-$name}.txt" + params={{ name: 'document' }} +> + Document File +</Link> + +// Navigate to file without optional name +<Link + to="/files/prefix{-$name}.txt" + params={{ name: undefined }} +> + Default File +</Link> +``` + +**All Optional Parameters** +Routes where all parameters are optional: + +```tsx +// Navigate to specific date +<Link + to="/{-$year}/{-$month}/{-$day}" + params={{ year: '2023', month: '12', day: '25' }} +> + Christmas 2023 +</Link> + +// Navigate to partial date +<Link + to="/{-$year}/{-$month}/{-$day}" + params={{ year: '2023', month: '12', day: undefined }} +> + December 2023 +</Link> + +// Navigate to root with all parameters removed +<Link + to="/{-$year}/{-$month}/{-$day}" + params={{ year: undefined, month: undefined, day: undefined }} +> + Home +</Link> +``` + +#### Navigation with Search Params and Optional Parameters + +Optional parameters work great in combination with search params: + +```tsx +// Combine optional path params with search params +<Link + to="/posts/{-$category}" + params={{ category: 'tech' }} + search={{ page: 1, sort: 'newest' }} +> + Tech Posts - Page 1 +</Link> + +// Remove path param but keep search params +<Link + to="/posts/{-$category}" + params={{ category: undefined }} + search={(prev) => prev} +> + All Posts - Same Filters +</Link> +``` + +#### Imperative Navigation with Optional Parameters + +All the same patterns work with imperative navigation: + +```tsx +function Component() { + const navigate = useNavigate() + + const clearFilters = () => { + navigate({ + to: '/posts/{-$category}/{-$tag}', + params: { category: undefined, tag: undefined }, + }) + } + + const setCategory = (category: string) => { + navigate({ + to: '/posts/{-$category}/{-$tag}', + params: (prev) => ({ ...prev, category }), + }) + } + + const applyFilters = (category?: string, tag?: string) => { + navigate({ + to: '/posts/{-$category}/{-$tag}', + params: { category, tag }, + }) + } +} +``` + +### Active & Inactive Props + +The `Link` component supports two additional props: `activeProps` and `inactiveProps`. +These props are functions that return additional props for the `active` and `inactive` +states of the link. All props other than styles and classes passed here will override the +original props passed to `Link`. Any styles or classes passed are merged together. + +Here's an example: + +```tsx +const link = ( + <Link + to="/blog/post/$postId" + params={{ + postId: 'my-first-blog-post', + }} + activeProps={{ + style: { + fontWeight: 'bold', + }, + }} + > + Section 1 + </Link> +) +``` + +### The `data-status` attribute + +In addition to the `activeProps` and `inactiveProps` props, the `Link` component also +adds a `data-status` attribute to the rendered element when it is in an active state. +This attribute will be `active` or `undefined` depending on the current state of the +link. This can come in handy if you prefer to use data-attributes to style your links +instead of props. + +### Active Options + +The `Link` component comes with an `activeOptions` property that offers a few options of +determining if a link is active or not. The following interface describes those options: + +```tsx +export interface ActiveOptions { + // If true, the link will be active if the current route matches the `to` route path exactly (no children routes) + // Defaults to `false` + exact?: boolean + // If true, the link will only be active if the current URL hash matches the `hash` prop + // Defaults to `false` + includeHash?: boolean // Defaults to false + // If true, the link will only be active if the current URL search params inclusively match the `search` prop + // Defaults to `true` + includeSearch?: boolean + // This modifies the `includeSearch` behavior. + // If true, properties in `search` that are explicitly `undefined` must NOT be present in the current URL search params for the link to be active. + // defaults to `false` + explicitUndefined?: boolean +} +``` + +By default, it will check if the resulting **pathname** is a prefix of the current route. +If any search params are provided, it will check that they _inclusively_ match those in +the current location. Hashes are not checked by default. + +For example, if you are on the `/blog/post/my-first-blog-post` route, the following links +will be active: + +```tsx +const link1 = ( + <Link to="/blog/post/$postId" params={{ postId: 'my-first-blog-post' }}> + Blog Post + </Link> +) +const link2 = <Link to="/blog/post">Blog Post</Link> +const link3 = <Link to="/blog">Blog Post</Link> +``` + +However, the following links will not be active: + +```tsx +const link4 = ( + <Link to="/blog/post/$postId" params={{ postId: 'my-second-blog-post' }}> + Blog Post + </Link> +) +``` + +It's common for some links to only be active if they are an exact match. A good example +of this would be a link to the home page. In scenarios like these, you can pass the +`exact: true` option: + +```tsx +const link = ( + <Link to="/" activeOptions={{ exact: true }}> + Home + </Link> +) +``` + +This will ensure that the link is not active when you are a child route. + +A few more options to be aware of: + +- If you want to include the hash in your matching, you can pass the `includeHash: true` + option +- If you do **not** want to include the search params in your matching, you can pass the + `includeSearch: false` option + +### Passing `isActive` to children + +The `Link` component accepts a function for its children, allowing you to propagate its +`isActive` property to children. For example, you could style a child component based on +whether the parent link is active: + +```tsx +const link = ( + <Link to="/blog/post"> + {({ isActive }) => { + return ( + <> + <span>My Blog Post</span> + <icon className={isActive ? 'active' : 'inactive'} /> + </> + ) + }} + </Link> +) +``` + +### Link Preloading + +The `Link` component supports automatically preloading routes on intent (hovering or +touchstart for now). This can be configured as a default in the router options (which +we'll talk more about soon) or by passing a `preload='intent'` prop to the `Link` +component. Here's an example: + +```tsx +const link = ( + <Link to="/blog/post/$postId" preload="intent"> + Blog Post + </Link> +) +``` + +With preloading enabled and relatively quick asynchronous route dependencies (if any), +this simple trick can increase the perceived performance of your application with very +little effort. + +What's even better is that by using a cache-first library like `@tanstack/query`, +preloaded routes will stick around and be ready for a stale-while-revalidate experience +if the user decides to navigate to the route later on. + +### Link Preloading Timeout + +Along with preloading is a configurable timeout which determines how long a user must +hover over a link to trigger the intent-based preloading. The default timeout is 50 +milliseconds, but you can change this by passing a `preloadTimeout` prop to the `Link` +component with the number of milliseconds you'd like to wait: + +```tsx +const link = ( + <Link to="/blog/post/$postId" preload="intent" preloadTimeout={100}> + Blog Post + </Link> +) +``` + +## `useNavigate` + +> ⚠️ Because of the `Link` component's built-in affordances around `href`, cmd/ctrl + +> click-ability, and active/inactive capabilities, it's recommended to use the `Link` +> component instead of `useNavigate` for anything the user can interact with (e.g. links, +> buttons). However, there are some cases where `useNavigate` is necessary to handle +> side-effect navigations (e.g. a successful async action that results in a navigation). + +The `useNavigate` hook returns a `navigate` function that can be called to imperatively +navigate. It's a great way to navigate to a route from a side-effect (e.g. a successful +async action). Here's an example: + +```tsx +function Component() { + const navigate = useNavigate({ from: '/posts/$postId' }) + + const handleSubmit = async (e: FrameworkFormEvent) => { + e.preventDefault() + + const response = await fetch('/posts', { + method: 'POST', + body: JSON.stringify({ title: 'My First Post' }), + }) + + const { id: postId } = await response.json() + + if (response.ok) { + navigate({ to: '/posts/$postId', params: { postId } }) + } + } +} +``` + +> 🧠 As shown above, you can pass the `from` option to specify the route to navigate from +> in the hook call. While this is also possible to pass in the resulting `navigate` +> function each time you call it, it's recommended to pass it here to reduce on potential +> error and also not type as much! + +### `navigate` Options + +The `navigate` function returned by `useNavigate` accepts the [ +`NavigateOptions` interface](#navigateoptions-interface) + +## `Navigate` Component + +Occasionally, you may find yourself needing to navigate immediately when a component +mounts. Your first instinct might be to reach for `useNavigate` and an immediate +side-effect (e.g. useEffect), but this is unnecessary. Instead, you can render the +`Navigate` component to achieve the same result: + +```tsx +function Component() { + return <Navigate to="/posts/$postId" params={{ postId: 'my-first-post' }} /> +} +``` + +Think of the `Navigate` component as a way to navigate to a route immediately when a +component mounts. It's a great way to handle client-only redirects. It is _definitely +not_ a substitute for handling server-aware redirects responsibly on the server. + +## `router.navigate` + +The `router.navigate` method is the same as the `navigate` function returned by +`useNavigate` and accepts the same [ +`NavigateOptions` interface](#navigateoptions-interface). Unlike the `useNavigate` hook, +it is available anywhere your `router` instance is available and is thus a great way to +navigate imperatively from anywhere in your application, including outside of your +framework. + +## `useMatchRoute` and `<MatchRoute>` + +The `useMatchRoute` hook and `<MatchRoute>` component are the same thing, but the hook is +a bit more flexible. They both accept the standard navigation `ToOptions` interface +either as options or props and return `true/false` if that route is currently matched. It +also has a handy `pending` option that will return `true` if the route is currently +pending (e.g. a route is currently transitioning to that route). This can be extremely +useful for showing optimistic UI around where a user is navigating: + +```tsx +function Component() { + return ( + <div> + <Link to="/users"> + Users + <MatchRoute to="/users" pending> + <Spinner /> + </MatchRoute> + </Link> + </div> + ) +} +``` + +The component version `<MatchRoute>` can also be used with a function as children to +render something when the route is matched: + +```tsx +function Component() { + return ( + <div> + <Link to="/users"> + Users + <MatchRoute to="/users" pending> + {(match) => { + return <Spinner show={match} /> + }} + </MatchRoute> + </Link> + </div> + ) +} +``` + +The hook version `useMatchRoute` returns a function that can be called programmatically +to check if a route is matched: + +```tsx +function Component() { + const matchRoute = useMatchRoute() + + useEffect(() => { + if (matchRoute({ to: '/users', pending: true })) { + console.info('The /users route is matched and pending') + } + }) + + return ( + <div> + <Link to="/users">Users</Link> + </div> + ) +} +``` + +--- + +Phew! That's a lot of navigating! That said, hopefully you're feeling pretty good about +getting around your application now. Let's move on! + +# Not Found Errors + +> ⚠️ This page covers the newer `notFound` function and `notFoundComponent` API for +> handling not found errors. The `NotFoundRoute` route is deprecated and will be removed in +> a future release. See [Migrating from `NotFoundRoute`](#migrating-from-notfoundroute) for +> more information. + +## Overview + +There are 2 uses for not-found errors in TanStack Router: + +- **Non-matching route paths**: When a path does not match any known route matching + pattern **OR** when it partially matches a route, but with extra path segments + - The **router** will automatically throw a not-found error when a path does not + match any known route matching pattern + - If the router's `notFoundMode` is set to `fuzzy`, the nearest parent route with a + `notFoundComponent` will handle the error. If the router's `notFoundMode` is set to + `root`, the root route will handle the error. + - Examples: + - Attempting to access `/users` when there is no `/users` route + - Attempting to access `/posts/1/edit` when the route tree only handles + `/posts/$postId` +- **Missing resources**: When a resource cannot be found, such as a post with a given ID + or any asynchronous data that is not available or does not exist + - **You, the developer** must throw a not-found error when a resource cannot be + found. This can be done in the `beforeLoad` or `loader` functions using the + `notFound` utility. + - Will be handled by the nearest parent route with a `notFoundComponent` (when + `notFound` is called within `loader`) or the root route. + - Examples: + - Attempting to access `/posts/1` when the post with ID 1 does not exist + - Attempting to access `/docs/path/to/document` when the document does not exist + +Under the hood, both of these cases are implemented using the same `notFound` function +and `notFoundComponent` API. + +## The `notFoundMode` option + +When TanStack Router encounters a **pathname** that doesn't match any known route pattern +**OR** partially matches a route pattern but with extra trailing pathname segments, it +will automatically throw a not-found error. + +Depending on the `notFoundMode` option, the router will handle these automatic errors +differently:: + +- ["fuzzy" mode](#notfoundmode-fuzzy) (default): The router will intelligently find the + closest matching suitable route and display the `notFoundComponent`. +- ["root" mode](#notfoundmode-root): All not-found errors will be handled by the root + route's `notFoundComponent`, regardless of the nearest matching route. + +### `notFoundMode: 'fuzzy'` + +By default, the router's `notFoundMode` is set to `fuzzy`, which indicates that if a +pathname doesn't match any known route, the router will attempt to use the closest +matching route with children/(an outlet) and a configured not found component. + +> **❓ Why is this the default?** Fuzzy matching to preserve as much parent layout as +> possible for the user gives them more context to navigate to a useful location based on +> where they thought they would arrive. + +The nearest suitable route is found using the following criteria: + +- The route must have children and therefore an `Outlet` to render the + `notFoundComponent` +- The route must have a `notFoundComponent` configured or the router must have a + `defaultNotFoundComponent` configured + +For example, consider the following route tree: + +- `__root__` (has a `notFoundComponent` configured) + - `posts` (has a `notFoundComponent` configured) + - `$postId` (has a `notFoundComponent` configured) + +If provided the path of `/posts/1/edit`, the following component structure will be +rendered: + +- `<Root>` + - `<Posts>` + - `<Posts.notFoundComponent>` + +The `notFoundComponent` of the `posts` route will be rendered because it is the **nearest +suitable parent route with children (and therefore an outlet) and a `notFoundComponent` +configured**. + +### `notFoundMode: 'root'` + +When `notFoundMode` is set to `root`, all not-found errors will be handled by the root +route's `notFoundComponent` instead of bubbling up from the nearest fuzzy-matched route. + +For example, consider the following route tree: + +- `__root__` (has a `notFoundComponent` configured) + - `posts` (has a `notFoundComponent` configured) + - `$postId` (has a `notFoundComponent` configured) + +If provided the path of `/posts/1/edit`, the following component structure will be +rendered: + +- `<Root>` + - `<Root.notFoundComponent>` + +The `notFoundComponent` of the `__root__` route will be rendered because the +`notFoundMode` is set to `root`. + +## Configuring a route's `notFoundComponent` + +To handle both types of not-found errors, you can attach a `notFoundComponent` to a +route. This component will be rendered when a not-found error is thrown. + +For example, configuring a `notFoundComponent` for a `/settings` route to handle +non-existing settings pages: + +```tsx +export const Route = createFileRoute('/settings')({ + component: () => { + return ( + <div> + <p>Settings page</p> + <Outlet /> + </div> + ) + }, + notFoundComponent: () => { + return <p>This setting page doesn't exist!</p> + }, +}) +``` + +Or configuring a `notFoundComponent` for a `/posts/$postId` route to handle posts that +don't exist: + +```tsx +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params: { postId } }) => { + const post = await getPost(postId) + if (!post) throw notFound() + return { post } + }, + component: ({ post }) => { + return ( + <div> + <h1>{post.title}</h1> + <p>{post.body}</p> + </div> + ) + }, + notFoundComponent: () => { + return <p>Post not found!</p> + }, +}) +``` + +## Default Router-Wide Not Found Handling + +You may want to provide a default not-found component for every route in your app with +child routes. + +> Why only routes with children? **Leaf-node routes (routes without children) will never +render an `Outlet` and therefore are not able to handle not-found errors.** + +To do this, pass a `defaultNotFoundComponent` to the `createRouter` function: + +```tsx +const router = createRouter({ + defaultNotFoundComponent: () => { + return ( + <div> + <p>Not found!</p> + <Link to="/">Go home</Link> + </div> + ) + }, +}) +``` + +## Throwing your own `notFound` errors + +You can manually throw not-found errors in loader methods and components using the +`notFound` function. This is useful when you need to signal that a resource cannot be +found. + +The `notFound` function works in a similar fashion to the `redirect` function. To cause a +not-found error, you can **throw a `notFound()`**. + +```tsx +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params: { postId } }) => { + // Returns `null` if the post doesn't exist + const post = await getPost(postId) + if (!post) { + throw notFound() + // Alternatively, you can make the notFound function throw: + // notFound({ throw: true }) + } + // Post is guaranteed to be defined here because we threw an error + return { post } + }, +}) +``` + +The not-found error above will be handled by the same route or nearest parent route that +has either a `notFoundComponent` route option or the `defaultNotFoundComponent` router +option configured. + +If neither the route nor any suitable parent route is found to handle the error, the root +route will handle it using TanStack Router's **extremely basic (and purposefully +undesirable)** default not-found component that simply renders `<div>Not Found</div>`. +It's highly recommended to either attach at least one `notFoundComponent` to the root +route or configure a router-wide `defaultNotFoundComponent` to handle not-found errors. + +## Specifying Which Routes Handle Not Found Errors + +Sometimes you may want to trigger a not-found on a specific parent route and bypass the +normal not-found component propagation. To do this, pass in a route id to the `route` +option in the `notFound` function. + +```tsx +// _pathlessLayout.tsx +export const Route = createFileRoute('/_pathlessLayout')({ + // This will render + notFoundComponent: () => { + return <p>Not found (in _pathlessLayout)</p> + }, + component: () => { + return ( + <div> + <p>This is a pathless layout route!</p> + <Outlet /> + </div> + ) + }, +}) + +// _pathlessLayout/route-a.tsx +export const Route = createFileRoute('/_pathless/route-a')({ + loader: async () => { + // This will make LayoutRoute handle the not-found error + throw notFound({ routeId: '/_pathlessLayout' }) + // ^^^^^^^^^ This will autocomplete from the registered router + }, + // This WILL NOT render + notFoundComponent: () => { + return <p>Not found (in _pathlessLayout/route-a)</p> + }, +}) +``` + +### Manually targeting the root route + +You can also target the root route by passing the exported `rootRouteId` variable to the +`notFound` function's `route` property: + +```tsx +import { rootRouteId } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params: { postId } }) => { + const post = await getPost(postId) + if (!post) throw notFound({ routeId: rootRouteId }) + return { post } + }, +}) +``` + +### Throwing Not Found Errors in Components + +You can also throw not-found errors in components. However, **it is recommended to throw +not-found errors in loader methods instead of components in order to correctly type +loader data and prevent flickering.** + +TanStack Router exposes a `CatchNotFound` component similar to `CatchBoundary` that can +be used to catch not-found errors in components and display UI accordingly. + +### Data Loading Inside `notFoundComponent` + +`notFoundComponent` is a special case when it comes to data loading. * +*`SomeRoute.useLoaderData` may not be defined depending on which route you are trying to +access and where the not-found error gets thrown**. However, `Route.useParams`, +`Route.useSearch`, `Route.useRouteContext`, etc. will return a defined value. + +**If you need to pass incomplete loader data to `notFoundComponent`,** pass the data via +the `data` option in the `notFound` function and validate it in `notFoundComponent`. + +```tsx +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params: { postId } }) => { + const post = await getPost(postId) + if (!post) + throw notFound({ + // Forward some data to the notFoundComponent + // data: someIncompleteLoaderData + }) + return { post } + }, + // `data: unknown` is passed to the component via the `data` option when calling `notFound` + notFoundComponent: ({ data }) => { + // ❌ useLoaderData is not valid here: const { post } = Route.useLoaderData() + + // ✅: + const { postId } = Route.useParams() + const search = Route.useSearch() + const context = Route.useRouteContext() + + return <p>Post with id {postId} not found!</p> + }, +}) +``` + +## Usage With SSR + +See [SSR guide](../ssr.md) for more information. + +## Migrating from `NotFoundRoute` + +The `NotFoundRoute` API is deprecated in favor of `notFoundComponent`. The +`NotFoundRoute` API will be removed in a future release. + +**The `notFound` function and `notFoundComponent` will not work when +using `NotFoundRoute`.** + +The main differences are: + +- `NotFoundRoute` is a route that requires an `<Outlet>` on its parent route to render. + `notFoundComponent` is a component that can be attached to any route. +- When using `NotFoundRoute`, you can't use layouts. `notFoundComponent` can be used with + layouts. +- When using `notFoundComponent`, path matching is strict. This means that if you have a + route at `/post/$postId`, a not-found error will be thrown if you try to access + `/post/1/2/3`. With `NotFoundRoute`, `/post/1/2/3` would match the `NotFoundRoute` and + only render it if there is an `<Outlet>`. + +To migrate from `NotFoundRoute` to `notFoundComponent`, you'll just need to make a few +changes: + +```tsx +// router.tsx +import { createRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen.' +- import { notFoundRoute } from './notFoundRoute' // [!code --] + +export const router = createRouter({ + routeTree, +- notFoundRoute // [!code --] +}) + +// routes/__root.tsx +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute({ + // ... ++ notFoundComponent: () => { // [!code ++] ++ return <p>Not found!</p> // [!code ++] ++ } // [!code ++] +}) +``` + +Important changes: + +- A `notFoundComponent` is added to the root route for global not-found handling. + - You can also add a `notFoundComponent` to any other route in your route tree to + handle not-found errors for that specific route. +- The `notFoundComponent` does not support rendering an `<Outlet>`. + +# Outlets + +Nested routing means that routes can be nested within other routes, including the way +they render. So how do we tell our routes where to render this nested content? + +## The `Outlet` Component + +The `Outlet` component is used to render the next potentially matching child route. +`<Outlet />` doesn't take any props and can be rendered anywhere within a route's +component tree. If there is no matching child route, `<Outlet />` will render `null`. + +> [!TIP] +> If a route's `component` is left undefined, it will render an `<Outlet />` +> automatically. + +A great example is configuring the root route of your application. Let's give our root +route a component that renders a title, then an `<Outlet />` for our top-level routes to +render. + +```tsx +import { createRootRoute, Outlet } from '@tanstack/react-router' + +export const Route = createRootRoute({ + component: RootComponent, +}) + +function RootComponent() { + return ( + <div> + <h1>My App</h1> + <Outlet /> {/* This is where child routes will render */} + </div> + ) +} +``` + +# Parallel Routes + +We haven't covered this yet. Stay tuned! + +# Path Params + +Path params are used to match a single segment (the text until the next `/`) and provide +its value back to you as a **named** variable. They are defined by using the `$` +character prefix in the path, followed by the key variable to assign it to. The following +are valid path param paths: + +- `$postId` +- `$name` +- `$teamId` +- `about/$name` +- `team/$teamId` +- `blog/$postId` + +Because path param routes only match to the next `/`, child routes can be created to +continue expressing hierarchy: + +Let's create a post route file that uses a path param to match the post ID: + +- `posts.$postId.tsx` + +```tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params }) => { + return fetchPost(params.postId) + }, +}) +``` + +## Path Params can be used by child routes + +Once a path param has been parsed, it is available to all child routes. This means that +if we define a child route to our `postRoute`, we can use the `postId` variable from the +URL in the child route's path! + +## Path Params in Loaders + +Path params are passed to the loader as a `params` object. The keys of this object are +the names of the path params, and the values are the values that were parsed out of the +actual URL path. For example, if we were to visit the `/blog/123` URL, the `params` +object would be `{ postId: '123' }`: + +```tsx +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params }) => { + return fetchPost(params.postId) + }, +}) +``` + +The `params` object is also passed to the `beforeLoad` option: + +```tsx +export const Route = createFileRoute('/posts/$postId')({ + beforeLoad: async ({ params }) => { + // do something with params.postId + }, +}) +``` + +## Path Params in Components + +If we add a component to our `postRoute`, we can access the `postId` variable from the +URL by using the route's `useParams` hook: + +```tsx +export const Route = createFileRoute('/posts/$postId')({ + component: PostComponent, +}) + +function PostComponent() { + const { postId } = Route.useParams() + return <div>Post {postId}</div> +} +``` + +> 🧠 Quick tip: If your component is code-split, you can use +> the [getRouteApi function](../code-splitting.md#manually-accessing-route-apis-in-other-files-with-the-getrouteapi-helper) +> to avoid having to import the `Route` configuration to get access to the typed +`useParams()` hook. + +## Path Params outside of Routes + +You can also use the globally exported `useParams` hook to access any parsed path params +from any component in your app. You'll need to pass the `strict: false` option to +`useParams`, denoting that you want to access the params from an ambiguous location: + +```tsx +function PostComponent() { + const { postId } = useParams({ strict: false }) + return <div>Post {postId}</div> +} +``` + +## Navigating with Path Params + +When navigating to a route with path params, TypeScript will require you to pass the +params either as an object or as a function that returns an object of params. + +Let's see what an object style looks like: + +```tsx +function Component() { + return ( + <Link to="/blog/$postId" params={{ postId: '123' }}> + Post 123 + </Link> + ) +} +``` + +And here's what a function style looks like: + +```tsx +function Component() { + return ( + <Link to="/blog/$postId" params={(prev) => ({ ...prev, postId: '123' })}> + Post 123 + </Link> + ) +} +``` + +Notice that the function style is useful when you need to persist params that are already +in the URL for other routes. This is because the function style will receive the current +params as an argument, allowing you to modify them as needed and return the final params +object. + +## Prefixes and Suffixes for Path Params + +You can also use **prefixes** and **suffixes** with path params to create more complex +routing patterns. This allows you to match specific URL structures while still capturing +the dynamic segments. + +When using either prefixes or suffixes, you can define them by wrapping the path param in +curly braces `{}` and placing the prefix or suffix before or after the variable name. + +### Defining Prefixes + +Prefixes are defined by placing the prefix text outside the curly braces before the +variable name. For example, if you want to match a URL that starts with `post-` followed +by a post ID, you can define it like this: + +```tsx +// src/routes/posts/post-{$postId}.tsx +export const Route = createFileRoute('/posts/post-{$postId}')({ + component: PostComponent, +}) + +function PostComponent() { + const { postId } = Route.useParams() + // postId will be the value after 'post-' + return <div>Post ID: {postId}</div> +} +``` + +You can even combines prefixes with wildcard routes to create more complex patterns: + +```tsx +// src/routes/on-disk/storage-{$} +export const Route = createFileRoute('/on-disk/storage-{$postId}/$')({ + component: StorageComponent, +}) + +function StorageComponent() { + const { _splat } = Route.useParams() + // _splat, will be value after 'storage-' + // i.e. my-drive/documents/foo.txt + return <div>Storage Location: /{_splat}</div> +} +``` + +### Defining Suffixes + +Suffixes are defined by placing the suffix text outside the curly braces after the +variable name. For example, if you want to match a URL a filename that ends with `txt`, +you can define it like this: + +```tsx +// src/routes/files/{$fileName}txt +export const Route = createFileRoute('/files/{$fileName}.txt')({ + component: FileComponent, +}) + +function FileComponent() { + const { fileName } = Route.useParams() + // fileName will be the value before 'txt' + return <div>File Name: {fileName}</div> +} +``` + +You can also combine suffixes with wildcards for more complex routing patterns: + +```tsx +// src/routes/files/{$}[.]txt +export const Route = createFileRoute('/files/{$fileName}[.]txt')({ + component: FileComponent, +}) + +function FileComponent() { + const { _splat } = Route.useParams() + // _splat will be the value before '.txt' + return <div>File Splat: {_splat}</div> +} +``` + +### Combining Prefixes and Suffixes + +You can combine both prefixes and suffixes to create very specific routing patterns. For +example, if you want to match a URL that starts with `user-` and ends with `.json`, you +can define it like this: + +```tsx +// src/routes/users/user-{$userId}person +export const Route = createFileRoute('/users/user-{$userId}person')({ + component: UserComponent, +}) + +function UserComponent() { + const { userId } = Route.useParams() + // userId will be the value between 'user-' and 'person' + return <div>User ID: {userId}</div> +} +``` + +Similar to the previous examples, you can also use wildcards with prefixes and suffixes. +Go wild! + +## Optional Path Parameters + +Optional path parameters allow you to define route segments that may or may not be +present in the URL. They use the `{-$paramName}` syntax and provide flexible routing +patterns where certain parameters are optional. + +### Defining Optional Parameters + +Optional path parameters are defined using curly braces with a dash prefix: +`{-$paramName}` + +```tsx +// Single optional parameter +// src/routes/posts/{-$category}.tsx +export const Route = createFileRoute('/posts/{-$category}')({ + component: PostsComponent, +}) + +// Multiple optional parameters +// src/routes/posts/{-$category}/{-$slug}.tsx +export const Route = createFileRoute('/posts/{-$category}/{-$slug}')({ + component: PostComponent, +}) + +// Mixed required and optional parameters +// src/routes/users/$id/{-$tab}.tsx +export const Route = createFileRoute('/users/$id/{-$tab}')({ + component: UserComponent, +}) +``` + +### How Optional Parameters Work + +Optional parameters create flexible URL patterns: + +- `/posts/{-$category}` matches both `/posts` and `/posts/tech` +- `/posts/{-$category}/{-$slug}` matches `/posts`, `/posts/tech`, and + `/posts/tech/hello-world` +- `/users/$id/{-$tab}` matches `/users/123` and `/users/123/settings` + +When an optional parameter is not present in the URL, its value will be `undefined` in +your route handlers and components. + +### Accessing Optional Parameters + +Optional parameters work exactly like regular parameters in your components, but their +values may be `undefined`: + +```tsx +function PostsComponent() { + const { category } = Route.useParams() + + return <div>{category ? `Posts in ${category}` : 'All Posts'}</div> +} +``` + +### Optional Parameters in Loaders + +Optional parameters are available in loaders and may be `undefined`: + +```tsx +export const Route = createFileRoute('/posts/{-$category}')({ + loader: async ({ params }) => { + // params.category might be undefined + return fetchPosts({ category: params.category }) + }, +}) +``` + +### Optional Parameters in beforeLoad + +Optional parameters work in `beforeLoad` handlers as well: + +```tsx +export const Route = createFileRoute('/posts/{-$category}')({ + beforeLoad: async ({ params }) => { + if (params.category) { + // Validate category exists + await validateCategory(params.category) + } + }, +}) +``` + +### Advanced Optional Parameter Patterns + +#### With Prefix and Suffix + +Optional parameters support prefix and suffix patterns: + +```tsx +// File route: /files/prefix{-$name}.txt +// Matches: /files/prefix.txt and /files/prefixdocument.txt +export const Route = createFileRoute('/files/prefix{-$name}.txt')({ + component: FileComponent, +}) + +function FileComponent() { + const { name } = Route.useParams() + return <div>File: {name || 'default'}</div> +} +``` + +#### All Optional Parameters + +You can create routes where all parameters are optional: + +```tsx +// Route: /{-$year}/{-$month}/{-$day} +// Matches: /, /2023, /2023/12, /2023/12/25 +export const Route = createFileRoute('/{-$year}/{-$month}/{-$day}')({ + component: DateComponent, +}) + +function DateComponent() { + const { year, month, day } = Route.useParams() + + if (!year) return <div>Select a year</div> + if (!month) return <div>Year: {year}</div> + if (!day) + return ( + <div> + Month: {year}/{month} + </div> + ) + + return ( + <div> + Date: {year}/{month}/{day} + </div> + ) +} +``` + +#### Optional Parameters with Wildcards + +Optional parameters can be combined with wildcards for complex routing patterns: + +```tsx +// Route: /docs/{-$version}/$ +// Matches: /docs/extra/path, /docs/v2/extra/path +export const Route = createFileRoute('/docs/{-$version}/$')({ + component: DocsComponent, +}) + +function DocsComponent() { + const { version } = Route.useParams() + const { _splat } = Route.useParams() + + return ( + <div> + Version: {version || 'latest'} + Path: {_splat} + </div> + ) +} +``` + +### Navigating with Optional Parameters + +When navigating to routes with optional parameters, you have fine-grained control over +which parameters to include: + +```tsx +function Navigation() { + return ( + <div> + {/* Navigate with optional parameter */} + <Link to="/posts/{-$category}" params={{ category: 'tech' }}> + Tech Posts + </Link> + + {/* Navigate without optional parameter */} + <Link to="/posts/{-$category}" params={{ category: undefined }}> + All Posts + </Link> + + {/* Navigate with multiple optional parameters */} + <Link + to="/posts/{-$category}/{-$slug}" + params={{ category: 'tech', slug: 'react-tips' }} + > + Specific Post + </Link> + </div> + ) +} +``` + +### Type Safety with Optional Parameters + +TypeScript provides full type safety for optional parameters: + +```tsx +function PostsComponent() { + // TypeScript knows category might be undefined + const { category } = Route.useParams() // category: string | undefined + + // Safe navigation + const categoryUpper = category?.toUpperCase() + + return <div>{categoryUpper || 'All Categories'}</div> +} + +// Navigation is type-safe and flexible +<Link + to="/posts/{-$category}" + params={{ category: 'tech' }} // ✅ Valid - string +> + Tech Posts +</Link> + +<Link + to="/posts/{-$category}" + params={{ category: 123 }} // ✅ Valid - number (auto-stringified) +> + Category 123 +</Link> +``` + +## Internationalization (i18n) with Optional Path Parameters + +Optional path parameters are excellent for implementing internationalization (i18n) +routing patterns. You can use prefix patterns to handle multiple languages while +maintaining clean, SEO-friendly URLs. + +### Prefix-based i18n + +Use optional language prefixes to support URLs like `/en/about`, `/fr/about`, or just +`/about` (default language): + +```tsx +// Route: /{-$locale}/about +export const Route = createFileRoute('/{-$locale}/about')({ + component: AboutComponent, +}) + +function AboutComponent() { + const { locale } = Route.useParams() + const currentLocale = locale || 'en' // Default to English + + const content = { + en: { title: 'About Us', description: 'Learn more about our company.' }, + fr: { + title: 'À Propos', + description: 'En savoir plus sur notre entreprise.', + }, + es: { + title: 'Acerca de', + description: 'Conoce más sobre nuestra empresa.', + }, + } + + return ( + <div> + <h1>{content[currentLocale]?.title}</h1> + <p>{content[currentLocale]?.description}</p> + </div> + ) +} +``` + +This pattern matches: + +- `/about` (default locale) +- `/en/about` (explicit English) +- `/fr/about` (French) +- `/es/about` (Spanish) + +### Complex i18n Patterns + +Combine optional parameters for more sophisticated i18n routing: + +```tsx +// Route: /{-$locale}/blog/{-$category}/$slug +export const Route = createFileRoute('/{-$locale}/blog/{-$category}/$slug')({ + beforeLoad: async ({ params }) => { + const locale = params.locale || 'en' + const category = params.category + + // Validate locale and category + const validLocales = ['en', 'fr', 'es', 'de'] + if (locale && !validLocales.includes(locale)) { + throw new Error('Invalid locale') + } + + return { locale, category } + }, + loader: async ({ params, context }) => { + const { locale } = context + const { slug, category } = params + + return fetchBlogPost({ slug, category, locale }) + }, + component: BlogPostComponent, +}) + +function BlogPostComponent() { + const { locale, category, slug } = Route.useParams() + const data = Route.useLoaderData() + + return ( + <article> + <h1>{data.title}</h1> + <p> + Category: {category || 'All'} | Language: {locale || 'en'} + </p> + <div>{data.content}</div> + </article> + ) +} +``` + +This supports URLs like: + +- `/blog/tech/my-post` (default locale, tech category) +- `/fr/blog/my-post` (French, no category) +- `/en/blog/tech/my-post` (explicit English, tech category) +- `/es/blog/tecnologia/mi-post` (Spanish, Spanish category) + +### Language Navigation + +Create language switchers using optional i18n parameters with function-style params: + +```tsx +function LanguageSwitcher() { + const currentParams = useParams({ strict: false }) + + const languages = [ + { code: 'en', name: 'English' }, + { code: 'fr', name: 'Français' }, + { code: 'es', name: 'Español' }, + ] + + return ( + <div className="language-switcher"> + {languages.map(({ code, name }) => ( + <Link + key={code} + to="/{-$locale}/blog/{-$category}/$slug" + params={(prev) => ({ + ...prev, + locale: code === 'en' ? undefined : code, // Remove 'en' for clean URLs + })} + className={currentParams.locale === code ? 'active' : ''} + > + {name} + </Link> + ))} + </div> + ) +} +``` + +You can also create more sophisticated language switching logic: + +```tsx +function AdvancedLanguageSwitcher() { + const currentParams = useParams({ strict: false }) + + const handleLanguageChange = (newLocale: string) => { + return (prev: any) => { + // Preserve all existing params but update locale + const updatedParams = { ...prev } + + if (newLocale === 'en') { + // Remove locale for clean English URLs + delete updatedParams.locale + } else { + updatedParams.locale = newLocale + } + + return updatedParams + } + } + + return ( + <div className="language-switcher"> + <Link + to="/{-$locale}/blog/{-$category}/$slug" + params={handleLanguageChange('fr')} + > + Français + </Link> + + <Link + to="/{-$locale}/blog/{-$category}/$slug" + params={handleLanguageChange('es')} + > + Español + </Link> + + <Link + to="/{-$locale}/blog/{-$category}/$slug" + params={handleLanguageChange('en')} + > + English + </Link> + </div> + ) +} +``` + +### Advanced i18n with Optional Parameters + +Organize i18n routes using optional parameters for flexible locale handling: + +```tsx +// Route structure: +// routes/ +// {-$locale}/ +// index.tsx // /, /en, /fr +// about.tsx // /about, /en/about, /fr/about +// blog/ +// index.tsx // /blog, /en/blog, /fr/blog +// $slug.tsx // /blog/post, /en/blog/post, /fr/blog/post + +// routes/{-$locale}/index.tsx +export const Route = createFileRoute('/{-$locale}/')({ + component: HomeComponent, +}) + +function HomeComponent() { + const { locale } = Route.useParams() + const isRTL = ['ar', 'he', 'fa'].includes(locale || '') + + return ( + <div dir={isRTL ? 'rtl' : 'ltr'}> + <h1>Welcome ({locale || 'en'})</h1> + {/* Localized content */} + </div> + ) +} + +// routes/{-$locale}/about.tsx +export const Route = createFileRoute('/{-$locale}/about')({ + component: AboutComponent, +}) +``` + +### SEO and Canonical URLs + +Handle SEO for i18n routes properly: + +```tsx +export const Route = createFileRoute('/{-$locale}/products/$id')({ + component: ProductComponent, + head: ({ params, loaderData }) => { + const locale = params.locale || 'en' + const product = loaderData + + return { + title: product.title[locale] || product.title.en, + meta: [ + { + name: 'description', + content: product.description[locale] || product.description.en, + }, + { + property: 'og:locale', + content: locale, + }, + ], + links: [ + // Canonical URL (always use default locale format) + { + rel: 'canonical', + href: `https://example.com/products/${params.id}`, + }, + // Alternate language versions + { + rel: 'alternate', + hreflang: 'en', + href: `https://example.com/products/${params.id}`, + }, + { + rel: 'alternate', + hreflang: 'fr', + href: `https://example.com/fr/products/${params.id}`, + }, + { + rel: 'alternate', + hreflang: 'es', + href: `https://example.com/es/products/${params.id}`, + }, + ], + } + }, +}) +``` + +### Type Safety for i18n + +Ensure type safety for your i18n implementations: + +```tsx +// Define supported locales +type Locale = 'en' | 'fr' | 'es' | 'de' + +// Type-safe locale validation +function validateLocale(locale: string | undefined): locale is Locale { + return ['en', 'fr', 'es', 'de'].includes(locale as Locale) +} + +export const Route = createFileRoute('/{-$locale}/shop/{-$category}')({ + beforeLoad: async ({ params }) => { + const { locale } = params + + // Type-safe locale validation + if (locale && !validateLocale(locale)) { + throw redirect({ + to: '/shop/{-$category}', + params: { category: params.category }, + }) + } + + return { + locale: (locale as Locale) || 'en', + isDefaultLocale: !locale || locale === 'en', + } + }, + component: ShopComponent, +}) + +function ShopComponent() { + const { locale, category } = Route.useParams() + const { isDefaultLocale } = Route.useRouteContext() + + // TypeScript knows locale is Locale | undefined + // and we have validated it in beforeLoad + + return ( + <div> + <h1>Shop {category ? `- ${category}` : ''}</h1> + <p>Language: {locale || 'en'}</p> + {!isDefaultLocale && ( + <Link to="/shop/{-$category}" params={{ category }}> + View in English + </Link> + )} + </div> + ) +} +``` + +Optional path parameters provide a powerful and flexible foundation for implementing +internationalization in your TanStack Router applications. Whether you prefer +prefix-based or combined approaches, you can create clean, SEO-friendly URLs while +maintaining excellent developer experience and type safety. + +## Allowed Characters + +By default, path params are escaped with `encodeURIComponent`. If you want to allow other +valid URI characters (e.g. `@` or `+`), you can specify that in +your [RouterOptions](../../api/router/RouterOptionsType.md#pathparamsallowedcharacters-property). + +Example usage: + +```tsx +const router = createRouter({ + // ... + pathParamsAllowedCharacters: ['@'], +}) +``` + +The following is the list of accepted allowed characters: + +- `;` +- `:` +- `@` +- `&` +- `=` +- `+` +- `$` +- `,` + +# Preloading + +Preloading in TanStack Router is a way to load a route before the user actually navigates +to it. This is useful for routes that are likely to be visited by the user next. For +example, if you have a list of posts and the user is likely to click on one of them, you +can preload the post route so that it's ready to go when the user clicks on it. + +## Supported Preloading Strategies + +- Intent + - Preloading by **"intent"** works by using hover and touch start events on `<Link>` + components to preload the dependencies for the destination route. + - This strategy is useful for preloading routes that the user is likely to visit + next. +- Viewport Visibility + - Preloading by **"viewport**" works by using the Intersection Observer API to + preload the dependencies for the destination route when the `<Link>` component is + in the viewport. + - This strategy is useful for preloading routes that are below the fold or + off-screen. +- Render + - Preloading by **"render"** works by preloading the dependencies for the destination + route as soon as the `<Link>` component is rendered in the DOM. + - This strategy is useful for preloading routes that are always needed. + +## How long does preloaded data stay in memory? + +Preloaded route matches are temporarily cached in memory with a few important caveats: + +- **Unused preloaded data is removed after 30 seconds by default.** This can be + configured by setting the `defaultPreloadMaxAge` option on your router. +- **Obviously, when a route is loaded, its preloaded version is promoted to the router's + normal pending matches state.** + +If you need more control over preloading, caching and/or garbage collection of preloaded +data, you should use an external caching library +like [TanStack Query](https://tanstack.com/query). + +The simplest way to preload routes for your application is to set the `defaultPreload` +option to `intent` for your entire router: + +```tsx +import { createRouter } from '@tanstack/react-router' + +const router = createRouter({ + // ... + defaultPreload: 'intent', +}) +``` + +This will turn on `intent` preloading by default for all `<Link>` components in your +application. You can also set the `preload` prop on individual `<Link>` components to +override the default behavior. + +## Preload Delay + +By default, preloading will start after **50ms** of the user hovering or touching a +`<Link>` component. You can change this delay by setting the `defaultPreloadDelay` option +on your router: + +```tsx +import { createRouter } from '@tanstack/react-router' + +const router = createRouter({ + // ... + defaultPreloadDelay: 100, +}) +``` + +You can also set the `preloadDelay` prop on individual `<Link>` components to override +the default behavior on a per-link basis. + +## Built-in Preloading & `preloadStaleTime` + +If you're using the built-in loaders, you can control how long preloaded data is +considered fresh until another preload is triggered by setting either +`routerOptions.defaultPreloadStaleTime` or `routeOptions.preloadStaleTime` to a number of +milliseconds. **By default, preloaded data is considered fresh for 30 seconds.**. + +To change this, you can set the `defaultPreloadStaleTime` option on your router: + +```tsx +import { createRouter } from '@tanstack/react-router' + +const router = createRouter({ + // ... + defaultPreloadStaleTime: 10_000, +}) +``` + +Or, you can use the `routeOptions.preloadStaleTime` option on individual routes: + +```tsx +// src/routes/posts.$postId.tsx +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params }) => fetchPost(params.postId), + // Preload the route again if the preload cache is older than 10 seconds + preloadStaleTime: 10_000, +}) +``` + +## Preloading with External Libraries + +When integrating external caching libraries like React Query, which have their own +mechanisms for determining stale data, you may want to override the default preloading +and stale-while-revalidate logic of TanStack Router. These libraries often use options +like staleTime to control the freshness of data. + +To customize the preloading behavior in TanStack Router and fully leverage your external +library's caching strategy, you can bypass the built-in caching by setting +routerOptions.defaultPreloadStaleTime or routeOptions.preloadStaleTime to 0. This ensures +that all preloads are marked as stale internally, and loaders are always invoked, +allowing your external library, such as React Query, to manage data loading and caching. + +For example: + +```tsx +import { createRouter } from '@tanstack/react-router' + +const router = createRouter({ + // ... + defaultPreloadStaleTime: 0, +}) +``` + +This would then allow you, for instance, to use an option like React Query's `staleTime` +to control the freshness of your preloads. + +## Preloading Manually + +If you need to manually preload a route, you can use the router's `preloadRoute` method. +It accepts a standard TanStack `NavigateOptions` object and returns a promise that +resolves when the route is preloaded. + +```tsx +function Component() { + const router = useRouter() + + useEffect(() => { + async function preload() { + try { + const matches = await router.preloadRoute({ + to: postRoute, + params: { id: 1 }, + }) + } catch (err) { + // Failed to preload route + } + } + + preload() + }, [router]) + + return <div /> +} +``` + +If you need to preload only the JS chunk of a route, you can use the router's +`loadRouteChunk` method. It accepts a route object and returns a promise that resolves +when the route chunk is loaded. + +```tsx +function Component() { + const router = useRouter() + + useEffect(() => { + async function preloadRouteChunks() { + try { + const postsRoute = router.routesByPath['/posts'] + await Promise.all([ + router.loadRouteChunk(router.routesByPath['/']), + router.loadRouteChunk(postsRoute), + router.loadRouteChunk(postsRoute.parentRoute), + ]) + } catch (err) { + // Failed to preload route chunk + } + } + + preloadRouteChunks() + }, [router]) + + return <div /> +} +``` + +# Render Optimizations + +TanStack Router includes several optimizations to ensure your components only re-render +when necessary. These optimizations include: + +## structural sharing + +TanStack Router uses a technique called "structural sharing" to preserve as many +references as possible between re-renders, which is particularly useful for state stored +in the URL, such as search parameters. + +For example, consider a `details` route with two search parameters, `foo` and `bar`, +accessed like this: + +```tsx +const search = Route.useSearch() +``` + +When only `bar` is changed by navigating from `/details?foo=f1&bar=b1` to +`/details?foo=f1&bar=b2`, `search.foo` will be referentially stable and only `search.bar` +will be replaced. + +## fine-grained selectors + +You can access and subscribe to the router state using various hooks like +`useRouterState`, `useSearch`, and others. If you only want a specific component to +re-render when a particular subset of the router state such as a subset of the search +parameters changes, you can use partial subscriptions with the `select` property. + +```tsx +// component won't re-render when `bar` changes +const foo = Route.useSearch({ select: ({ foo }) => foo }) +``` + +### structural sharing with fine-grained selectors + +The `select` function can perform various calculations on the router state, allowing you +to return different types of values, such as objects. For example: + +```tsx +const result = Route.useSearch({ + select: (search) => { + return { + foo: search.foo, + hello: `hello ${search.foo}`, + } + }, +}) +``` + +Although this works, it will cause your component to re-render each time, since `select` +is now returning a new object each time it’s called. + +You can avoid this re-rendering issue by using "structural sharing" as described above. +By default, structural sharing is turned off to maintain backward compatibility, but this +may change in v2. + +To enable structural sharing for fine grained selectors, you have two options: + +#### Enable it by default in the router options: + +```tsx +const router = createRouter({ + routeTree, + defaultStructuralSharing: true, +}) +``` + +#### Enable it per hook usage as shown here: + +```tsx +const result = Route.useSearch({ + select: (search) => { + return { + foo: search.foo, + hello: `hello ${search.foo}`, + } + }, + structuralSharing: true, +}) +``` + +> [!IMPORTANT] +> Structural sharing only works with JSON-compatible data. This means you cannot use +`select` to return items like class instances if structural sharing is enabled. + +In line with TanStack Router's type-safe design, TypeScript will raise an error if you +attempt the following: + +```tsx +const result = Route.useSearch({ + select: (search) => { + return { + date: new Date(), + } + }, + structuralSharing: true, +}) +``` + +If structural sharing is enabled by default in the router options, you can prevent this +error by setting `structuralSharing: false`. + +# Route Masking + +Route masking is a way to mask the actual URL of a route that gets persisted to the +browser's history and URL bar. This is useful for scenarios where you want to show a +different URL than the one that is actually being navigated to and then falling back to +the displayed URL when it is shared and (optionally) when the page is reloaded. Here's a +few examples: + +- Navigating to a modal route like `/photo/5/modal`, but masking the actual URL as + `/photos/5` +- Navigating to a modal route like `/post/5/comments`, but masking the actual URL as + `/posts/5` +- Navigating to a route with the search param `?showLogin=true`, but masking the URL to + _not_ contain the search param +- Navigating to a route with the search param `?modal=settings`, but masking the URL as ` + /settings' + +Each of these scenarios can be achieved with route masking and even extended to support +more advanced patterns like [parallel routes](../parallel-routes.md). + +## How does route masking work? + +> [!IMPORTANT] +> You **do not** need to understand how route masking works in order to use it. This +> section is for those who are curious about how it works under the hood. Skip +> to [How do I use route masking?](#how-do-i-use-route-masking) to learn how to use it!. + +Route masking utilizes the `location.state` API to store the desired runtime location +inside of the location that will get written to the URL. It stores this runtime location +under the `__tempLocation` state property: + +```tsx +const location = { + pathname: '/photos/5', + search: '', + hash: '', + state: { + key: 'wesdfs', + __tempKey: 'sadfasd', + __tempLocation: { + pathname: '/photo/5/modal', + search: '', + hash: '', + state: {}, + }, + }, +} +``` + +When the router parses a location from history with the `location.state.__tempLocation` +property, it will use that location instead of the one that was parsed from the URL. This +allows you to navigate to a route like `/photos/5` and have the router actually navigate +to `/photo/5/modal` instead. When this happens, the history location is saved back into +the `location.maskedLocation` property, just in case we need to know what the **actual +URL** is. One example of where this is used is in the Devtools where we detect if a route +is masked and show the actual URL instead of the masked one! + +Remember, you don't need to worry about any of this. It's all handled for you +automatically under the hood! + +## How do I use route masking? + +Route masking is a simple API that can be used in 2 ways: + +- Imperatively via the `mask` option available on the `<Link>` and `navigate()` APIs +- Declaratively via the Router's `routeMasks` option + +When using either route masking APIs, the `mask` option accepts the same navigation +object that the `<Link>` and `navigate()` APIs accept. This means you can use the same +`to`, `replace`, `state`, and `search` options that you're already familiar with. The +only difference is that the `mask` option will be used to mask the URL of the route being +navigated to. + +> 🧠 The mask option is also **type-safe**! This means that if you're using TypeScript, +> you'll get type errors if you try to pass an invalid navigation object to the `mask` +> option. Booyah! + +### Imperative route masking + +The `<Link>` and `navigate()` APIs both accept a `mask` option that can be used to mask +the URL of the route being navigated to. Here's an example of using it with the `<Link>` +component: + +```tsx +<Link + to="/photos/$photoId/modal" + params={{ photoId: 5 }} + mask={{ + to: '/photos/$photoId', + params: { + photoId: 5, + }, + }} +> + Open Photo +</Link> +``` + +And here's an example of using it with the `navigate()` API: + +```tsx +const navigate = useNavigate() + +function onOpenPhoto() { + navigate({ + to: '/photos/$photoId/modal', + params: { photoId: 5 }, + mask: { + to: '/photos/$photoId', + params: { + photoId: 5, + }, + }, + }) +} +``` + +### Declarative route masking + +In addition to the imperative API, you can also use the Router's `routeMasks` option to +declaratively mask routes. Instead of needing to pass the `mask` option to every `<Link>` +or `navigate()` call, you can instead create a route mask on the Router to mask routes +that match a certain pattern. Here's an example of the same route mask from above, but +using the `routeMasks` option instead: + +// Use the following for the example below + +```tsx +import { createRouteMask } from '@tanstack/react-router' + +const photoModalToPhotoMask = createRouteMask({ + routeTree, + from: '/photos/$photoId/modal', + to: '/photos/$photoId', + params: (prev) => ({ + photoId: prev.photoId, + }), +}) + +const router = createRouter({ + routeTree, + routeMasks: [photoModalToPhotoMask], +}) +``` + +When creating a route mask, you'll need to pass 1 argument with at least: + +- `routeTree` - The route tree that the route mask will be applied to +- `from` - The route ID that the route mask will be applied to +- `...navigateOptions` - The standard `to`, `search`, `params`, `replace`, etc options + that the `<Link>` and `navigate()` APIs accept + +> 🧠 The `createRouteMask` option is also **type-safe**! This means that if you're using +> TypeScript, you'll get type errors if you try to pass an invalid route mask to the +`routeMasks` option. + +## Unmasking when sharing the URL + +URLs are automatically unmasked when they are shared since as soon as a URL is detached +from your browsers local history stack, the URL masking data is no longer available. +Essentially, as soon as you copy and paste a URL out of your history, its masking data is +lost... after all, that's the point of masking a URL! + +## Local Unmasking Defaults + +**By default, URLs are not unmasked when the page is reloaded locally**. Masking data is +stored in the `location.state` property of the history location, so as long as the +history location is still in memory in your history stack, the masking data will be +available and the URL will continue to be masked. + +## Unmasking on page reload + +**As stated above, URLs are not unmasked when the page is reloaded by default**. + +If you want to unmask a URL locally when the page is reloaded, you have 3 options, each +overriding the previous one in priority if passed: + +- Set the Router's default `unmaskOnReload` option to `true` +- Return the `unmaskOnReload: true` option from the masking function when creating a + route mask with `createRouteMask()` +- Pass the `unmaskOnReload: true` option to the `<Link`> component or `navigate()` API + +# Router Context + +TanStack Router's router context is a very powerful tool that can be used for dependency +injection among many other things. Aptly named, the router context is passed through the +router and down through each matching route. At each route in the hierarchy, the context +can be modified or added to. Here's a few ways you might use the router context +practically: + +- Dependency Injection + - You can supply dependencies (e.g. a loader function, a data fetching client, a + mutation service) which the route and all child routes can access and use without + importing or creating directly. +- Breadcrumbs + - While the main context object for each route is merged as it descends, each route's + unique context is also stored making it possible to attach breadcrumbs or methods + to each route's context. +- Dynamic meta tag management + - You can attach meta tags to each route's context and then use a meta tag manager to + dynamically update the meta tags on the page as the user navigates the site. + +These are just suggested uses of the router context. You can use it for whatever you +want! + +## Typed Router Context + +Like everything else, the root router context is strictly typed. This type can be +augmented via any route's `beforeLoad` option as it is merged down the route match tree. +To constrain the type of the root router context, you must use the +`createRootRouteWithContext<YourContextTypeHere>()(routeOptions)` function to create a +new router context instead of the `createRootRoute()` function to create your root route. +Here's an example: + +```tsx +import { + createRootRouteWithContext, + createRouter, +} from '@tanstack/react-router' + +interface MyRouterContext { + user: User +} + +// Use the routerContext to create your root route +const rootRoute = createRootRouteWithContext<MyRouterContext>()({ + component: App, +}) + +const routeTree = rootRoute.addChildren([ + // ... +]) + +// Use the routerContext to create your router +const router = createRouter({ + routeTree, +}) +``` + +## Passing the initial Router Context + +The router context is passed to the router at instantiation time. You can pass the +initial router context to the router via the `context` option: + +> [!TIP] +> If your context has any required properties, you will see a TypeScript error if you +> don't pass them in the initial router context. If all of your context properties are +> optional, you will not see a TypeScript error and passing the context will be optional. +> If you don't pass a router context, it defaults to `{}`. + +```tsx +import { createRouter } from '@tanstack/react-router' + +// Use the routerContext you created to create your router +const router = createRouter({ + routeTree, + context: { + user: { + id: '123', + name: 'John Doe', + }, + }, +}) +``` + +### Invalidating the Router Context + +If you need to invalidate the context state you are passing into the router, you can call +the `invalidate` method to tell the router to recompute the context. This is useful when +you need to update the context state and have the router recompute the context for all +routes. + +```tsx +function useAuth() { + const router = useRouter() + const [user, setUser] = useState<User | null>(null) + + useEffect(() => { + const unsubscribe = auth.onAuthStateChanged((user) => { + setUser(user) + router.invalidate() + }) + + return unsubscribe + }, []) + + return user +} +``` + +## Using the Router Context + +Once you have defined the router context type, you can use it in your route definitions: + +```tsx +// src/routes/todos.tsx +export const Route = createFileRoute('/todos')({ + component: Todos, + loader: ({ context }) => fetchTodosByUserId(context.user.id), +}) +``` + +You can even inject data fetching and mutation implementations themselves! In fact, this +is highly recommended 😜 + +Let's try this with a simple function to fetch some todos: + +```tsx +const fetchTodosByUserId = async ({ userId }) => { + const response = await fetch(`/api/todos?userId=${userId}`) + const data = await response.json() + return data +} + +const router = createRouter({ + routeTree: rootRoute, + context: { + userId: '123', + fetchTodosByUserId, + }, +}) +``` + +Then, in your route: + +```tsx +// src/routes/todos.tsx +export const Route = createFileRoute('/todos')({ + component: Todos, + loader: ({ context }) => context.fetchTodosByUserId(context.userId), +}) +``` + +### How about an external data fetching library? + +```tsx +import { + createRootRouteWithContext, + createRouter, +} from '@tanstack/react-router' + +interface MyRouterContext { + queryClient: QueryClient +} + +const rootRoute = createRootRouteWithContext<MyRouterContext>()({ + component: App, +}) + +const queryClient = new QueryClient() + +const router = createRouter({ + routeTree: rootRoute, + context: { + queryClient, + }, +}) +``` + +Then, in your route: + +```tsx +// src/routes/todos.tsx +export const Route = createFileRoute('/todos')({ + component: Todos, + loader: async ({ context }) => { + await context.queryClient.ensureQueryData({ + queryKey: ['todos', { userId: user.id }], + queryFn: fetchTodos, + }) + }, +}) +``` + +## How about using React Context/Hooks? + +When trying to use React Context or Hooks in your route's `beforeLoad` or `loader` +functions, it's important to remember React' +s [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks). You can't use hooks +in a non-React function, so you can't use hooks in your `beforeLoad` or `loader` +functions. + +So, how do we use React Context or Hooks in our route's `beforeLoad` or `loader` +functions? We can use the router context to pass down the React Context or Hooks to our +route's `beforeLoad` or `loader` functions. + +Let's look at the setup for an example, where we pass down a `useNetworkStrength` hook to +our route's `loader` function: + +- `src/routes/__root.tsx` + +```tsx +// First, make sure the context for the root route is typed +import { createRootRouteWithContext } from '@tanstack/react-router' +import { useNetworkStrength } from '@/hooks/useNetworkStrength' + +interface MyRouterContext { + networkStrength: ReturnType<typeof useNetworkStrength> +} + +export const Route = createRootRouteWithContext<MyRouterContext>()({ + component: App, +}) +``` + +In this example, we'd instantiate the hook before rendering the router using the +`<RouterProvider />`. This way, the hook would be called in React-land, therefore +adhering to the Rules of Hooks. + +- `src/router.tsx` + +```tsx +import { createRouter } from '@tanstack/react-router' + +import { routeTree } from './routeTree.gen' + +export const router = createRouter({ + routeTree, + context: { + networkStrength: undefined!, // We'll set this in React-land + }, +}) +``` + +- `src/main.tsx` + +```tsx +import { RouterProvider } from '@tanstack/react-router' +import { router } from './router' + +import { useNetworkStrength } from '@/hooks/useNetworkStrength' + +function App() { + const networkStrength = useNetworkStrength() + // Inject the returned value from the hook into the router context + return <RouterProvider router={router} context={{ networkStrength }} /> +} + +// ... +``` + +So, now in our route's `loader` function, we can access the `networkStrength` hook from +the router context: + +- `src/routes/posts.tsx` + +```tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts')({ + component: Posts, + loader: ({ context }) => { + if (context.networkStrength === 'STRONG') { + // Do something + } + }, +}) +``` + +## Modifying the Router Context + +The router context is passed down the route tree and is merged at each route. This means +that you can modify the context at each route and the modifications will be available to +all child routes. Here's an example: + +- `src/routes/__root.tsx` + +```tsx +import { createRootRouteWithContext } from '@tanstack/react-router' + +interface MyRouterContext { + foo: boolean +} + +export const Route = createRootRouteWithContext<MyRouterContext>()({ + component: App, +}) +``` + +- `src/router.tsx` + +```tsx +import { createRouter } from '@tanstack/react-router' + +import { routeTree } from './routeTree.gen' + +const router = createRouter({ + routeTree, + context: { + foo: true, + }, +}) +``` + +- `src/routes/todos.tsx` + +```tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/todos')({ + component: Todos, + beforeLoad: () => { + return { + bar: true, + } + }, + loader: ({ context }) => { + context.foo // true + context.bar // true + }, +}) +``` + +## Processing Accumulated Route Context + +Context, especially the isolated route `context` objects, make it trivial to accumulate +and process the route context objects for all matched routes. Here's an example where we +use all of the matched route contexts to generate a breadcrumb trail: + +```tsx +// src/routes/__root.tsx +export const Route = createRootRoute({ + component: () => { + const matches = useRouterState({ select: (s) => s.matches }) + + const breadcrumbs = matches + .filter((match) => match.context.getTitle) + .map(({ pathname, context }) => { + return { + title: context.getTitle(), + path: pathname, + } + }) + + // ... + }, +}) +``` + +Using that same route context, we could also generate a title tag for our page's +`<head>`: + +```tsx +// src/routes/__root.tsx +export const Route = createRootRoute({ + component: () => { + const matches = useRouterState({ select: (s) => s.matches }) + + const matchWithTitle = [...matches] + .reverse() + .find((d) => d.context.getTitle) + + const title = matchWithTitle?.context.getTitle() || 'My App' + + return ( + <html> + <head> + <title>{title} + + {/* ... */} + + ) + }, +}) +``` + +# Scroll Restoration + +## Hash/Top-of-Page Scrolling + +Out of the box, TanStack Router supports both **hash scrolling** and **top-of-page +scrolling** without any additional configuration. + +## Scroll-to-top & Nested Scrollable Areas + +By default, scroll-to-top mimics the behavior of the browser, which means only the +`window` itself is scrolled to the top after successful navigation. For many apps +however, it's common for the main scrollable area to be a nested div or similar because +of advanced layouts. If you would like TanStack Router to also scroll these main +scrollable areas for you, you can add selectors to target them using the +`routerOptions.scrollToTopSelectors`: + +```tsx +const router = createRouter({ + scrollToTopSelectors: ['#main-scrollable-area'], +}) +``` + +For complex selectors that cannot be simply resolved using +`document.querySelector(selector)`, you can pass functions that return HTML elements to +`routerOptions.scrollToTopSelectors`: + +```tsx +const selector = () => + document + .querySelector('#shadowRootParent') + ?.shadowRoot?.querySelector('#main-scrollable-area') + +const router = createRouter({ + scrollToTopSelectors: [selector], +}) +``` + +These selectors are handled **in addition to `window`** which cannot be disabled +currently. + +## Scroll Restoration + +Scroll restoration is the process of restoring the scroll position of a page when the +user navigates back to it. This is normally a built-in feature for standard HTML based +websites, but can be difficult to replicate for SPA applications because: + +- SPAs typically use the `history.pushState` API for navigation, so the browser doesn't + know to restore the scroll position natively +- SPAs sometimes render content asynchronously, so the browser doesn't know the height of + the page until after it's rendered +- SPAs can sometimes use nested scrollable containers to force specific layouts and + features. + +Not only that, but it's very common for applications to have multiple scrollable areas +within an app, not just the body. For example, a chat application might have a scrollable +sidebar and a scrollable chat area. In this case, you would want to restore the scroll +position of both areas independently. + +To alleviate this problem, TanStack Router provides a scroll restoration component and +hook that handle the process of monitoring, caching and restoring scroll positions for +you. + +It does this by: + +- Monitoring the DOM for scroll events +- Registering scrollable areas with the scroll restoration cache +- Listening to the proper router events to know when to cache and restore scroll + positions +- Storing scroll positions for each scrollable area in the cache (including `window` and + `body`) +- Restoring scroll positions after successful navigations before DOM paint + +That may sound like a lot, but for you, it's as simple as this: + +```tsx +import { createRouter } from '@tanstack/react-router' + +const router = createRouter({ + scrollRestoration: true, +}) +``` + +> [!NOTE] +> The `` component still works, but has been deprecated. + +## Custom Cache Keys + +Falling in behind Remix's own Scroll Restoration APIs, you can also customize the key +used to cache scroll positions for a given scrollable area using the `getKey` option. +This could be used, for example, to force the same scroll position to be used regardless +of the users browser history. + +The `getKey` option receives the relevant `Location` state from TanStack Router and +expects you to return a string to uniquely identify the scrollable measurements for that +state. + +The default `getKey` is `(location) => location.state.__TSR_key!`, where `__TSR_key` is +the unique key generated for each entry in the history. + +> Older versions, prior to `v1.121.34`, used `state.key` as the default key, but this has +> been deprecated in favor of `state.__TSR_key`. For now, `location.state.key` will still +> be available for compatibility, but it will be removed in the next major version. + +## Examples + +You could sync scrolling to the pathname: + +```tsx +import { createRouter } from '@tanstack/react-router' + +const router = createRouter({ + getScrollRestorationKey: (location) => location.pathname, +}) +``` + +You can conditionally sync only some paths, then use the key for the rest: + +```tsx +import { createRouter } from '@tanstack/react-router' + +const router = createRouter({ + getScrollRestorationKey: (location) => { + const paths = ['/', '/chat'] + return paths.includes(location.pathname) + ? location.pathname + : location.state.__TSR_key! + }, +}) +``` + +## Preventing Scroll Restoration + +Sometimes you may want to prevent scroll restoration from happening. To do this you can +utilize the `resetScroll` option available on the following APIs: + +- `` +- `navigate({ resetScroll: false })` +- `redirect({ resetScroll: false })` + +When `resetScroll` is set to `false`, the scroll position for the next navigation will +not be restored (if navigating to an existing history event in the stack) or reset to the +top (if it's a new history event in the stack). + +## Manual Scroll Restoration + +Most of the time, you won't need to do anything special to get scroll restoration to +work. However, there are some cases where you may need to manually control scroll +restoration. The most common example is **virtualized lists**. + +To manually control scroll restoration for virtualized lists within the whole browser +window: + +[//]: # 'VirtualizedWindowScrollRestorationExample' + +```tsx +function Component() { + const scrollEntry = useElementScrollRestoration({ + getElement: () => window, + }) + + // Let's use TanStack Virtual to virtualize some content! + const virtualizer = useWindowVirtualizer({ + count: 10000, + estimateSize: () => 100, + // We pass the scrollY from the scroll restoration entry to the virtualizer + // as the initial offset + initialOffset: scrollEntry?.scrollY, + }) + + return ( +
+ {virtualizer.getVirtualItems().map(item => ( + ... + ))} +
+ ) +} +``` + +[//]: # 'VirtualizedWindowScrollRestorationExample' + +To manually control scroll restoration for a specific element, you can use the +`useElementScrollRestoration` hook and the `data-scroll-restoration-id` DOM attribute: + +[//]: # 'ManualRestorationExample' + +```tsx +function Component() { + // We need a unique ID for manual scroll restoration on a specific element + // It should be as unique as possible for this element across your app + const scrollRestorationId = 'myVirtualizedContent' + + // We use that ID to get the scroll entry for this element + const scrollEntry = useElementScrollRestoration({ + id: scrollRestorationId, + }) + + // Let's use TanStack Virtual to virtualize some content! + const virtualizerParentRef = React.useRef(null) + const virtualizer = useVirtualizer({ + count: 10000, + getScrollElement: () => virtualizerParentRef.current, + estimateSize: () => 100, + // We pass the scrollY from the scroll restoration entry to the virtualizer + // as the initial offset + initialOffset: scrollEntry?.scrollY, + }) + + return ( +
+ ... +
+ ) +} +``` + +[//]: # 'ManualRestorationExample' + +## Scroll Behavior + +To control the scroll behavior when navigating between pages, you can use the +`scrollRestorationBehavior` option. This allows you to make the transition between pages +instant instead of a smooth scroll. The global configuration of scroll restoration +behavior has the same options as those supported by the browser, which are `smooth`, +`instant`, and `auto` ( +see [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView#behavior) +for more information). + +```tsx +import { createRouter } from '@tanstack/react-router' + +const router = createRouter({ + scrollRestorationBehavior: 'instant', +}) +``` + +# Search Params + +Similar to how TanStack Query made handling server-state in your React and Solid +applications a breeze, TanStack Router aims to unlock the power of URL search params in +your applications. + +> 🧠 If you are on a really old browser, like IE11, you may need to use a polyfill for +`URLSearchParams`. + +## Why not just use `URLSearchParams`? + +We get it, you've been hearing a lot of "use the platform" lately and for the most part, +we agree. However, we also believe it's important to recognize where the platform falls +short for more advanced use-cases and we believe `URLSearchParams` is one of these +circumstances. + +Traditional Search Param APIs usually assume a few things: + +- Search params are always strings +- They are _mostly_ flat +- Serializing and deserializing using `URLSearchParams` is good enough (Spoiler alert: + it's not.) +- Search params modifications are tightly coupled with the URL's pathname and must be + updated together, even if the pathname is not changing. + +Reality is very different from these assumptions though. + +- Search params represent application state, so inevitably, we will expect them to have + the same DX associated with other state managers. This means having the capability of + distinguishing between primitive value types and efficiently storing and manipulating + complex data structures like nested arrays and objects. +- There are many ways to serialize and deserialize state with different tradeoffs. You + should be able to choose the best one for your application or at the very least get a + better default than `URLSearchParams`. +- Immutability & Structural Sharing. Every time you stringify and parse url search + params, referential integrity and object identity is lost because each new parse + creates a brand new data structure with a unique memory reference. If not properly + managed over its lifetime, this constant serialization and parsing can result in + unexpected and undesirable performance issues, especially in frameworks like React that + choose to track reactivity via immutability or in Solid that normally relies on + reconciliation to detect changes from deserialized data sources. +- Search params, while an important part of the URL, do frequently change independently + of the URL's pathname. For example, a user may want to change the page number of a + paginated list without touching the URL's pathname. + +## Search Params, the "OG" State Manager + +You've probably seen search params like `?page=3` or `?filter-name=tanner` in the URL. +There is no question that this is truly **a form of global state** living inside of the +URL. It's valuable to store specific pieces of state in the URL because: + +- Users should be able to: + - Cmd/Ctrl + Click to open a link in a new tab and reliably see the state they + expected + - Bookmark and share links from your application with others with assurances that + they will see exactly the state as when the link was copied. + - Refresh your app or navigate back and forth between pages without losing their + state +- Developers should be able to easily: + - Add, remove or modify state in the URL with the same great DX as other state + managers + - Easily validate search params coming from the URL in a format and type that is safe + for their application to consume + - Read and write to search params without having to worry about the underlying + serialization format + +## JSON-first Search Params + +To achieve the above, the first step built in to TanStack Router is a powerful search +param parser that automatically converts the search string of your URL to structured +JSON. This means that you can store any JSON-serializable data structure in your search +params and it will be parsed and serialized as JSON. This is a huge improvement over +`URLSearchParams` which has limited support for array-like structures and nested data. + +For example, navigating to the following route: + +```tsx +const link = ( + +) +``` + +Will result in the following URL: + +``` +/shop?pageIndex=3&includeCategories=%5B%22electronics%22%2C%22gifts%22%5D&sortBy=price&desc=true +``` + +When this URL is parsed, the search params will be accurately converted back to the +following JSON: + +```json +{ + "pageIndex": 3, + "includeCategories": ["electronics", "gifts"], + "sortBy": "price", + "desc": true +} +``` + +If you noticed, there are a few things going on here: + +- The first level of the search params is flat and string based, just like + `URLSearchParams`. +- First level values that are not strings are accurately preserved as actual numbers and + booleans. +- Nested data structures are automatically converted to URL-safe JSON strings + +> 🧠 It's common for other tools to assume that search params are always flat and +> string-based which is why we've chosen to keep things URLSearchParam compliant at the +> first level. This ultimately means that even though TanStack Router is managing your +> nested search params as JSON, other tools will still be able to write to the URL and read +> first-level params normally. + +## Validating and Typing Search Params + +Despite TanStack Router being able to parse search params into reliable JSON, they +ultimately still came from **a user-facing raw-text input**. Similar to other +serialization boundaries, this means that before you consume search params, they should +be validated into a format that your application can trust and rely on. + +### Enter Validation + TypeScript! + +TanStack Router provides convenient APIs for validating and typing search params. This +all starts with the `Route`'s `validateSearch` option: + +```tsx +// /routes/shop.products.tsx + +type ProductSearchSortOptions = 'newest' | 'oldest' | 'price' + +type ProductSearch = { + page: number + filter: string + sort: ProductSearchSortOptions +} + +export const Route = createFileRoute('/shop/products')({ + validateSearch: (search: Record): ProductSearch => { + // validate and parse the search params into a typed state + return { + page: Number(search?.page ?? 1), + filter: (search.filter as string) || '', + sort: (search.sort as ProductSearchSortOptions) || 'newest', + } + }, +}) +``` + +In the above example, we're validating the search params of the `Route` and returning a +typed `ProductSearch` object. This typed object is then made available to this route's +other options **and any child routes, too!** + +### Validating Search Params + +The `validateSearch` option is a function that is provided the JSON parsed (but +non-validated) search params as a `Record` and returns a typed object of +your choice. It's usually best to provide sensible fallbacks for malformed or unexpected +search params so your users' experience stays non-interrupted. + +Here's an example: + +```tsx +// /routes/shop.products.tsx + +type ProductSearchSortOptions = 'newest' | 'oldest' | 'price' + +type ProductSearch = { + page: number + filter: string + sort: ProductSearchSortOptions +} + +export const Route = createFileRoute('/shop/products')({ + validateSearch: (search: Record): ProductSearch => { + // validate and parse the search params into a typed state + return { + page: Number(search?.page ?? 1), + filter: (search.filter as string) || '', + sort: (search.sort as ProductSearchSortOptions) || 'newest', + } + }, +}) +``` + +Here's an example using the [Zod](https://zod.dev/) library (but feel free to use any +validation library you want) to both validate and type the search params in a single +step: + +```tsx +// /routes/shop.products.tsx + +import { z } from 'zod' + +const productSearchSchema = z.object({ + page: z.number().catch(1), + filter: z.string().catch(''), + sort: z.enum(['newest', 'oldest', 'price']).catch('newest'), +}) + +type ProductSearch = z.infer + +export const Route = createFileRoute('/shop/products')({ + validateSearch: (search) => productSearchSchema.parse(search), +}) +``` + +Because `validateSearch` also accepts an object with the `parse` property, this can be +shortened to: + +```tsx +validateSearch: productSearchSchema +``` + +In the above example, we used Zod's `.catch()` modifier instead of `.default()` to avoid +showing an error to the user because we firmly believe that if a search parameter is +malformed, you probably don't want to halt the user's experience through the app to show +a big fat error message. That said, there may be times that you **do want to show an +error message**. In that case, you can use `.default()` instead of `.catch()`. + +The underlying mechanics why this works relies on the `validateSearch` function throwing +an error. If an error is thrown, the route's `onError` option will be triggered (and +`error.routerCode` will be set to `VALIDATE_SEARCH` and the `errorComponent` will be +rendered instead of the route's `component` where you can handle the search param error +however you'd like. + +#### Adapters + +When using a library like [Zod](https://zod.dev/) to validate search params you might +want to `transform` search params before committing the search params to the URL. A +common `zod` `transform` is `default` for example. + +```tsx +import { createFileRoute } from '@tanstack/react-router' +import { z } from 'zod' + +const productSearchSchema = z.object({ + page: z.number().default(1), + filter: z.string().default(''), + sort: z.enum(['newest', 'oldest', 'price']).default('newest'), +}) + +export const Route = createFileRoute('/shop/products/')({ + validateSearch: productSearchSchema, +}) +``` + +It might be surprising that when you try to navigate to this route, `search` is required. +The following `Link` will type error as `search` is missing. + +```tsx + +``` + +For validation libraries we recommend using adapters which infer the correct `input` and +`output` types. + +### Zod + +An adapter is provided for [Zod](https://zod.dev/) which will pipe through the correct +`input` type and `output` type + +```tsx +import { createFileRoute } from '@tanstack/react-router' +import { zodValidator } from '@tanstack/zod-adapter' +import { z } from 'zod' + +const productSearchSchema = z.object({ + page: z.number().default(1), + filter: z.string().default(''), + sort: z.enum(['newest', 'oldest', 'price']).default('newest'), +}) + +export const Route = createFileRoute('/shop/products/')({ + validateSearch: zodValidator(productSearchSchema), +}) +``` + +The important part here is the following use of `Link` no longer requires `search` params + +```tsx + +``` + +However the use of `catch` here overrides the types and makes `page`, `filter` and `sort` +`unknown` causing type loss. We have handled this case by providing a `fallback` generic +function which retains the types but provides a `fallback` value when validation fails + +```tsx +import { createFileRoute } from '@tanstack/react-router' +import { fallback, zodValidator } from '@tanstack/zod-adapter' +import { z } from 'zod' + +const productSearchSchema = z.object({ + page: fallback(z.number(), 1).default(1), + filter: fallback(z.string(), '').default(''), + sort: fallback(z.enum(['newest', 'oldest', 'price']), 'newest').default( + 'newest', + ), +}) + +export const Route = createFileRoute('/shop/products/')({ + validateSearch: zodValidator(productSearchSchema), +}) +``` + +Therefore when navigating to this route, `search` is optional and retains the correct +types. + +While not recommended, it is also possible to configure `input` and `output` type in case +the `output` type is more accurate than the `input` type + +```tsx +const productSearchSchema = z.object({ + page: fallback(z.number(), 1).default(1), + filter: fallback(z.string(), '').default(''), + sort: fallback(z.enum(['newest', 'oldest', 'price']), 'newest').default( + 'newest', + ), +}) + +export const Route = createFileRoute('/shop/products/')({ + validateSearch: zodValidator({ + schema: productSearchSchema, + input: 'output', + output: 'input', + }), +}) +``` + +This provides flexibility in which type you want to infer for navigation and which types +you want to infer for reading search params. + +### Valibot + +> [!WARNING] +> Router expects the valibot 1.0 package to be installed. + +When using [Valibot](https://valibot.dev/) an adapter is not needed to ensure the correct +`input` and `output` types are used for navigation and reading search params. This is +because `valibot` +implements [Standard Schema](https://github.com/standard-schema/standard-schema) + +```tsx +import { createFileRoute } from '@tanstack/react-router' +import * as v from 'valibot' + +const productSearchSchema = v.object({ + page: v.optional(v.fallback(v.number(), 1), 1), + filter: v.optional(v.fallback(v.string(), ''), ''), + sort: v.optional( + v.fallback(v.picklist(['newest', 'oldest', 'price']), 'newest'), + 'newest', + ), +}) + +export const Route = createFileRoute('/shop/products/')({ + validateSearch: productSearchSchema, +}) +``` + +### Arktype + +> [!WARNING] +> Router expects the arktype 2.0-rc package to be installed. + +When using [ArkType](https://arktype.io/) an adapter is not needed to ensure the correct +`input` and `output` types are used for navigation and reading search params. This is +because [ArkType](https://arktype.io/) +implements [Standard Schema](https://github.com/standard-schema/standard-schema) + +```tsx +import { createFileRoute } from '@tanstack/react-router' +import { type } from 'arktype' + +const productSearchSchema = type({ + page: 'number = 1', + filter: 'string = ""', + sort: '"newest" | "oldest" | "price" = "newest"', +}) + +export const Route = createFileRoute('/shop/products/')({ + validateSearch: productSearchSchema, +}) +``` + +### Effect/Schema + +When using [Effect/Schema](https://effect.website/docs/schema/introduction/) an adapter +is not needed to ensure the correct `input` and `output` types are used for navigation +and reading search params. This is +because [Effect/Schema](https://effect.website/docs/schema/standard-schema/) +implements [Standard Schema](https://github.com/standard-schema/standard-schema) + +```tsx +import { createFileRoute } from '@tanstack/react-router' +import { Schema as S } from 'effect' + +const productSearchSchema = S.standardSchemaV1( + S.Struct({ + page: S.NumberFromString.pipe( + S.optional, + S.withDefaults({ + constructor: () => 1, + decoding: () => 1, + }), + ), + filter: S.String.pipe( + S.optional, + S.withDefaults({ + constructor: () => '', + decoding: () => '', + }), + ), + sort: S.Literal('newest', 'oldest', 'price').pipe( + S.optional, + S.withDefaults({ + constructor: () => 'newest' as const, + decoding: () => 'newest' as const, + }), + ), + }), +) + +export const Route = createFileRoute('/shop/products/')({ + validateSearch: productSearchSchema, +}) +``` + +## Reading Search Params + +Once your search params have been validated and typed, you're finally ready to start +reading and writing to them. There are a few ways to do this in TanStack Router, so let's +check them out. + +### Using Search Params in Loaders + +Please read +the [Search Params in Loaders](../data-loading.md#using-loaderdeps-to-access-search-params) +section for more information about how to read search params in loaders with the +`loaderDeps` option. + +### Search Params are inherited from Parent Routes + +The search parameters and types of parents are merged as you go down the route tree, so +child routes also have access to their parent's search params: + +- `shop.products.tsx` + +```tsx +const productSearchSchema = z.object({ + page: z.number().catch(1), + filter: z.string().catch(''), + sort: z.enum(['newest', 'oldest', 'price']).catch('newest'), +}) + +type ProductSearch = z.infer + +export const Route = createFileRoute('/shop/products')({ + validateSearch: productSearchSchema, +}) +``` + +- `shop.products.$productId.tsx` + +```tsx +export const Route = createFileRoute('/shop/products/$productId')({ + beforeLoad: ({ search }) => { + search + // ^? ProductSearch ✅ + }, +}) +``` + +### Search Params in Components + +You can access your route's validated search params in your route's `component` via the +`useSearch` hook. + +```tsx +// /routes/shop.products.tsx + +export const Route = createFileRoute('/shop/products')({ + validateSearch: productSearchSchema, +}) + +const ProductList = () => { + const { page, filter, sort } = Route.useSearch() + + return
...
+} +``` + +> [!TIP] +> If your component is code-split, you can use +> the [getRouteApi function](../code-splitting.md#manually-accessing-route-apis-in-other-files-with-the-getrouteapi-helper) +> to avoid having to import the `Route` configuration to get access to the typed +`useSearch()` hook. + +### Search Params outside of Route Components + +You can access your route's validated search params anywhere in your app using the +`useSearch` hook. By passing the `from` id/path of your origin route, you'll get even +better type safety: + +```tsx +// /routes/shop.products.tsx +export const Route = createFileRoute('/shop/products')({ + validateSearch: productSearchSchema, + // ... +}) + +// Somewhere else... + +// /components/product-list-sidebar.tsx +const routeApi = getRouteApi('/shop/products') + +const ProductList = () => { + const routeSearch = routeApi.useSearch() + + // OR + + const { page, filter, sort } = useSearch({ + from: Route.fullPath, + }) + + return
...
+} +``` + +Or, you can loosen up the type-safety and get an optional `search` object by passing +`strict: false`: + +```tsx +function ProductList() { + const search = useSearch({ + strict: false, + }) + // { + // page: number | undefined + // filter: string | undefined + // sort: 'newest' | 'oldest' | 'price' | undefined + // } + + return
...
+} +``` + +## Writing Search Params + +Now that you've learned how to read your route's search params, you'll be happy to know +that you've already seen the primary APIs to modify and update them. Let's remind +ourselves a bit + +### `` + +The best way to update search params is to use the `search` prop on the `` +component. + +If the search for the current page shall be updated and the `from` prop is specified, the +`to` prop can be omitted. +Here's an example: + +```tsx +// /routes/shop.products.tsx +export const Route = createFileRoute('/shop/products')({ + validateSearch: productSearchSchema, +}) + +const ProductList = () => { + return ( +
+ ({ page: prev.page + 1 })}> + Next Page + +
+ ) +} +``` + +If you want to update the search params in a generic component that is rendered on +multiple routes, specifying `from` can be challenging. + +In this scenario you can set `to="."` which will give you access to loosely typed search +params. +Here is an example that illustrates this: + +```tsx +// `page` is a search param that is defined in the __root route and hence available on all routes. +const PageSelector = () => { + return ( +
+ ({ ...prev, page: prev.page + 1 })}> + Next Page + +
+ ) +} +``` + +If the generic component is only rendered in a specific subtree of the route tree, you +can specify that subtree using `from`. Here you can omit `to='.'` if you want. + +```tsx +// `page` is a search param that is defined in the /posts route and hence available on all of its child routes. +const PageSelector = () => { + return ( +
+ ({ ...prev, page: prev.page + 1 })} + > + Next Page + +
+ ) +``` + +### `useNavigate(), navigate({ search })` + +The `navigate` function also accepts a `search` option that works the same way as the +`search` prop on ``: + +```tsx +// /routes/shop.products.tsx +export const Route = createFileRoute('/shop/products/$productId')({ + validateSearch: productSearchSchema, +}) + +const ProductList = () => { + const navigate = useNavigate({ from: Route.fullPath }) + + return ( +
+ +
+ ) +} +``` + +### `router.navigate({ search })` + +The `router.navigate` function works exactly the same way as the `useNavigate`/`navigate` +hook/function above. + +### `` + +The `` component works exactly the same way as the `useNavigate`/ +`navigate` hook/function above, but accepts its options as props instead of a function +argument. + +## Transforming search with search middlewares + +When link hrefs are built, by default the only thing that matters for the query string +part is the `search` property of a ``. + +TanStack Router provides a way to manipulate search params before the href is generated +via **search middlewares**. +Search middlewares are functions that transform the search parameters when generating new +links for a route or its descendants. +They are also executed upon navigation after search validation to allow manipulation of +the query string. + +The following example shows how to make sure that for **every** link that is being built, +the `rootValue` search param is added _if_ it is part of the current search params. If a +link specifies `rootValue` inside `search`, then that value is used for building the +link. + +```tsx +import { z } from 'zod' +import { createFileRoute } from '@tanstack/react-router' +import { zodValidator } from '@tanstack/zod-adapter' + +const searchSchema = z.object({ + rootValue: z.string().optional(), +}) + +export const Route = createRootRoute({ + validateSearch: zodValidator(searchSchema), + search: { + middlewares: [ + ({ search, next }) => { + const result = next(search) + return { + rootValue: search.rootValue, + ...result, + } + }, + ], + }, +}) +``` + +Since this specific use case is quite common, TanStack Router provides a generic +implementation to retain search params via `retainSearchParams`: + +```tsx +import { z } from 'zod' +import { createFileRoute, retainSearchParams } from '@tanstack/react-router' +import { zodValidator } from '@tanstack/zod-adapter' + +const searchSchema = z.object({ + rootValue: z.string().optional(), +}) + +export const Route = createRootRoute({ + validateSearch: zodValidator(searchSchema), + search: { + middlewares: [retainSearchParams(['rootValue'])], + }, +}) +``` + +Another common use case is to strip out search params from links if their default value +is set. TanStack Router provides a generic implementation for this use case via +`stripSearchParams`: + +```tsx +import { z } from 'zod' +import { createFileRoute, stripSearchParams } from '@tanstack/react-router' +import { zodValidator } from '@tanstack/zod-adapter' + +const defaultValues = { + one: 'abc', + two: 'xyz', +} + +const searchSchema = z.object({ + one: z.string().default(defaultValues.one), + two: z.string().default(defaultValues.two), +}) + +export const Route = createFileRoute('/hello')({ + validateSearch: zodValidator(searchSchema), + search: { + // strip default values + middlewares: [stripSearchParams(defaultValues)], + }, +}) +``` + +Multiple middlewares can be chained. The following example shows how to combine both +`retainSearchParams` and `stripSearchParams`. + +```tsx +import { + Link, + createFileRoute, + retainSearchParams, + stripSearchParams, +} from '@tanstack/react-router' +import { z } from 'zod' +import { zodValidator } from '@tanstack/zod-adapter' + +const defaultValues = ['foo', 'bar'] + +export const Route = createFileRoute('/search')({ + validateSearch: zodValidator( + z.object({ + retainMe: z.string().optional(), + arrayWithDefaults: z.string().array().default(defaultValues), + required: z.string(), + }), + ), + search: { + middlewares: [ + retainSearchParams(['retainMe']), + stripSearchParams({ arrayWithDefaults: defaultValues }), + ], + }, +}) +``` + +# SSR + +> [!WARNING] +> While every effort has been made to separate these APIs from changes to Tanstack Start, +> there are underlying shared implementations internally. Therefore these can be subject to +> change and should be regarded as experimental until Start reaches stable status. + +Server Side Rendering (SSR) is the process of rendering a component on the server and +sending the HTML markup to the client. The client then hydrates the markup into a fully +interactive component. + +There are usually two different flavors of SSR to be considered: + +- Non-streaming SSR + - The entire page is rendered on the server and sent to the client in one single HTML + request, including the serialized data the application needs to hydrate on the + client. +- Streaming SSR + - The critical first paint of the page is rendered on the server and sent to the + client in one single HTML request, including the serialized data the application + needs to hydrate on the client + - The rest of the page is then streamed to the client as it is rendered on the + server. + +This guide will explain how to implement both flavors of SSR with TanStack Router! + +## Non-Streaming SSR + +Non-Streaming server-side rendering is the classic process of rendering the markup for +your entire application page on the server and sending the completed HTML markup (and +data) to the client. The client then hydrates the markup into a fully interactive +application again. + +To implement non-streaming SSR with TanStack Router, you will need the following +utilities: + +- `RouterClient` from `@tanstack/react-router` + - e.g. `` + - Rendering this component in your client entry will render your application and also + automatically implement the `Wrap` component option on `Router` +- And, either: + - `defaultRenderHandler` from `@tanstack/react-router` + - This will render your application in your server entry and also automatically + handle application-level hydration/dehydration and also automatically implement + the RouterServer component. + or: + - `renderRouterToString` from `@tanstack/react-router` + - This differs from defaultRenderHandler in that it allows you to manually + specify the `Wrap` component option on `Router` together with any other + providers you may need to wrap it with. + - `RouterServer` from `@tanstack/react-router` + - This implements the `Wrap` component option on `Router` + +### Automatic Server History + +On the client, Router defaults to using an instance of `createBrowserHistory`, which is +the preferred type of history to use on the client. On the server, however, you will want +to use an instance of `createMemoryHistory` instead. This is because +`createBrowserHistory` uses the `window` object, which does not exist on the server. This +is handled automatically for you in the RouterServer component. + +### Automatic Loader Dehydration/Hydration + +Resolved loader data fetched by routes is automatically dehydrated and rehydrated by +TanStack Router so long as you complete the standard SSR steps outlined in this guide. + +⚠️ If you are using deferred data streaming, you will also need to ensure that you have +implemented the [SSR Streaming & Stream Transform](#streaming-ssr) pattern near the end +of this guide. + +For more information on how to utilize data loading, see +the [Data Loading](../data-loading.md) guide. + +### Router Creation + +Since your router will exist both on the server and the client, it's important that you +create your router in a way that is consistent between both of these environments. The +easiest way to do this is to expose a `createRouter` function in a shared file that can +be imported and called by both your server and client entry files. + +```tsx +// src/router.tsx +import { createRouter as createTanstackRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +export function createRouter() { + return createTanstackRouter({ routeTree }) +} + +declare module '@tanstack/react-router' { + interface Register { + router: ReturnType + } +} +``` + +### Rendering the Application on the Server + +Now that you have a router instance that has loaded all of the critical data for the +current URL, you can render your application on the server: + +using `defaultRenderToString` + +```tsx +// src/entry-server.tsx +import { + createRequestHandler, + defaultRenderToString, +} from '@tanstack/react-router/ssr/server' +import { createRouter } from './router' + +export async function render({ request }: { request: Request }) { + const handler = createRequestHandler({ request, createRouter }) + + return await handler(defaultRenderToString) +} +``` + +using `renderRouterToString` + +```tsx +// src/entry-server.tsx +import { + createRequestHandler, + renderRouterToString, + RouterServer, +} from '@tanstack/react-router/ssr/server' +import { createRouter } from './router' + +export function render({ request }: { request: Request }) { + const handler = createRequestHandler({ request, createRouter }) + + return handler(({ request, responseHeaders, router }) => + renderRouterToString({ + request, + responseHeaders, + router, + children: , + }), + ) +} +``` + +NOTE: The createRequestHandler method requires a web api standard Request object, while +the handler method will return a web api standard Response promise. + +Should you be using a server framework like Express that uses its own Request and +Response objects you would need to convert from the one to the other. Please have a look +at the examples for how such an implementation might look like. + +## Rendering the Application on the Client + +On the client, things are much simpler. + +- Create your router instance +- Render your application using the `` component + +[//]: # 'ClientEntryFileExample' + +```tsx +// src/entry-client.tsx +import { hydrateRoot } from 'react-dom/client' +import { RouterClient } from '@tanstack/react-router/ssr/client' +import { createRouter } from './router' + +const router = createRouter() + +hydrateRoot(document, ) +``` + +[//]: # 'ClientEntryFileExample' + +With this setup, your application will be rendered on the server and then hydrated on the +client! + +## Streaming SSR + +Streaming SSR is the most modern flavor of SSR and is the process of continuously and +incrementally sending HTML markup to the client as it is rendered on the server. This is +slightly different from traditional SSR in concept because beyond being able to dehydrate +and rehydrate a critical first paint, markup and data with less priority or slower +response times can be streamed to the client after the initial render, but in the same +request. + +This pattern can be useful for pages that have slow or high-latency data fetching +requirements. For example, if you have a page that needs to fetch data from a third-party +API, you can stream the critical initial markup and data to the client and then stream +the less-critical third-party data to the client as it is resolved. + +> [!NOTE] +> This streaming pattern is all automatic as long as you are using either +`defaultStreamHandler` or `renderRouterToStream`. + +using `defaultStreamHandler` + +```tsx +// src/entry-server.tsx +import { + createRequestHandler, + defaultStreamHandler, +} from '@tanstack/react-router/ssr/server' +import { createRouter } from './router' + +export async function render({ request }: { request: Request }) { + const handler = createRequestHandler({ request, createRouter }) + + return await handler(defaultStreamHandler) +} +``` + +using `renderRouterToStream` + +```tsx +// src/entry-server.tsx +import { + createRequestHandler, + renderRouterToStream, + RouterServer, +} from '@tanstack/react-router/ssr/server' +import { createRouter } from './router' + +export function render({ request }: { request: Request }) { + const handler = createRequestHandler({ request, createRouter }) + + return handler(({ request, responseHeaders, router }) => + renderRouterToStream({ + request, + responseHeaders, + router, + children: , + }), + ) +} +``` + +## Streaming Dehydration/Hydration + +Streaming dehydration/hydration is an advanced pattern that goes beyond markup and allows +you to dehydrate and stream any supporting data from the server to the client and +rehydrate it on arrival. This is useful for applications that may need to further +use/manage the underlying data that was used to render the initial markup on the server. + +## Data Serialization + +When using SSR, data passed between the server and the client must be serialized before +it is sent across network-boundaries. TanStack Router handles this serialization using a +very lightweight serializer that supports common data types beyond +JSON.stringify/JSON.parse. + +Out of the box, the following types are supported: + +- `undefined` +- `Date` +- `Error` +- `FormData` + +If you feel that there are other types that should be supported by default, please open +an issue on the TanStack Router repository. + +If you are using more complex data types like `Map`, `Set`, `BigInt`, etc, you may need +to use a custom serializer to ensure that your type-definitions are accurate and your +data is correctly serialized and deserialized. We are currently working on both a more +robust serializer and a way to customize the serializer for your application. Open an +issue if you are interested in helping out! + +# Static Route Data + +When creating routes, you can optionally specify a `staticData` property in the route's +options. This object can literally contain anything you want as long as it's +synchronously available when you create your route. + +In addition to being able to access this data from the route itself, you can also access +it from any match under the `match.staticData` property. + +## Example + +- `posts.tsx` + +```tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts')({ + staticData: { + customData: 'Hello!', + }, +}) +``` + +You can then access this data anywhere you have access to your routes, including matches +that can be mapped back to their routes. + +- `__root.tsx` + +```tsx +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute({ + component: () => { + const matches = useMatches() + + return ( +
+ {matches.map((match) => { + return
{match.staticData.customData}
+ })} +
+ ) + }, +}) +``` + +## Enforcing Static Data + +If you want to enforce that a route has static data, you can use declaration merging to +add a type to the route's static option: + +```tsx +declare module '@tanstack/react-router' { + interface StaticDataRouteOption { + customData: string + } +} +``` + +Now, if you try to create a route without the `customData` property, you'll get a type +error: + +```tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts')({ + staticData: { + // Property 'customData' is missing in type '{ customData: number; }' but required in type 'StaticDataRouteOption'.ts(2741) + }, +}) +``` + +## Optional Static Data + +If you want to make static data optional, simply add a `?` to the property: + +```tsx +declare module '@tanstack/react-router' { + interface StaticDataRouteOption { + customData?: string + } +} +``` + +As long as there are any required properties on the `StaticDataRouteOption`, you'll be +required to pass in an object. + +# TanStack Start + +TanStack Start is a full-stack framework for building server-rendered React applications +built on top of [TanStack Router](https://tanstack.com/router). + +To set up a TanStack Start project, you'll need to: + +1. Install the dependencies +2. Add a configuration file +3. Create required templating + +Follow this guide to build a basic TanStack Start web application. Together, we will use +TanStack Start to: + +- Serve an index page... +- Which displays a counter... +- With a button to increment the counter persistently. + +[Here is what that will look like](https://stackblitz.com/github/tanstack/router/tree/main/examples/react/start-basic-counter) + +Create a new project if you're starting fresh. + +```shell +mkdir myApp +cd myApp +npm init -y +``` + +Create a `tsconfig.json` file with at least the following settings: + +```jsonc +{ + "compilerOptions": { + "jsx": "react-jsx", + "moduleResolution": "Bundler", + "module": "Preserve", + "target": "ES2022", + "skipLibCheck": true, + }, +} +``` + +# Install Dependencies + +TanStack Start is powered by the following packages and need to be installed as +dependencies: + +- [@tanstack/start](https://github.com/tanstack/start) +- [@tanstack/react-router](https://tanstack.com/router) +- [Vite](https://vite.dev/) + +To install them, run: + +```shell +npm i @tanstack/react-start @tanstack/react-router vite +``` + +You'll also need React and the Vite React plugin, so install their dependencies as well: + +```shell +npm i react react-dom @vitejs/plugin-react +``` + +Please, for you, your fellow developers, and your users' sake, use TypeScript: + +```shell +npm i -D typescript @types/react @types/react-dom +``` + +# Update Configuration Files + +We'll then update our `package.json` to use Vite's CLI and set `"type": "module"`: + +```jsonc +{ + // ... + "type": "module", + "scripts": { + "dev": "vite dev", + "build": "vite build", + "start": "vite start", + }, +} +``` + +Then configure TanStack Start's `app.config.ts` file: + +```typescript +// app.config.ts +import { defineConfig } from '@tanstack/react-start/config' + +export default defineConfig({}) +``` + +# Add the Basic Templating + +There are 2 required files for TanStack Start usage: + +1. The router configuration +2. The root of your application + +Once configuration is done, we'll have a file tree that looks like the following: + +``` +. +├── app/ +│ ├── routes/ +│ │ └── `__root.tsx` +│ ├── `router.tsx` +│ ├── `routeTree.gen.ts` +├── `.gitignore` +├── `app.config.ts` +├── `package.json` +└── `tsconfig.json` +``` + +## The Router Configuration + +This is the file that will dictate the behavior of TanStack Router used within Start for +both the server and the client. Here, you can configure everything +from the default [preloading functionality](../preloading.md) +to [caching staleness](../data-loading.md). + +```tsx +// app/router.tsx +import { createRouter as createTanStackRouter } from '@tanstack/react-router' +import { routeTree } from './routeTree.gen' + +export function createRouter() { + const router = createTanStackRouter({ + routeTree, + }) + + return router +} + +declare module '@tanstack/react-router' { + interface Register { + router: ReturnType + } +} +``` + +> `routeTree.gen.ts` is not a file you're expected to have at this point. +> It will be generated when you run TanStack Start (via `npm run dev` or `npm run start`) +> for the first time. + +## The Root of Your Application + +Finally, we need to create the root of our application. This is the entry point for all +application routes. The code in this file will wrap all other routes in the application. + +```tsx +// app/routes/__root.tsx +import { createRootRoute, HeadContent, Scripts } from '@tanstack/react-router' +import { Outlet } from '@tanstack/react-router' +import * as React from 'react' + +export const Route = createRootRoute({ + head: () => ({ + meta: [ + { + charSet: 'utf-8', + }, + { + name: 'viewport', + content: 'width=device-width, initial-scale=1', + }, + { + title: 'TanStack Start Starter', + }, + ], + }), + component: RootComponent, +}) + +function RootComponent() { + return ( + + + + ) +} + +function RootDocument({ children }: { children: React.ReactNode }) { + return ( + + + + + + {children} + + + + ) +} +``` + +# Writing Your First Route + +Now that we have the basic templating setup, we can write our first route. This is done +by creating a new file in the `app/routes` directory. + +```tsx +// app/routes/index.tsx +import * as fs from 'fs' +import { createFileRoute, useRouter } from '@tanstack/react-router' +import { createServerFn } from '@tanstack/react-start' + +const filePath = 'count.txt' + +async function readCount() { + return parseInt( + await fs.promises.readFile(filePath, 'utf-8').catch(() => '0'), + ) +} + +const getCount = createServerFn({ + method: 'GET', +}).handler(() => { + return readCount() +}) + +const updateCount = createServerFn({ method: 'POST' }) + .validator((d: number) => d) + .handler(async ({ data }) => { + const count = await readCount() + await fs.promises.writeFile(filePath, `${count + data}`) + }) + +export const Route = createFileRoute('/')({ + component: Home, + loader: async () => await getCount(), +}) + +function Home() { + const router = useRouter() + const state = Route.useLoaderData() + + return ( + + ) +} +``` + +That's it! 🤯 You've now set up a TanStack Start project and written your first route. 🎉 + +You can now run `npm run dev` to start your server and navigate to +`http://localhost:3000` to see your route in action. + +# Type Safety + +TanStack Router is built to be as type-safe as possible within the limits of the +TypeScript compiler and runtime. This means that it's not only written in TypeScript, but +that it also **fully infers the types it's provided and tenaciously pipes them through +the entire routing experience**. + +Ultimately, this means that you write **less types as a developer** and have **more +confidence in your code** as it evolves. + +## Route Definitions + +### File-based Routing + +Routes are hierarchical, and so are their definitions. If you're using file-based +routing, much of the type-safety is already taken care of for you. + +### Code-based Routing + +If you're using the `Route` class directly, you'll need to be aware of how to ensure your +routes are typed properly using the `Route`'s `getParentRoute` option. This is because +child routes need to be aware of **all** of their parent routes types. Without this, +those precious search params you parsed out of your _layout_ and _pathless layout_ +routes, 3 levels up, would be lost to the JS void. + +So, don't forget to pass the parent route to your child routes! + +```tsx +const parentRoute = createRoute({ + getParentRoute: () => parentRoute, +}) +``` + +## Exported Hooks, Components, and Utilities + +For the types of your router to work with top-level exports like `Link`, `useNavigate`, +`useParams`, etc. they must permeate the type-script module boundary and be registered +right into the library. To do this, we use declaration merging on the exported `Register` +interface. + +```ts +const router = createRouter({ + // ... +}) + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} +``` + +By registering your router with the module, you can now use the exported hooks, +components, and utilities with your router's exact types. + +## Fixing the Component Context Problem + +Component context is a wonderful tool in React and other frameworks for providing +dependencies to components. However, if that context is changing types as it moves +throughout your component hierarchy, it becomes impossible for TypeScript to know how to +infer those changes. To get around this, context-based hooks and components require that +you give them a hint on how and where they are being used. + +```tsx +export const Route = createFileRoute('/posts')({ + component: PostsComponent, +}) + +function PostsComponent() { + // Each route has type-safe versions of most of the built-in hooks from TanStack Router + const params = Route.useParams() + const search = Route.useSearch() + + // Some hooks require context from the *entire* router, not just the current route. To achieve type-safety here, + // we must pass the `from` param to tell the hook our relative position in the route hierarchy. + const navigate = useNavigate({ from: Route.fullPath }) + // ... etc +} +``` + +Every hook and component that requires a context hint will have a `from` param where you +can pass the ID or path of the route you are rendering within. + +> 🧠 Quick tip: If your component is code-split, you can use +> the [getRouteApi function](../code-splitting.md#manually-accessing-route-apis-in-other-files-with-the-getrouteapi-helper) +> to avoid having to pass in the `Route.fullPath` to get access to the typed `useParams()` +> and `useSearch()` hooks. + +### What if I don't know the route? What if it's a shared component? + +The `from` property is optional, which means if you don't pass it, you'll get the router' +s best guess on what types will be available. Usually, that means you'll get a union of +all of the types of all of the routes in the router. + +### What if I pass the wrong `from` path? + +It's technically possible to pass a `from` that satisfies TypeScript, but may not match +the actual route you are rendering within at runtime. In this case, each hook and +component that supports `from` will detect if your expectations don't match the actual +route you are rendering within, and will throw a runtime error. + +### What if I don't know the route, or it's a shared component, and I can't pass `from`? + +If you are rendering a component that is shared across multiple routes, or you are +rendering a component that is not within a route, you can pass `strict: false` instead of +a `from` option. This will not only silence the runtime error, but will also give you +relaxed, but accurate types for the potential hook you are calling. A good example of +this is calling `useSearch` from a shared component: + +```tsx +function MyComponent() { + const search = useSearch({ strict: false }) +} +``` + +In this case, the `search` variable will be typed as a union of all possible search +params from all routes in the router. + +## Router Context + +Router context is so extremely useful as it's the ultimate hierarchical dependency +injection. You can supply context to the router and to each and every route it renders. +As you build up this context, TanStack Router will merge it down with the hierarchy of +routes, so that each route has access to the context of all of its parents. + +The `createRootRouteWithContext` factory creates a new router with the instantiated type, +which then creates a requirement for you to fulfill the same type contract to your +router, and will also ensure that your context is properly typed throughout the entire +route tree. + +```tsx +const rootRoute = createRootRouteWithContext<{ whateverYouWant: true }>()({ + component: App, +}) + +const routeTree = rootRoute.addChildren([ + // ... all child routes will have access to `whateverYouWant` in their context +]) + +const router = createRouter({ + routeTree, + context: { + // This will be required to be passed now + whateverYouWant: true, + }, +}) +``` + +## Performance Recommendations + +As your application scales, TypeScript check times will naturally increase. There are a +few things to keep in mind when your application scales to keep your TS check times down. + +### Only infer types you need + +A great pattern with client side data caches (TanStack Query, etc.) is to prefetch data. +For example with TanStack Query you might have a route which calls +`queryClient.ensureQueryData` in a `loader`. + +```tsx +export const Route = createFileRoute('/posts/$postId/deep')({ + loader: ({ context: { queryClient }, params: { postId } }) => + queryClient.ensureQueryData(postQueryOptions(postId)), + component: PostDeepComponent, +}) + +function PostDeepComponent() { + const params = Route.useParams() + const data = useSuspenseQuery(postQueryOptions(params.postId)) + + return <> +} +``` + +This may look fine and for small route trees and you may not notice any TS performance +issues. However in this case TS has to infer the loader's return type, despite it never +being used in your route. If the loader data is a complex type with many routes that +prefetch in this manner, it can slow down editor performance. In this case, the change is +quite simple and let typescript infer Promise. + +```tsx +export const Route = createFileRoute('/posts/$postId/deep')({ + loader: async ({ context: { queryClient }, params: { postId } }) => { + await queryClient.ensureQueryData(postQueryOptions(postId)) + }, + component: PostDeepComponent, +}) + +function PostDeepComponent() { + const params = Route.useParams() + const data = useSuspenseQuery(postQueryOptions(params.postId)) + + return <> +} +``` + +This way the loader data is never inferred and it moves the inference out of the route +tree to the first time you use `useSuspenseQuery`. + +### Narrow to relevant routes as much as you possibly can + +Consider the following usage of `Link` + +```tsx + + +``` + +**These examples are bad for TS performance**. That's because `search` resolves to a +union of all `search` params for all routes and TS has to check whatever you pass to the +`search` prop against this potentially big union. As your application grows, this check +time will increase linearly to number of routes and search params. We have done our best +to optimize for this case (TypeScript will typically do this work once and cache it) but +the initial check against this large union is expensive. This also applies to `params` +and other API's such as `useSearch`, `useParams`, `useNavigate` etc. + +Instead you should try to narrow to relevant routes with `from` or `to`. + +```tsx + + +``` + +Remember you can always pass a union to `to` or `from` to narrow the routes you're +interested in. + +```tsx +const from: '/posts/$postId/deep' | '/posts/' = '/posts/' + +``` + +You can also pass branches to `from` to only resolve `search` or `params` to be from any +descendants of that branch: + +```tsx +const from = '/posts' + +``` + +`/posts` could be a branch with many descendants which share the same `search` or +`params` + +### Consider using the object syntax of `addChildren` + +It's typical of routes to have `params` `search`, `loaders` or `context` that can even +reference external dependencies which are also heavy on TS inference. For such +applications, using objects for creating the route tree can be more performant than +tuples. + +`createChildren` also can accept an object. For large route trees with complex routes and +external libraries, objects can be much faster for TS to type check as opposed to large +tuples. The performance gains depend on your project, what external dependencies you have +and how the types for those libraries are written + +```tsx +const routeTree = rootRoute.addChildren({ + postsRoute: postsRoute.addChildren({ postRoute, postsIndexRoute }), + indexRoute, +}) +``` + +Note this syntax is more verbose but has better TS performance. With file based routing, +the route tree is generated for you so a verbose route tree is not a concern + +### Avoid internal types without narrowing + +It's common you might want to re-use types exposed. For example you might be tempted to +use `LinkProps` like so + +```tsx +const props: LinkProps = { + to: '/posts/', +} + +return ( + +) +``` + +**This is VERY bad for TS Performance**. The problem here is `LinkProps` has no type +arguments and is therefore an extremely large type. It includes `search` which is a union +of all `search` params, it contains `params` which is a union of all `params`. When +merging this object with `Link` it will do a structural comparison of this huge type. + +Instead you can use `as const satisfies` to infer a precise type and not `LinkProps` +directly to avoid the huge check + +```tsx +const props = { + to: '/posts/', +} as const satisfies LinkProps + +return ( + +) +``` + +As `props` is not of type `LinkProps` and therefore this check is cheaper because the +type is much more precise. You can also improve type checking further by narrowing +`LinkProps` + +```tsx +const props = { + to: '/posts/', +} as const satisfies LinkProps + +return ( + +) +``` + +This is even faster as we're checking against the narrowed `LinkProps` type. + +You can also use this to narrow the type of `LinkProps` to a specific type to be used as +a prop or parameter to a function + +```tsx +export const myLinkProps = [ + { + to: '/posts', + }, + { + to: '/posts/$postId', + params: { postId: 'postId' }, + }, +] as const satisfies ReadonlyArray + +export type MyLinkProps = (typeof myLinkProps)[number] + +const MyComponent = (props: { linkProps: MyLinkProps }) => { + return +} +``` + +This is faster than using `LinkProps` directly in a component because `MyLinkProps` is a +much more precise type + +Another solution is not to use `LinkProps` and to provide inversion of control to render +a `Link` component narrowed to a specific route. Render props are a good method of +inverting control to the user of a component + +```tsx +export interface MyComponentProps { + readonly renderLink: () => React.ReactNode +} + +const MyComponent = (props: MyComponentProps) => { + return
{props.renderLink()}
+} + +const Page = () => { + return } /> +} +``` + +This particular example is very fast as we've inverted control of where we're navigating +to the user of the component. The `Link` is narrowed to the exact route +we want to navigate to + +# Type Utilities + +Most types exposed by TanStack Router are internal, subject to breaking changes and not +always easy to use. That is why TanStack Router has a subset of exposed types focused on +ease of use with the intension to be used externally. These types provide the same type +safe experience from TanStack Router's runtime concepts on the type level, with +flexibility of where to provide type checking + +## Type checking Link options with `ValidateLinkOptions` + +`ValidateLinkOptions` type checks object literal types to ensure they conform to `Link` +options at inference sites. For example, you may have a generic `HeadingLink` component +which accepts a `title` prop along with `linkOptions`, the idea being this component can +be re-used for any navigation. + +```tsx +export interface HeaderLinkProps< + TRouter extends RegisteredRouter = RegisteredRouter, + TOptions = unknown, +> { + title: string + linkOptions: ValidateLinkOptions +} + +export function HeadingLink( + props: HeaderLinkProps, +): React.ReactNode +export function HeadingLink(props: HeaderLinkProps): React.ReactNode { + return ( + <> +

{props.title}

+ + + ) +} +``` + +A more permissive overload of `HeadingLink` is used to avoid type assertions you would +otherwise have to do with the generic signature. Using a looser signature without type +parameters is an easy way to avoid type assertions in the implementation of `HeadingLink` + +All type parameters for utilities are optional but for the best TypeScript performance +`TRouter` should always be specified for the public facing signature. And `TOptions` +should always be used at inference sites like `HeadingLink` to infer the `linkOptions` to +correctly narrow `params` and `search` + +The result of this is that `linkOptions` in the following is completely type-safe + +```tsx + + +``` + +## Type checking an array of Link options with `ValidateLinkOptionsArray` + +All navigation type utilities have an array variant. `ValidateLinkOptionsArray` enables +type checking of an array of `Link` options. For example, you might have a generic `Menu` +component where each item is a `Link`. + +```tsx +export interface MenuProps< + TRouter extends RegisteredRouter = RegisteredRouter, + TItems extends ReadonlyArray = ReadonlyArray, +> { + items: ValidateLinkOptionsArray +} + +export function Menu< + TRouter extends RegisteredRouter = RegisteredRouter, + TItems extends ReadonlyArray, +>(props: MenuProps): React.ReactNode +export function Menu(props: MenuProps): React.ReactNode { + return ( +
    + {props.items.map((item) => ( +
  • + +
  • + ))} +
+ ) +} +``` + +This of course allows the following `items` prop to be completely type-safe + +```tsx +
+``` + +It is also possible to fix `from` for each `Link` options in the array. This would allow +all `Menu` items to navigate relative to `from`. Additional type checking of `from` can +be provided by the `ValidateFromPath` utility + +```tsx +export interface MenuProps< + TRouter extends RegisteredRouter = RegisteredRouter, + TItems extends ReadonlyArray = ReadonlyArray, + TFrom extends string = string, +> { + from: ValidateFromPath + items: ValidateLinkOptionsArray +} + +export function Menu< + TRouter extends RegisteredRouter = RegisteredRouter, + TItems extends ReadonlyArray, + TFrom extends string = string, +>(props: MenuProps): React.ReactNode +export function Menu(props: MenuProps): React.ReactNode { + return ( +
    + {props.items.map((item) => ( +
  • + +
  • + ))} +
+ ) +} +``` + +`ValidateLinkOptionsArray` allows you to fix `from` by providing an extra type parameter. +The result is a type safe array of `Link` options providing navigation relative to `from` + +```tsx +
+``` + +## Type checking redirect options with `ValidateRedirectOptions` + +`ValidateRedirectOptions` type checks object literal types to ensure they conform to +redirect options at inference sites. For example, you may need a generic +`fetchOrRedirect` function which accepts a `url` along with `redirectOptions`, the idea +being this function will redirect when the `fetch` fails. + +```tsx +export async function fetchOrRedirect< + TRouter extends RegisteredRouter = RegisteredRouter, + TOptions, +>( + url: string, + redirectOptions: ValidateRedirectOptions, +): Promise +export async function fetchOrRedirect( + url: string, + redirectOptions: ValidateRedirectOptions, +): Promise { + const response = await fetch(url) + + if (!response.ok && response.status === 401) { + throw redirect(redirectOptions) + } + + return await response.json() +} +``` + +The result is that `redirectOptions` passed to `fetchOrRedirect` is completely type-safe + +```tsx +fetchOrRedirect('http://example.com/', { to: '/login' }) +``` + +## Type checking navigate options with `ValidateNavigateOptions` + +`ValidateNavigateOptions` type checks object literal types to ensure they conform to +navigate options at inference sites. For example, you may want to write a custom hook to +enable/disable navigation. + +[//]: # 'TypeCheckingNavigateOptionsWithValidateNavigateOptionsImpl' + +```tsx +export interface UseConditionalNavigateResult { + enable: () => void + disable: () => void + navigate: () => void +} + +export function useConditionalNavigate< + TRouter extends RegisteredRouter = RegisteredRouter, + TOptions, +>( + navigateOptions: ValidateNavigateOptions, +): UseConditionalNavigateResult +export function useConditionalNavigate( + navigateOptions: ValidateNavigateOptions, +): UseConditionalNavigateResult { + const [enabled, setEnabled] = useState(false) + const navigate = useNavigate() + return { + enable: () => setEnabled(true), + disable: () => setEnabled(false), + navigate: () => { + if (enabled) { + navigate(navigateOptions) + } + }, + } +} +``` + +[//]: # 'TypeCheckingNavigateOptionsWithValidateNavigateOptionsImpl' + +The result of this is that `navigateOptions` passed to `useConditionalNavigate` is +completely type-safe and we can enable/disable navigation based on react state + +```tsx +const { enable, disable, navigate } = useConditionalNavigate({ + to: '/posts/$postId', + params: { postId: 'postId' }, +}) +``` + + + +<@tanstack/react-router_routing> +Always Apply: false - This rule should only be applied when relevant files are open +Always apply this rule in these files: src/**/*.ts, src/**/*.tsx + +# Code-Based Routing + +> [!TIP] +> Code-based routing is not recommended for most applications. It is recommended to +> use [File-Based Routing](../file-based-routing.md) instead. + +## ⚠️ Before You Start + +- If you're using [File-Based Routing](../file-based-routing.md), **skip this guide**. +- If you still insist on using code-based routing, you must read + the [Routing Concepts](../routing-concepts.md) guide first, as it also covers core + concepts of the router. + +## Route Trees + +Code-based routing is no different from file-based routing in that it uses the same route +tree concept to organize, match and compose matching routes into a component tree. The +only difference is that instead of using the filesystem to organize your routes, you use +code. + +Let's consider the same route tree from +the [Route Trees & Nesting](../route-trees.md#route-trees) guide, and convert it to +code-based routing: + +Here is the file-based version: + +``` +routes/ +├── __root.tsx +├── index.tsx +├── about.tsx +├── posts/ +│ ├── index.tsx +│ ├── $postId.tsx +├── posts.$postId.edit.tsx +├── settings/ +│ ├── profile.tsx +│ ├── notifications.tsx +├── _pathlessLayout.tsx +├── _pathlessLayout/ +│ ├── route-a.tsx +├── ├── route-b.tsx +├── files/ +│ ├── $.tsx +``` + +And here is a summarized code-based version: + +```tsx +import { createRootRoute, createRoute } from '@tanstack/react-router' + +const rootRoute = createRootRoute() + +const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', +}) + +const aboutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'about', +}) + +const postsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'posts', +}) + +const postsIndexRoute = createRoute({ + getParentRoute: () => postsRoute, + path: '/', +}) + +const postRoute = createRoute({ + getParentRoute: () => postsRoute, + path: '$postId', +}) + +const postEditorRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'posts/$postId/edit', +}) + +const settingsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'settings', +}) + +const profileRoute = createRoute({ + getParentRoute: () => settingsRoute, + path: 'profile', +}) + +const notificationsRoute = createRoute({ + getParentRoute: () => settingsRoute, + path: 'notifications', +}) + +const pathlessLayoutRoute = createRoute({ + getParentRoute: () => rootRoute, + id: 'pathlessLayout', +}) + +const pathlessLayoutARoute = createRoute({ + getParentRoute: () => pathlessLayoutRoute, + path: 'route-a', +}) + +const pathlessLayoutBRoute = createRoute({ + getParentRoute: () => pathlessLayoutRoute, + path: 'route-b', +}) + +const filesRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'files/$', +}) +``` + +## Anatomy of a Route + +All other routes other than the root route are configured using the `createRoute` +function: + +```tsx +const route = createRoute({ + getParentRoute: () => rootRoute, + path: '/posts', + component: PostsComponent, +}) +``` + +The `getParentRoute` option is a function that returns the parent route of the route +you're creating. + +**❓❓❓ "Wait, you're making me pass the parent route for every route I make?"** + +Absolutely! The reason for passing the parent route has **everything to do with the +magical type safety** of TanStack Router. Without the parent route, TypeScript would have +no idea what types to supply your route with! + +> [!IMPORTANT] +> For every route that **NOT** the **Root Route** or a **Pathless Layout Route**, a +`path` option is required. This is the path that will be matched against the URL pathname +> to determine if the route is a match. + +When configuring route `path` option on a route, it ignores leading and trailing +slashes (this does not include "index" route paths `/`). You can include them if you +want, but they will be normalized internally by TanStack Router. Here is a table of valid +paths and what they will be normalized to: + +| Path | Normalized Path | +|----------|-----------------| +| `/` | `/` | +| `/about` | `about` | +| `about/` | `about` | +| `about` | `about` | +| `$` | `$` | +| `/$` | `$` | +| `/$/` | `$` | + +## Manually building the route tree + +When building a route tree in code, it's not enough to define the parent route of each +route. You must also construct the final route tree by adding each route to its parent +route's `children` array. This is because the route tree is not built automatically for +you like it is in file-based routing. + +```tsx +/* prettier-ignore */ +const routeTree = rootRoute.addChildren([ + indexRoute, + aboutRoute, + postsRoute.addChildren([ + postsIndexRoute, + postRoute, + ]), + postEditorRoute, + settingsRoute.addChildren([ + profileRoute, + notificationsRoute, + ]), + pathlessLayoutRoute.addChildren([ + pathlessLayoutARoute, + pathlessLayoutBRoute, + ]), + filesRoute.addChildren([ + fileRoute, + ]), +]) +/* prettier-ignore-end */ +``` + +But before you can go ahead and build the route tree, you need to understand how the +Routing Concepts for Code-Based Routing work. + +## Routing Concepts for Code-Based Routing + +Believe it or not, file-based routing is really a superset of code-based routing and uses +the filesystem and a bit of code-generation abstraction on top of it to generate this +structure you see above automatically. + +We're going to assume you've read the [Routing Concepts](../routing-concepts.md) guide +and are familiar with each of these main concepts: + +- The Root Route +- Basic Routes +- Index Routes +- Dynamic Route Segments +- Splat / Catch-All Routes +- Layout Routes +- Pathless Routes +- Non-Nested Routes + +Now, let's take a look at how to create each of these route types in code. + +## The Root Route + +Creating a root route in code-based routing is thankfully the same as doing so in +file-based routing. Call the `createRootRoute()` function. + +Unlike file-based routing however, you do not need to export the root route if you don't +want to. It's certainly not recommended to build an entire route tree and application in +a single file (although you can and we do this in the examples to demonstrate routing +concepts in brevity). + +```tsx +// Standard root route +import { createRootRoute } from '@tanstack/react-router' + +const rootRoute = createRootRoute() + +// Root route with Context +import { createRootRouteWithContext } from '@tanstack/react-router' +import type { QueryClient } from '@tanstack/react-query' + +export interface MyRouterContext { + queryClient: QueryClient +} +const rootRoute = createRootRouteWithContext() +``` + +To learn more about Context in TanStack Router, see +the [Router Context](../../guide/router-context.md) guide. + +## Basic Routes + +To create a basic route, simply provide a normal `path` string to the `createRoute` +function: + +```tsx +const aboutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'about', +}) +``` + +See, it's that simple! The `aboutRoute` will match the URL `/about`. + +## Index Routes + +Unlike file-based routing, which uses the `index` filename to denote an index route, +code-based routing uses a single slash `/` to denote an index route. For example, the +`posts.index.tsx` file from our example route tree above would be represented in +code-based routing like this: + +```tsx +const postsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'posts', +}) + +const postsIndexRoute = createRoute({ + getParentRoute: () => postsRoute, + // Notice the single slash `/` here + path: '/', +}) +``` + +So, the `postsIndexRoute` will match the URL `/posts/` (or `/posts`). + +## Dynamic Route Segments + +Dynamic route segments work exactly the same in code-based routing as they do in +file-based routing. Simply prefix a segment of the path with a `$` and it will be +captured into the `params` object of the route's `loader` or `component`: + +```tsx +const postIdRoute = createRoute({ + getParentRoute: () => postsRoute, + path: '$postId', + // In a loader + loader: ({ params }) => fetchPost(params.postId), + // Or in a component + component: PostComponent, +}) + +function PostComponent() { + const { postId } = postIdRoute.useParams() + return
Post ID: {postId}
+} +``` + +> [!TIP] +> If your component is code-split, you can use +> the [getRouteApi function](../../guide/code-splitting.md#manually-accessing-route-apis-in-other-files-with-the-getrouteapi-helper) +> to avoid having to import the `postIdRoute` configuration to get access to the typed +`useParams()` hook. + +## Splat / Catch-All Routes + +As expected, splat/catch-all routes also work the same in code-based routing as they do +in file-based routing. Simply prefix a segment of the path with a `$` and it will be +captured into the `params` object under the `_splat` key: + +```tsx +const filesRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'files', +}) + +const fileRoute = createRoute({ + getParentRoute: () => filesRoute, + path: '$', +}) +``` + +For the URL `/documents/hello-world`, the `params` object will look like this: + +```js +{ + '_splat': 'documents/hello-world' +} +``` + +## Layout Routes + +Layout routes are routes that wrap their children in a layout component. In code-based +routing, you can create a layout route by simply nesting a route under another route: + +```tsx +const postsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'posts', + component: PostsLayoutComponent, // The layout component +}) + +function PostsLayoutComponent() { + return ( +
+

Posts

+ +
+ ) +} + +const postsIndexRoute = createRoute({ + getParentRoute: () => postsRoute, + path: '/', +}) + +const postsCreateRoute = createRoute({ + getParentRoute: () => postsRoute, + path: 'create', +}) + +const routeTree = rootRoute.addChildren([ + // The postsRoute is the layout route + // Its children will be nested under the PostsLayoutComponent + postsRoute.addChildren([postsIndexRoute, postsCreateRoute]), +]) +``` + +Now, both the `postsIndexRoute` and `postsCreateRoute` will render their contents inside +of the `PostsLayoutComponent`: + +```tsx +// URL: /posts + + + + +// URL: /posts/create + + + +``` + +## Pathless Layout Routes + +In file-based routing a pathless layout route is prefixed with a `_`, but in code-based +routing, this is simply a route with an `id` instead of a `path` option. This is because +code-based routing does not use the filesystem to organize routes, so there is no need to +prefix a route with a `_` to denote that it has no path. + +```tsx +const pathlessLayoutRoute = createRoute({ + getParentRoute: () => rootRoute, + id: 'pathlessLayout', + component: PathlessLayoutComponent, +}) + +function PathlessLayoutComponent() { + return ( +
+

Pathless Layout

+ +
+ ) +} + +const pathlessLayoutARoute = createRoute({ + getParentRoute: () => pathlessLayoutRoute, + path: 'route-a', +}) + +const pathlessLayoutBRoute = createRoute({ + getParentRoute: () => pathlessLayoutRoute, + path: 'route-b', +}) + +const routeTree = rootRoute.addChildren([ + // The pathless layout route has no path, only an id + // So its children will be nested under the pathless layout route + pathlessLayoutRoute.addChildren([pathlessLayoutARoute, pathlessLayoutBRoute]), +]) +``` + +Now both `/route-a` and `/route-b` will render their contents inside of the +`PathlessLayoutComponent`: + +```tsx +// URL: /route-a + + + + +// URL: /route-b + + + +``` + +## Non-Nested Routes + +Building non-nested routes in code-based routing does not require using a trailing `_` in +the path, but does require you to build your route and route tree with the right paths +and nesting. Let's consider the route tree where we want the post editor to **not** be +nested under the posts route: + +- `/posts_/$postId/edit` +- `/posts` + - `$postId` + +To do this we need to build a separate route for the post editor and include the entire +path in the `path` option from the root of where we want the route to be nested (in this +case, the root): + +```tsx +// The posts editor route is nested under the root route +const postEditorRoute = createRoute({ + getParentRoute: () => rootRoute, + // The path includes the entire path we need to match + path: 'posts/$postId/edit', +}) + +const postsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'posts', +}) + +const postRoute = createRoute({ + getParentRoute: () => postsRoute, + path: '$postId', +}) + +const routeTree = rootRoute.addChildren([ + // The post editor route is nested under the root route + postEditorRoute, + postsRoute.addChildren([postRoute]), +]) +``` + +# File-Based Routing + +Most of the TanStack Router documentation is written for file-based routing and is +intended to help you understand in more detail how to configure file-based routing and +the technical details behind how it works. While file-based routing is the preferred and +recommended way to configure TanStack Router, you can also +use [code-based routing](../code-based-routing.md) if you prefer. + +## What is File-Based Routing? + +File-based routing is a way to configure your routes using the filesystem. Instead of +defining your route structure via code, you can define your routes using a series of +files and directories that represent the route hierarchy of your application. This brings +a number of benefits: + +- **Simplicity**: File-based routing is visually intuitive and easy to understand for + both new and experienced developers. +- **Organization**: Routes are organized in a way that mirrors the URL structure of your + application. +- **Scalability**: As your application grows, file-based routing makes it easy to add new + routes and maintain existing ones. +- **Code-Splitting**: File-based routing allows TanStack Router to automatically + code-split your routes for better performance. +- **Type-Safety**: File-based routing raises the ceiling on type-safety by generating + managing type linkages for your routes, which can otherwise be a tedious process via + code-based routing. +- **Consistency**: File-based routing enforces a consistent structure for your routes, + making it easier to maintain and update your application and move from one project to + another. + +## `/`s or `.`s? + +While directories have long been used to represent route hierarchy, file-based routing +introduces an additional concept of using the `.` character in the file-name to denote a +route nesting. This allows you to avoid creating directories for few deeply nested routes +and continue to use directories for wider route hierarchies. Let's take a look at some +examples! + +## Directory Routes + +Directories can be used to denote route hierarchy, which can be useful for organizing +multiple routes into logical groups and also cutting down on the filename length for +large groups of deeply nested routes. + +See the example below: + +| Filename | Route Path | Component Output | +|-------------------------|---------------------------|-----------------------------------| +| ʦ `__root.tsx` | | `` | +| ʦ `index.tsx` | `/` (exact) | `` | +| ʦ `about.tsx` | `/about` | `` | +| ʦ `posts.tsx` | `/posts` | `` | +| 📂 `posts` | | | +| ┄ ʦ `index.tsx` | `/posts` (exact) | `` | +| ┄ ʦ `$postId.tsx` | `/posts/$postId` | `` | +| 📂 `posts_` | | | +| ┄ 📂 `$postId` | | | +| ┄ ┄ ʦ `edit.tsx` | `/posts/$postId/edit` | `` | +| ʦ `settings.tsx` | `/settings` | `` | +| 📂 `settings` | | `` | +| ┄ ʦ `profile.tsx` | `/settings/profile` | `` | +| ┄ ʦ `notifications.tsx` | `/settings/notifications` | `` | +| ʦ `_pathlessLayout.tsx` | | `` | +| 📂 `_pathlessLayout` | | | +| ┄ ʦ `route-a.tsx` | `/route-a` | `` | +| ┄ ʦ `route-b.tsx` | `/route-b` | `` | +| 📂 `files` | | | +| ┄ ʦ `$.tsx` | `/files/$` | `` | +| 📂 `account` | | | +| ┄ ʦ `route.tsx` | `/account` | `` | +| ┄ ʦ `overview.tsx` | `/account/overview` | `` | + +## Flat Routes + +Flat routing gives you the ability to use `.`s to denote route nesting levels. + +This can be useful when you have a large number of uniquely deeply nested routes and want +to avoid creating directories for each one: + +See the example below: + +| Filename | Route Path | Component Output | +|---------------------------------|---------------------------|-----------------------------------| +| ʦ `__root.tsx` | | `` | +| ʦ `index.tsx` | `/` (exact) | `` | +| ʦ `about.tsx` | `/about` | `` | +| ʦ `posts.tsx` | `/posts` | `` | +| ʦ `posts.index.tsx` | `/posts` (exact) | `` | +| ʦ `posts.$postId.tsx` | `/posts/$postId` | `` | +| ʦ `posts_.$postId.edit.tsx` | `/posts/$postId/edit` | `` | +| ʦ `settings.tsx` | `/settings` | `` | +| ʦ `settings.profile.tsx` | `/settings/profile` | `` | +| ʦ `settings.notifications.tsx` | `/settings/notifications` | `` | +| ʦ `_pathlessLayout.tsx` | | `` | +| ʦ `_pathlessLayout.route-a.tsx` | `/route-a` | `` | +| ʦ `_pathlessLayout.route-b.tsx` | `/route-b` | `` | +| ʦ `files.$.tsx` | `/files/$` | `` | +| ʦ `account.tsx` | `/account` | `` | +| ʦ `account.overview.tsx` | `/account/overview` | `` | + +## Mixed Flat and Directory Routes + +It's extremely likely that a 100% directory or flat route structure won't be the best fit +for your project, which is why TanStack Router allows you to mix both flat and directory +routes together to create a route tree that uses the best of both worlds where it makes +sense: + +See the example below: + +| Filename | Route Path | Component Output | +|--------------------------------|---------------------------|-----------------------------------| +| ʦ `__root.tsx` | | `` | +| ʦ `index.tsx` | `/` (exact) | `` | +| ʦ `about.tsx` | `/about` | `` | +| ʦ `posts.tsx` | `/posts` | `` | +| 📂 `posts` | | | +| ┄ ʦ `index.tsx` | `/posts` (exact) | `` | +| ┄ ʦ `$postId.tsx` | `/posts/$postId` | `` | +| ┄ ʦ `$postId.edit.tsx` | `/posts/$postId/edit` | `` | +| ʦ `settings.tsx` | `/settings` | `` | +| ʦ `settings.profile.tsx` | `/settings/profile` | `` | +| ʦ `settings.notifications.tsx` | `/settings/notifications` | `` | +| ʦ `account.tsx` | `/account` | `` | +| ʦ `account.overview.tsx` | `/account/overview` | `` | + +Both flat and directory routes can be mixed together to create a route tree that uses the +best of both worlds where it makes sense. + +> [!TIP] +> If you find that the default file-based routing structure doesn't fit your needs, you +> can always use [Virtual File Routes](../virtual-file-routes.md) to control the source of +> your routes whilst still getting the awesome performance benefits of file-based routing. + +## Getting started with File-Based Routing + +To get started with file-based routing, you'll need to configure your project's bundler +to use the TanStack Router Plugin or the TanStack Router CLI. + +To enable file-based routing, you'll need to be using React with a supported bundler. See +if your bundler is listed in the configuration guides below. + +[//]: # 'SupportedBundlersList' + +- [Installation with Vite](../installation-with-vite.md) +- [Installation with Rspack/Rsbuild](../installation-with-rspack.md) +- [Installation with Webpack](../installation-with-webpack.md) +- [Installation with Esbuild](../installation-with-esbuild.md) + +[//]: # 'SupportedBundlersList' + +When using TanStack Router's file-based routing through one of the supported bundlers, +our plugin will **automatically generate your route configuration through your bundler's +dev and build processes**. It is the easiest way to use TanStack Router's route +generation features. + +If your bundler is not yet supported, you can reach out to us on Discord or GitHub to let +us know. Till then, fear not! You can still use the [ +`@tanstack/router-cli`](../installation-with-router-cli.md) package to generate your +route tree file. + +# File Naming Conventions + +File-based routing requires that you follow a few simple file naming conventions to +ensure that your routes are generated correctly. The concepts these conventions enable +are covered in detail in the [Route Trees & Nesting](../route-trees.md) guide. + +| Feature | Description | +|------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| **`__root.tsx`** | The root route file must be named `__root.tsx` and must be placed in the root of the configured `routesDirectory`. | +| **`.` Separator** | Routes can use the `.` character to denote a nested route. For example, `blog.post` will be generated as a child of `blog`. | +| **`$` Token** | Route segments with the `$` token are parameterized and will extract the value from the URL pathname as a route `param`. | +| **`_` Prefix** | Route segments with the `_` prefix are considered to be pathless layout routes and will not be used when matching its child routes against the URL pathname. | +| **`_` Suffix** | Route segments with the `_` suffix exclude the route from being nested under any parent routes. | +| **`-` Prefix** | Files and folders with the `-` prefix are excluded from the route tree. They will not be added to the `routeTree.gen.ts` file and can be used to colocate logic in route folders. | +| **`(folder)` folder name pattern** | A folder that matches this pattern is treated as a **route group**, preventing the folder from being included in the route's URL path. | +| **`index` Token** | Route segments ending with the `index` token (before any file extensions) will match the parent route when the URL pathname matches the parent route exactly. This can be configured via the `indexToken` configuration option, see [options](../../../../api/file-based-routing.md#indextoken). | +| **`.route.tsx` File Type** | When using directories to organise routes, the `route` suffix can be used to create a route file at the directory's path. For example, `blog.post.route.tsx` or `blog/post/route.tsx` can be used as the route file for the `/blog/post` route. This can be configured via the `routeToken` configuration option, see [options](../../../../api/file-based-routing.md#routetoken). | + +> **💡 Remember:** The file-naming conventions for your project could be affected by +> what [options](../../../../api/file-based-routing.md) are configured. + +## Dynamic Path Params + +Dynamic path params can be used in both flat and directory routes to create routes that +can match a dynamic segment of the URL path. Dynamic path params are denoted by the `$` +character in the filename: + +| Filename | Route Path | Component Output | +|-----------------------|------------------|-----------------------| +| ... | ... | ... | +| ʦ `posts.$postId.tsx` | `/posts/$postId` | `` | + +We'll learn more about dynamic path params in +the [Path Params](../../guide/path-params.md) guide. + +## Pathless Routes + +Pathless routes wrap child routes with either logic or a component without requiring a +URL path. Non-path routes are denoted by the `_` character in the filename: + +| Filename | Route Path | Component Output | +|----------------|------------|------------------| +| ʦ `_app.tsx` | | | +| ʦ `_app.a.tsx` | /a | `
` | +| ʦ `_app.b.tsx` | /b | `` | + +To learn more about pathless routes, see +the [Routing Concepts - Pathless Routes](../routing-concepts.md#pathless-layout-routes) +guide. + +# Installation with Vite + +[//]: # 'BundlerConfiguration' + +To use file-based routing with **Esbuild**, you'll need to install the +`@tanstack/router-plugin` package. + +```sh +npm install -D @tanstack/router-plugin +``` + +Once installed, you'll need to add the plugin to your configuration. + +```tsx +// esbuild.config.js +import { tanstackRouter } from '@tanstack/router-plugin/esbuild' + +export default { + // ... + plugins: [ + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + }), + ], +} +``` + +Or, you can clone +our [Quickstart Esbuild example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-esbuild-file-based) +and get started. + +Now that you've added the plugin to your Esbuild configuration, you're all set to start +using file-based routing with TanStack Router. + +[//]: # 'BundlerConfiguration' + +## Ignoring the generated route tree file + +If your project is configured to use a linter and/or formatter, you may want to ignore +the generated route tree file. This file is managed by TanStack Router and therefore +shouldn't be changed by your linter or formatter. + +Here are some resources to help you ignore the generated route tree file: + +- +Prettier - [https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore](https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore) +- +ESLint - [https://eslint.org/docs/latest/use/configure/ignore#ignoring-files](https://eslint.org/docs/latest/use/configure/ignore#ignoring-files) +- +Biome - [https://biomejs.dev/reference/configuration/#filesignore](https://biomejs.dev/reference/configuration/#filesignore) + +> [!WARNING] +> If you are using VSCode, you may experience the route tree file unexpectedly open (with +> errors) after renaming a route. + +You can prevent that from the VSCode settings by marking the file as readonly. Our +recommendation is to also exclude it from search results and file watcher with the +following settings: + +```json +{ + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + }, + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + } +} +``` + +You can use those settings either at a user level or only for a single workspace by +creating the file `.vscode/settings.json` at the root of your project. + +## Configuration + +When using the TanStack Router Plugin with Esbuild for File-based routing, it comes with +some sane defaults that should work for most projects: + +```json +{ + "routesDirectory": "./src/routes", + "generatedRouteTree": "./src/routeTree.gen.ts", + "routeFileIgnorePrefix": "-", + "quoteStyle": "single" +} +``` + +If these defaults work for your project, you don't need to configure anything at all! +However, if you need to customize the configuration, you can do so by editing the +configuration object passed into the `tanstackRouter` function. + +You can find all the available configuration options in +the [File-based Routing API Reference](../../../../api/file-based-routing.md). + +# Installation with Router CLI + +> [!WARNING] +> You should only use the TanStack Router CLI if you are not using a supported bundler. +> The CLI only supports the generation of the route tree file and does not provide any +> other features. + +To use file-based routing with the TanStack Router CLI, you'll need to install the +`@tanstack/router-cli` package. + +```sh +npm install -D @tanstack/router-cli +``` + +Once installed, you'll need to amend your your scripts in your `package.json` for the CLI +to `watch` and `generate` files. + +```json +{ + "scripts": { + "generate-routes": "tsr generate", + "watch-routes": "tsr watch", + "build": "npm run generate-routes && ...", + "dev": "npm run watch-routes && ..." + } +} +``` + +[//]: # 'AfterScripts' + +[//]: # 'AfterScripts' + +You shouldn't forget to _ignore_ the generated route tree file. Head over to +the [Ignoring the generated route tree file](#ignoring-the-generated-route-tree-file) +section to learn more. + +With the CLI installed, the following commands are made available via the `tsr` command + +## Using the `generate` command + +Generates the routes for a project based on the provided configuration. + +```sh +tsr generate +``` + +## Using the `watch` command + +Continuously watches the specified directories and regenerates routes as needed. + +**Usage:** + +```sh +tsr watch +``` + +With file-based routing enabled, whenever you start your application in development mode, +TanStack Router will watch your configured `routesDirectory` and generate your route tree +whenever a file is added, removed, or changed. + +## Ignoring the generated route tree file + +If your project is configured to use a linter and/or formatter, you may want to ignore +the generated route tree file. This file is managed by TanStack Router and therefore +shouldn't be changed by your linter or formatter. + +Here are some resources to help you ignore the generated route tree file: + +- +Prettier - [https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore](https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore) +- +ESLint - [https://eslint.org/docs/latest/use/configure/ignore#ignoring-files](https://eslint.org/docs/latest/use/configure/ignore#ignoring-files) +- +Biome - [https://biomejs.dev/reference/configuration/#filesignore](https://biomejs.dev/reference/configuration/#filesignore) + +> [!WARNING] +> If you are using VSCode, you may experience the route tree file unexpectedly open (with +> errors) after renaming a route. + +You can prevent that from the VSCode settings by marking the file as readonly. Our +recommendation is to also exclude it from search results and file watcher with the +following settings: + +```json +{ + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + }, + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + } +} +``` + +You can use those settings either at a user level or only for a single workspace by +creating the file `.vscode/settings.json` at the root of your project. + +## Configuration + +When using the TanStack Router CLI for File-based routing, it comes with some sane +defaults that should work for most projects: + +```json +{ + "routesDirectory": "./src/routes", + "generatedRouteTree": "./src/routeTree.gen.ts", + "routeFileIgnorePrefix": "-", + "quoteStyle": "single" +} +``` + +If these defaults work for your project, you don't need to configure anything at all! +However, if you need to customize the configuration, you can do so by creating a +`tsr.config.json` file in the root of your project directory. + +[//]: # 'TargetConfiguration' + +[//]: # 'TargetConfiguration' + +You can find all the available configuration options in +the [File-based Routing API Reference](../../../../api/file-based-routing.md). + +# Installation with Rspack + +[//]: # 'BundlerConfiguration' + +To use file-based routing with **Rspack** or **Rsbuild**, you'll need to install the +`@tanstack/router-plugin` package. + +```sh +npm install -D @tanstack/router-plugin +``` + +Once installed, you'll need to add the plugin to your configuration. + +```tsx +// rsbuild.config.ts +import { defineConfig } from '@rsbuild/core' +import { pluginReact } from '@rsbuild/plugin-react' +import { tanstackRouter } from '@tanstack/router-plugin/rspack' + +export default defineConfig({ + plugins: [pluginReact()], + tools: { + rspack: { + plugins: [ + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + }), + ], + }, + }, +}) +``` + +Or, you can clone +our [Quickstart Rspack/Rsbuild example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-rspack-file-based) +and get started. + +Now that you've added the plugin to your Rspack/Rsbuild configuration, you're all set to +start using file-based routing with TanStack Router. + +[//]: # 'BundlerConfiguration' + +## Ignoring the generated route tree file + +If your project is configured to use a linter and/or formatter, you may want to ignore +the generated route tree file. This file is managed by TanStack Router and therefore +shouldn't be changed by your linter or formatter. + +Here are some resources to help you ignore the generated route tree file: + +- +Prettier - [https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore](https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore) +- +ESLint - [https://eslint.org/docs/latest/use/configure/ignore#ignoring-files](https://eslint.org/docs/latest/use/configure/ignore#ignoring-files) +- +Biome - [https://biomejs.dev/reference/configuration/#filesignore](https://biomejs.dev/reference/configuration/#filesignore) + +> [!WARNING] +> If you are using VSCode, you may experience the route tree file unexpectedly open (with +> errors) after renaming a route. + +You can prevent that from the VSCode settings by marking the file as readonly. Our +recommendation is to also exclude it from search results and file watcher with the +following settings: + +```json +{ + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + }, + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + } +} +``` + +You can use those settings either at a user level or only for a single workspace by +creating the file `.vscode/settings.json` at the root of your project. + +## Configuration + +When using the TanStack Router Plugin with Rspack (or Rsbuild) for File-based routing, it +comes with some sane defaults that should work for most projects: + +```json +{ + "routesDirectory": "./src/routes", + "generatedRouteTree": "./src/routeTree.gen.ts", + "routeFileIgnorePrefix": "-", + "quoteStyle": "single" +} +``` + +If these defaults work for your project, you don't need to configure anything at all! +However, if you need to customize the configuration, you can do so by editing the +configuration object passed into the `tanstackRouter` function. + +You can find all the available configuration options in +the [File-based Routing API Reference](../../../../api/file-based-routing.md). + +# Installation with Vite + +[//]: # 'BundlerConfiguration' + +To use file-based routing with **Vite**, you'll need to install the +`@tanstack/router-plugin` package. + +```sh +npm install -D @tanstack/router-plugin +``` + +Once installed, you'll need to add the plugin to your Vite configuration. + +```ts +// vite.config.ts +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { tanstackRouter } from '@tanstack/router-plugin/vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + // Please make sure that '@tanstack/router-plugin' is passed before '@vitejs/plugin-react' + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + }), + react(), + // ... + ], +}) +``` + +Or, you can clone +our [Quickstart Vite example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-file-based) +and get started. + +> [!WARNING] +> If you are using the older `@tanstack/router-vite-plugin` package, you can still +> continue to use it, as it will be aliased to the `@tanstack/router-plugin/vite` package. +> However, we would recommend using the `@tanstack/router-plugin` package directly. + +Now that you've added the plugin to your Vite configuration, you're all set to start +using file-based routing with TanStack Router. + +[//]: # 'BundlerConfiguration' + +## Ignoring the generated route tree file + +If your project is configured to use a linter and/or formatter, you may want to ignore +the generated route tree file. This file is managed by TanStack Router and therefore +shouldn't be changed by your linter or formatter. + +Here are some resources to help you ignore the generated route tree file: + +- +Prettier - [https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore](https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore) +- +ESLint - [https://eslint.org/docs/latest/use/configure/ignore#ignoring-files](https://eslint.org/docs/latest/use/configure/ignore#ignoring-files) +- +Biome - [https://biomejs.dev/reference/configuration/#filesignore](https://biomejs.dev/reference/configuration/#filesignore) + +> [!WARNING] +> If you are using VSCode, you may experience the route tree file unexpectedly open (with +> errors) after renaming a route. + +You can prevent that from the VSCode settings by marking the file as readonly. Our +recommendation is to also exclude it from search results and file watcher with the +following settings: + +```json +{ + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + }, + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + } +} +``` + +You can use those settings either at a user level or only for a single workspace by +creating the file `.vscode/settings.json` at the root of your project. + +## Configuration + +When using the TanStack Router Plugin with Vite for File-based routing, it comes with +some sane defaults that should work for most projects: + +```json +{ + "routesDirectory": "./src/routes", + "generatedRouteTree": "./src/routeTree.gen.ts", + "routeFileIgnorePrefix": "-", + "quoteStyle": "single" +} +``` + +If these defaults work for your project, you don't need to configure anything at all! +However, if you need to customize the configuration, you can do so by editing the +configuration object passed into the `tanstackRouter` function. + +You can find all the available configuration options in +the [File-based Routing API Reference](../../../../api/file-based-routing.md). + +# Installation with Webpack + +[//]: # 'BundlerConfiguration' + +To use file-based routing with **Webpack**, you'll need to install the +`@tanstack/router-plugin` package. + +```sh +npm install -D @tanstack/router-plugin +``` + +Once installed, you'll need to add the plugin to your configuration. + +```tsx +// webpack.config.ts +import { tanstackRouter } from '@tanstack/router-plugin/webpack' + +export default { + plugins: [ + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + }), + ], +} +``` + +Or, you can clone +our [Quickstart Webpack example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-webpack-file-based) +and get started. + +Now that you've added the plugin to your Webpack configuration, you're all set to start +using file-based routing with TanStack Router. + +[//]: # 'BundlerConfiguration' + +## Ignoring the generated route tree file + +If your project is configured to use a linter and/or formatter, you may want to ignore +the generated route tree file. This file is managed by TanStack Router and therefore +shouldn't be changed by your linter or formatter. + +Here are some resources to help you ignore the generated route tree file: + +- +Prettier - [https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore](https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore) +- +ESLint - [https://eslint.org/docs/latest/use/configure/ignore#ignoring-files](https://eslint.org/docs/latest/use/configure/ignore#ignoring-files) +- +Biome - [https://biomejs.dev/reference/configuration/#filesignore](https://biomejs.dev/reference/configuration/#filesignore) + +> [!WARNING] +> If you are using VSCode, you may experience the route tree file unexpectedly open (with +> errors) after renaming a route. + +You can prevent that from the VSCode settings by marking the file as readonly. Our +recommendation is to also exclude it from search results and file watcher with the +following settings: + +```json +{ + "files.readonlyInclude": { + "**/routeTree.gen.ts": true + }, + "files.watcherExclude": { + "**/routeTree.gen.ts": true + }, + "search.exclude": { + "**/routeTree.gen.ts": true + } +} +``` + +You can use those settings either at a user level or only for a single workspace by +creating the file `.vscode/settings.json` at the root of your project. + +## Configuration + +When using the TanStack Router Plugin with Webpack for File-based routing, it comes with +some sane defaults that should work for most projects: + +```json +{ + "routesDirectory": "./src/routes", + "generatedRouteTree": "./src/routeTree.gen.ts", + "routeFileIgnorePrefix": "-", + "quoteStyle": "single" +} +``` + +If these defaults work for your project, you don't need to configure anything at all! +However, if you need to customize the configuration, you can do so by editing the +configuration object passed into the `tanstackRouter` function. + +You can find all the available configuration options in +the [File-based Routing API Reference](../../../../api/file-based-routing.md). + +# Route Matching + +Route matching follows a consistent and predictable pattern. This guide will explain how +route trees are matched. + +When TanStack Router processes your route tree, all of your routes are automatically +sorted to match the most specific routes first. This means that regardless of the order +your route tree is defined, routes will always be sorted in this order: + +- Index Route +- Static Routes (most specific to least specific) +- Dynamic Routes (longest to shortest) +- Splat/Wildcard Routes + +Consider the following pseudo route tree: + +``` +Root + - blog + - $postId + - / + - new + - / + - * + - about + - about/us +``` + +After sorting, this route tree will become: + +``` +Root + - / + - about/us + - about + - blog + - / + - new + - $postId + - * +``` + +This final order represents the order in which routes will be matched based on +specificity. + +Using that route tree, let's follow the matching process for a few different URLs: + +- `/blog` + ``` + Root + ❌ / + ❌ about/us + ❌ about + ⏩ blog + ✅ / + - new + - $postId + - * + ``` +- `/blog/my-post` + ``` + Root + ❌ / + ❌ about/us + ❌ about + ⏩ blog + ❌ / + ❌ new + ✅ $postId + - * + ``` +- `/` + ``` + Root + ✅ / + - about/us + - about + - blog + - / + - new + - $postId + - * + ``` +- `/not-a-route` + ``` + Root + ❌ / + ❌ about/us + ❌ about + ❌ blog + - / + - new + - $postId + ✅ * + ``` + +# Route Trees + +TanStack Router uses a nested route tree to match up the URL with the correct component +tree to render. + +To build a route tree, TanStack Router supports: + +- [File-Based Routing](../file-based-routing.md) +- [Code-Based Routing](../code-based-routing.md) + +Both methods support the exact same core features and functionality, but **file-based +routing requires less code for the same or better results**. For this reason, * +*file-based routing is the preferred and recommended way** to configure TanStack Router. +Most of the documentation is written from the perspective of file-based routing. + +## Route Trees + +Nested routing is a powerful concept that allows you to use a URL to render a nested +component tree. For example, given the URL of `/blog/posts/123`, you could create a route +hierarchy that looks like this: + +```tsx +├── blog +│ ├── posts +│ │ ├── $postId +``` + +And render a component tree that looks like this: + +```tsx + + + + + +``` + +Let's take that concept and expand it out to a larger site structure, but with file-names +now: + +``` +/routes +├── __root.tsx +├── index.tsx +├── about.tsx +├── posts/ +│ ├── index.tsx +│ ├── $postId.tsx +├── posts.$postId.edit.tsx +├── settings/ +│ ├── profile.tsx +│ ├── notifications.tsx +├── _pathlessLayout/ +│ ├── route-a.tsx +├── ├── route-b.tsx +├── files/ +│ ├── $.tsx +``` + +The above is a valid route tree configuration that can be used with TanStack Router! +There's a lot of power and convention to unpack with file-based routing, so let's break +it down a bit. + +## Route Tree Configuration + +Route trees can be configured using a few different ways: + +- [Flat Routes](../file-based-routing.md#flat-routes) +- [Directories](../file-based-routing.md#directory-routes) +- [Mixed Flat Routes and Directories](../file-based-routing.md#mixed-flat-and-directory-routes) +- [Virtual File Routes](../virtual-file-routes.md) +- [Code-Based Routes](../code-based-routing.md) + +Please be sure to check out the full documentation links above for each type of route +tree, or just proceed to the next section to get started with file-based routing. + +# Routing Concepts + +TanStack Router supports a number of powerful routing concepts that allow you to build +complex and dynamic routing systems with ease. + +Each of these concepts is useful and powerful, and we'll dive into each of them in the +following sections. + +## Anatomy of a Route + +All other routes, other than the [Root Route](#the-root-route), are configured using the +`createFileRoute` function, which provides type safety when using file-based routing: + +```tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: PostsComponent, +}) +``` + +The `createFileRoute` function takes a single argument, the file-route's path as a +string. + +**❓❓❓ "Wait, you're making me pass the path of the route file to `createFileRoute`?"** + +Yes! But don't worry, this path is **automatically written and managed by the router for +you via the TanStack Router Bundler Plugin or Router CLI.** So, as you create new routes, +move routes around or rename routes, the path will be updated for you automatically. + +The reason for this pathname has everything to do with the magical type safety of +TanStack Router. Without this pathname, TypeScript would have no idea what file we're +in! (We wish TypeScript had a built-in for this, but they don't yet 🤷‍♂️) + +## The Root Route + +The root route is the top-most route in the entire tree and encapsulates all other routes +as children. + +- It has no path +- It is **always** matched +- Its `component` is **always** rendered + +Even though it doesn't have a path, the root route has access to all of the same +functionality as other routes including: + +- components +- loaders +- search param validation +- etc. + +To create a root route, call the `createRootRoute()` function and export it as the +`Route` variable in your route file: + +```tsx +// Standard root route +import { createRootRoute } from '@tanstack/react-router' + +export const Route = createRootRoute() + +// Root route with Context +import { createRootRouteWithContext } from '@tanstack/react-router' +import type { QueryClient } from '@tanstack/react-query' + +export interface MyRouterContext { + queryClient: QueryClient +} +export const Route = createRootRouteWithContext() +``` + +To learn more about Context in TanStack Router, see +the [Router Context](../../guide/router-context.md) guide. + +## Basic Routes + +Basic routes match a specific path, for example `/about`, `/settings`, +`/settings/notifications` are all basic routes, as they match the path exactly. + +Let's take a look at an `/about` route: + +```tsx +// about.tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/about')({ + component: AboutComponent, +}) + +function AboutComponent() { + return
About
+} +``` + +Basic routes are simple and straightforward. They match the path exactly and render the +provided component. + +## Index Routes + +Index routes specifically target their parent route when it is **matched exactly and no +child route is matched**. + +Let's take a look at an index route for a `/posts` URL: + +```tsx +// posts.index.tsx +import { createFileRoute } from '@tanstack/react-router' + +// Note the trailing slash, which is used to target index routes +export const Route = createFileRoute('/posts/')({ + component: PostsIndexComponent, +}) + +function PostsIndexComponent() { + return
Please select a post!
+} +``` + +This route will be matched when the URL is `/posts` exactly. + +## Dynamic Route Segments + +Route path segments that start with a `$` followed by a label are dynamic and capture +that section of the URL into the `params` object for use in your application. For +example, a pathname of `/posts/123` would match the `/posts/$postId` route, and the +`params` object would be `{ postId: '123' }`. + +These params are then usable in your route's configuration and components! Let's look at +a `posts.$postId.tsx` route: + +```tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts/$postId')({ + // In a loader + loader: ({ params }) => fetchPost(params.postId), + // Or in a component + component: PostComponent, +}) + +function PostComponent() { + // In a component! + const { postId } = Route.useParams() + return
Post ID: {postId}
+} +``` + +> 🧠 Dynamic segments work at **each** segment of the path. For example, you could have a +> route with the path of `/posts/$postId/$revisionId` and each `$` segment would be +> captured into the `params` object. + +## Splat / Catch-All Routes + +A route with a path of only `$` is called a "splat" route because it _always_ captures +_any_ remaining section of the URL pathname from the `$` to the end. The captured +pathname is then available in the `params` object under the special `_splat` property. + +For example, a route targeting the `files/$` path is a splat route. If the URL pathname +is `/files/documents/hello-world`, the `params` object would contain +`documents/hello-world` under the special `_splat` property: + +```js +{ + '_splat': 'documents/hello-world' +} +``` + +> ⚠️ In v1 of the router, splat routes are also denoted with a `*` instead of a `_splat` +> key for backwards compatibility. This will be removed in v2. + +> 🧠 Why use `$`? Thanks to tools like Remix, we know that despite `*`s being the most +> common character to represent a wildcard, they do not play nice with filenames or CLI +> tools, so just like them, we decided to use `$` instead. + +## Optional Path Parameters + +Optional path parameters allow you to define route segments that may or may not be +present in the URL. They use the `{-$paramName}` syntax and provide flexible routing +patterns where certain parameters are optional. + +```tsx +// posts.{-$category}.tsx - Optional category parameter +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts/{-$category}')({ + component: PostsComponent, +}) + +function PostsComponent() { + const { category } = Route.useParams() + + return
{category ? `Posts in ${category}` : 'All Posts'}
+} +``` + +This route will match both `/posts` (category is `undefined`) and `/posts/tech` (category +is `"tech"`). + +You can also define multiple optional parameters in a single route: + +```tsx +// posts.{-$category}.{-$slug}.tsx +export const Route = createFileRoute('/posts/{-$category}/{-$slug}')({ + component: PostsComponent, +}) +``` + +This route matches `/posts`, `/posts/tech`, and `/posts/tech/hello-world`. + +> 🧠 Routes with optional parameters are ranked lower in priority than exact matches, +> ensuring that more specific routes like `/posts/featured` are matched before +`/posts/{-$category}`. + +## Layout Routes + +Layout routes are used to wrap child routes with additional components and logic. They +are useful for: + +- Wrapping child routes with a layout component +- Enforcing a `loader` requirement before displaying any child routes +- Validating and providing search params to child routes +- Providing fallbacks for error components or pending elements to child routes +- Providing shared context to all child routes +- And more! + +Let's take a look at an example layout route called `app.tsx`: + +``` +routes/ +├── app.tsx +├── app.dashboard.tsx +├── app.settings.tsx +``` + +In the tree above, `app.tsx` is a layout route that wraps two child routes, +`app.dashboard.tsx` and `app.settings.tsx`. + +This tree structure is used to wrap the child routes with a layout component: + +```tsx +import { Outlet, createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/app')({ + component: AppLayoutComponent, +}) + +function AppLayoutComponent() { + return ( +
+

App Layout

+ +
+ ) +} +``` + +The following table shows which component(s) will be rendered based on the URL: + +| URL Path | Component | +|------------------|--------------------------| +| `/app` | `` | +| `/app/dashboard` | `` | +| `/app/settings` | `` | + +Since TanStack Router supports mixed flat and directory routes, you can also express your +application's routing using layout routes within directories: + +``` +routes/ +├── app/ +│ ├── route.tsx +│ ├── dashboard.tsx +│ ├── settings.tsx +``` + +In this nested tree, the `app/route.tsx` file is a configuration for the layout route +that wraps two child routes, `app/dashboard.tsx` and `app/settings.tsx`. + +Layout Routes also let you enforce component and loader logic for Dynamic Route Segments: + +``` +routes/ +├── app/users/ +│ ├── $userId/ +| | ├── route.tsx +| | ├── index.tsx +| | ├── edit.tsx +``` + +## Pathless Layout Routes + +Like [Layout Routes](#layout-routes), Pathless Layout Routes are used to wrap child +routes with additional components and logic. However, pathless layout routes do not +require a matching `path` in the URL and are used to wrap child routes with additional +components and logic without requiring a matching `path` in the URL. + +Pathless Layout Routes are prefixed with an underscore (`_`) to denote that they are " +pathless". + +> 🧠 The part of the path after the `_` prefix is used as the route's ID and is required +> because every route must be uniquely identifiable, especially when using TypeScript so as +> to avoid type errors and accomplish autocomplete effectively. + +Let's take a look at an example route called `_pathlessLayout.tsx`: + +``` + +routes/ +├── _pathlessLayout.tsx +├── _pathlessLayout.a.tsx +├── _pathlessLayout.b.tsx + +``` + +In the tree above, `_pathlessLayout.tsx` is a pathless layout route that wraps two child +routes, `_pathlessLayout.a.tsx` and `_pathlessLayout.b.tsx`. + +The `_pathlessLayout.tsx` route is used to wrap the child routes with a Pathless layout +component: + +```tsx +import { Outlet, createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/_pathlessLayout')({ + component: PathlessLayoutComponent, +}) + +function PathlessLayoutComponent() { + return ( +
+

Pathless layout

+ +
+ ) +} +``` + +The following table shows which component will be rendered based on the URL: + +| URL Path | Component | +|----------|-----------------------| +| `/` | `` | +| `/a` | `
` | +| `/b` | `` | + +Since TanStack Router supports mixed flat and directory routes, you can also express your +application's routing using pathless layout routes within directories: + +``` +routes/ +├── _pathlessLayout/ +│ ├── route.tsx +│ ├── a.tsx +│ ├── b.tsx +``` + +However, unlike Layout Routes, since Pathless Layout Routes do match based on URL path +segments, this means that these routes do not +support [Dynamic Route Segments](#dynamic-route-segments) as part of their path and +therefore cannot be matched in the URL. + +This means that you cannot do this: + +``` +routes/ +├── _$postId/ ❌ +│ ├── ... +``` + +Rather, you'd have to do this: + +``` +routes/ +├── $postId/ +├── _postPathlessLayout/ ✅ +│ ├── ... +``` + +## Non-Nested Routes + +Non-nested routes can be created by suffixing a parent file route segment with a `_` and +are used to **un-nest** a route from its parents and render its own component tree. + +Consider the following flat route tree: + +``` +routes/ +├── posts.tsx +├── posts.$postId.tsx +├── posts_.$postId.edit.tsx +``` + +The following table shows which component will be rendered based on the URL: + +| URL Path | Component | +|-------------------|------------------------------| +| `/posts` | `` | +| `/posts/123` | `` | +| `/posts/123/edit` | `` | + +- The `posts.$postId.tsx` route is nested as normal under the `posts.tsx` route and will + render ``. +- The `posts_.$postId.edit.tsx` route **does not share** the same `posts` prefix as the + other routes and therefore will be treated as if it is a top-level route and will + render ``. + +## Excluding Files and Folders from Routes + +Files and folders can be excluded from route generation with a `-` prefix attached to the +file name. This gives you the ability to colocate logic in the route directories. + +Consider the following route tree: + +``` +routes/ +├── posts.tsx +├── -posts-table.tsx // 👈🏼 ignored +├── -components/ // 👈🏼 ignored +│ ├── header.tsx // 👈🏼 ignored +│ ├── footer.tsx // 👈🏼 ignored +│ ├── ... +``` + +We can import from the excluded files into our posts route + +```tsx +import { createFileRoute } from '@tanstack/react-router' +import { PostsTable } from './-posts-table' +import { PostsHeader } from './-components/header' +import { PostsFooter } from './-components/footer' + +export const Route = createFileRoute('/posts')({ + loader: () => fetchPosts(), + component: PostComponent, +}) + +function PostComponent() { + const posts = Route.useLoaderData() + + return ( +
+ + + +
+ ) +} +``` + +The excluded files will not be added to `routeTree.gen.ts`. + +## Pathless Route Group Directories + +Pathless route group directories use `()` as a way to group routes files together +regardless of their path. They are purely organizational and do not affect the route tree +or component tree in any way. + +``` +routes/ +├── index.tsx +├── (app)/ +│ ├── dashboard.tsx +│ ├── settings.tsx +│ ├── users.tsx +├── (auth)/ +│ ├── login.tsx +│ ├── register.tsx +``` + +In the example above, the `app` and `auth` directories are purely organizational and do +not affect the route tree or component tree in any way. They are used to group related +routes together for easier navigation and organization. + +The following table shows which component will be rendered based on the URL: + +| URL Path | Component | +|--------------|---------------| +| `/` | `` | +| `/dashboard` | `` | +| `/settings` | `` | +| `/users` | `` | +| `/login` | `` | +| `/register` | `` | + +As you can see, the `app` and `auth` directories are purely organizational and do not +affect the route tree or component tree in any way. + +# Virtual File Routes + +> We'd like to thank the Remix team +> for [pioneering the concept of virtual file routes](https://www.youtube.com/watch?v=fjTX8hQTlEc&t=730s). +> We've taken inspiration from their work and adapted it to work with TanStack Router's +> existing file-based route-tree generation. + +Virtual file routes are a powerful concept that allows you to build a route tree +programmatically using code that references real files in your project. This can be +useful if: + +- You have an existing route organization that you want to keep. +- You want to customize the location of your route files. +- You want to completely override TanStack Router's file-based route generation and build + your own convention. + +Here's a quick example of using virtual file routes to map a route tree to a set of real +files in your project: + +```tsx +// routes.ts +import { + rootRoute, + route, + index, + layout, + physical, +} from '@tanstack/virtual-file-routes' + +export const routes = rootRoute('root.tsx', [ + index('index.tsx'), + layout('pathlessLayout.tsx', [ + route('/dashboard', 'app/dashboard.tsx', [ + index('app/dashboard-index.tsx'), + route('/invoices', 'app/dashboard-invoices.tsx', [ + index('app/invoices-index.tsx'), + route('$id', 'app/invoice-detail.tsx'), + ]), + ]), + physical('/posts', 'posts'), + ]), +]) +``` + +## Configuration + +Virtual file routes can be configured either via: + +- The `TanStackRouter` plugin for Vite/Rspack/Webpack +- The `tsr.config.json` file for the TanStack Router CLI + +## Configuration via the TanStackRouter Plugin + +If you're using the `TanStackRouter` plugin for Vite/Rspack/Webpack, you can configure +virtual file routes by passing the path of your routes file to the `virtualRoutesConfig` +option when setting up the plugin: + +```tsx +// 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({ + target: 'react', + virtualRouteConfig: './routes.ts', + }), + react(), + ], +}) +``` + +Or, you choose to define the virtual routes directly in the configuration: + +```tsx +// vite.config.ts +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { tanstackRouter } from '@tanstack/router-plugin/vite' +import { rootRoute } from '@tanstack/virtual-file-routes' + +const routes = rootRoute('root.tsx', [ + // ... the rest of your virtual route tree +]) + +export default defineConfig({ + plugins: [tanstackRouter({ virtualRouteConfig: routes }), react()], +}) +``` + +## Creating Virtual File Routes + +To create virtual file routes, you'll need to import the `@tanstack/virtual-file-routes` +package. This package provides a set of functions that allow you to create virtual routes +that reference real files in your project. A few utility functions are exported from the +package: + +- `rootRoute` - Creates a virtual root route. +- `route` - Creates a virtual route. +- `index` - Creates a virtual index route. +- `layout` - Creates a virtual pathless layout route. +- `physical` - Creates a physical virtual route (more on this later). + +## Virtual Root Route + +The `rootRoute` function is used to create a virtual root route. It takes a file name and +an array of children routes. Here's an example of a virtual root route: + +```tsx +// routes.ts +import { rootRoute } from '@tanstack/virtual-file-routes' + +export const routes = rootRoute('root.tsx', [ + // ... children routes +]) +``` + +## Virtual Route + +The `route` function is used to create a virtual route. It takes a path, a file name, and +an array of children routes. Here's an example of a virtual route: + +```tsx +// routes.ts +import { route } from '@tanstack/virtual-file-routes' + +export const routes = rootRoute('root.tsx', [ + route('/about', 'about.tsx', [ + // ... children routes + ]), +]) +``` + +You can also define a virtual route without a file name. This allows to set a common path +prefix for its children: + +```tsx +// routes.ts +import { route } from '@tanstack/virtual-file-routes' + +export const routes = rootRoute('root.tsx', [ + route('/hello', [ + route('/world', 'world.tsx'), // full path will be "/hello/world" + route('/universe', 'universe.tsx'), // full path will be "/hello/universe" + ]), +]) +``` + +## Virtual Index Route + +The `index` function is used to create a virtual index route. It takes a file name. +Here's an example of a virtual index route: + +```tsx +import { index } from '@tanstack/virtual-file-routes' + +const routes = rootRoute('root.tsx', [index('index.tsx')]) +``` + +## Virtual Pathless Route + +The `layout` function is used to create a virtual pathless route. It takes a file name, +an array of children routes, and an optional pathless ID. Here's an example of a virtual +pathless route: + +```tsx +// routes.ts +import { layout } from '@tanstack/virtual-file-routes' + +export const routes = rootRoute('root.tsx', [ + layout('pathlessLayout.tsx', [ + // ... children routes + ]), +]) +``` + +You can also specify a pathless ID to give the route a unique identifier that is +different from the filename: + +```tsx +// routes.ts +import { layout } from '@tanstack/virtual-file-routes' + +export const routes = rootRoute('root.tsx', [ + layout('my-pathless-layout-id', 'pathlessLayout.tsx', [ + // ... children routes + ]), +]) +``` + +## Physical Virtual Routes + +Physical virtual routes are a way to "mount" a directory of good ol' TanStack Router File +Based routing convention under a specific URL path. This can be useful if you are using +virtual routes to customize a small portion of your route tree high up in the hierarchy, +but want to use the standard file-based routing convention for sub-routes and +directories. + +Consider the following file structure: + +``` +/routes +├── root.tsx +├── index.tsx +├── pathlessLayout.tsx +├── app +│ ├── dashboard.tsx +│ ├── dashboard-index.tsx +│ ├── dashboard-invoices.tsx +│ ├── invoices-index.tsx +│ ├── invoice-detail.tsx +└── posts + ├── index.tsx + ├── $postId.tsx + ├── $postId.edit.tsx + ├── comments/ + │ ├── index.tsx + │ ├── $commentId.tsx + └── likes/ + ├── index.tsx + ├── $likeId.tsx +``` + +Let's use virtual routes to customize our route tree for everything but `posts`, then use +physical virtual routes to mount the `posts` directory under the `/posts` path: + +```tsx +// routes.ts +export const routes = rootRoute('root.tsx', [ + // Set up your virtual routes as normal + index('index.tsx'), + layout('pathlessLayout.tsx', [ + route('/dashboard', 'app/dashboard.tsx', [ + index('app/dashboard-index.tsx'), + route('/invoices', 'app/dashboard-invoices.tsx', [ + index('app/invoices-index.tsx'), + route('$id', 'app/invoice-detail.tsx'), + ]), + ]), + // Mount the `posts` directory under the `/posts` path + physical('/posts', 'posts'), + ]), +]) +``` + +## Virtual Routes inside of TanStack Router File Based routing + +The previous section showed you how you can use TanStack Router's File Based routing +convention inside of a virtual route configuration. +However, the opposite is possible as well. +You can configure the main part of your app's route tree using TanStack Router's File +Based routing convention and opt into virtual route configuration for specific subtrees. + +Consider the following file structure: + +``` +/routes +├── __root.tsx +├── foo +│ ├── bar +│ │ ├── __virtual.ts +│ │ ├── details.tsx +│ │ ├── home.tsx +│ │ └── route.ts +│ └── bar.tsx +└── index.tsx +``` + +Let's look at the `bar` directory which contains a special file named `__virtual.ts`. +This file instructs the generator to switch over to virtual file route configuration for +this directory (and its child directories). + +`__virtual.ts` configures the virtual routes for that particular subtree of the route +tree. It uses the same API as explained above, with the only difference being that no +`rootRoute` is defined for that subtree: + +```tsx +// routes/foo/bar/__virtual.ts +import { + defineVirtualSubtreeConfig, + index, + route, +} from '@tanstack/virtual-file-routes' + +export default defineVirtualSubtreeConfig([ + index('home.tsx'), + route('$id', 'details.tsx'), +]) +``` + +The helper function `defineVirtualSubtreeConfig` is closely modeled after vite's +`defineConfig` and allows you to define a subtree configuration via a default export. The +default export can either be + +- a subtree config object +- a function returning a subtree config object +- an async function returning a subtree config object + +## Inception + +You can mix and match TanStack Router's File Based routing convention and virtual route +configuration however you like. +Let's go deeper! +Check out the following example that starts off using File Based routing convention, +switches over to virtual route configuration for `/posts`, switches back to File Based +routing convention for `/posts/lets-go` only to switch over to virtual route +configuration again for `/posts/lets-go/deeper`. + +``` +├── __root.tsx +├── index.tsx +├── posts +│ ├── __virtual.ts +│ ├── details.tsx +│ ├── home.tsx +│ └── lets-go +│ ├── deeper +│ │ ├── __virtual.ts +│ │ └── home.tsx +│ └── index.tsx +└── posts.tsx +``` + +## Configuration via the TanStack Router CLI + +If you're using the TanStack Router CLI, you can configure virtual file routes by +defining the path to your routes file in the `tsr.config.json` file: + +```json +// tsr.config.json +{ + "virtualRouteConfig": "./routes.ts" +} +``` + +Or you can define the virtual routes directly in the configuration, while much less +common allows you to configure them via the TanStack Router CLI by adding a +`virtualRouteConfig` object to your `tsr.config.json` file and defining your virtual +routes and passing the resulting JSON that is generated by calling the actual +`rootRoute`/`route`/`index`/etc functions from the `@tanstack/virtual-file-routes` +package: + +```json +// tsr.config.json +{ + "virtualRouteConfig": { + "type": "root", + "file": "root.tsx", + "children": [ + { + "type": "index", + "file": "home.tsx" + }, + { + "type": "route", + "file": "posts/posts.tsx", + "path": "/posts", + "children": [ + { + "type": "index", + "file": "posts/posts-home.tsx" + }, + { + "type": "route", + "file": "posts/posts-detail.tsx", + "path": "$postId" + } + ] + }, + { + "type": "layout", + "id": "first", + "file": "layout/first-pathless-layout.tsx", + "children": [ + { + "type": "layout", + "id": "second", + "file": "layout/second-pathless-layout.tsx", + "children": [ + { + "type": "route", + "file": "a.tsx", + "path": "/route-a" + }, + { + "type": "route", + "file": "b.tsx", + "path": "/route-b" + } + ] + } + ] + } + ] + } +} +``` + + + +<@tanstack/react-router_setup-and-architecture> +Always Apply: false - This rule should only be applied when relevant files are open +Always apply this rule in these files: package.json, vite.config.ts, tsconfig.json, src/* +*/*.ts, src/**/*.tsx + +# Overview + +**TanStack Router is a router for building React and Solid applications**. Some of its +features include: + +- 100% inferred TypeScript support +- Typesafe navigation +- Nested Routing and layout routes (with pathless layouts) +- Built-in Route Loaders w/ SWR Caching +- Designed for client-side data caches (TanStack Query, SWR, etc.) +- Automatic route prefetching +- Asynchronous route elements and error boundaries +- File-based Route Generation +- Typesafe JSON-first Search Params state management APIs +- Path and Search Parameter Schema Validation +- Search Param Navigation APIs +- Custom Search Param parser/serializer support +- Search param middleware +- Route matching/loading middleware + +To get started quickly, head to the next page. For a more lengthy explanation, buckle up +while I bring you up to speed! + +## "A Fork in the Route" + +Using a router to build applications is widely regarded as a must-have and is usually one +of the first choices you’ll make in your tech stack. + +[//]: # 'WhyChooseTanStackRouter' + +**So, why should you choose TanStack Router over another router?** + +To answer this question, we need to look at the other options in the space. There are +many if you look hard enough, but in my experience, only a couple are worth exploring +seriously: + +- **Next.js** - Widely regarded as the de facto framework for starting a new React + project, it’s laser focused on performance, workflow, and bleeding edge technology. Its + APIs and abstractions are powerful, but can sometimes come across as non-standard. Its + extremely fast growth and adoption in the industry has resulted in a featured packed + experience, but at the expense of feeling overwhelming and sometimes bloated. +- **Remix / React Router** - A full-stack framework based on the historically successful + React Router offers a similarly powerful developer and user experience, with APIs and + vision based firmly on web standards like Request/Response and a focus on running + anywhere JS can run. Many of its APIs and abstractions are wonderfully designed and + were inspiration for more than a few TanStack Router APIs. That said, its rigid design, + bolted-on type safety and sometimes strict over-adherence to platform APIs can leave + some developers wanting more. + +Both of these frameworks (and their routers) are great, and I can personally attest that +both are very good solutions for building React applications. My experience has also +taught me that these solutions could also be much better, especially around the actual +routing APIs that are available to developers to make their apps faster, easier, and more +enjoyable to work with. + +It's probably no surprise at this point that picking a router is so important that it is +often tied 1-to-1 with your choice of framework, since most frameworks rely on a specific +router. + +[//]: # 'WhyChooseTanStackRouter' + +**Does this mean that TanStack Router is a framework?** + +TanStack Router itself is not a "framework" in the traditional sense, since it doesn't +address a few other common full-stack concerns. However TanStack Router has been designed +to be upgradable to a full-stack framework when used in conjunction with other tools that +address bundling, deployments, and server-side-specific functionality. This is why we are +currently developing [TanStack Start](https://tanstack.com/start), a full-stack framework +that is built on top of TanStack Router and Vite. + +For a deeper dive on the history of TanStack Router, feel free to +read [TanStack Router's History](../decisions-on-dx.md#tanstack-routers-origin-story). + +## Why TanStack Router? + +TanStack Router delivers on the same fundamental expectations as other routers that +you’ve come to expect: + +- Nested routes, layout routes, grouped routes +- File-based Routing +- Parallel data loading +- Prefetching +- URL Path Params +- Error Boundaries and Handling +- SSR +- Route Masking + +And it also delivers some new features that raise the bar: + +- 100% inferred TypeScript support +- Typesafe navigation +- Built-in SWR Caching for loaders +- Designed for client-side data caches (TanStack Query, SWR, etc.) +- Typesafe JSON-first Search Params state management APIs +- Path and Search Parameter Schema Validation +- Search Parameter Navigation APIs +- Custom Search Param parser/serializer support +- Search param middleware +- Inherited Route Context +- Mixed file-based and code-based routing + +Let’s dive into some of the more important ones in more detail! + +## 100% Inferred TypeScript Support + +Everything these days is written “in Typescript” or at the very least offers type +definitions that are veneered over runtime functionality, but too few packages in the +ecosystem actually design their APIs with TypeScript in mind. So while I’m pleased that +your router is auto-completing your option fields and catching a few property/method +typos here and there, there is much more to be had. + +- TanStack Router is fully aware of all of your routes and their configuration at any + given point in your code. This includes the path, path params, search params, context, + and any other configuration you’ve provided. Ultimately this means that you can + navigate to any route in your app with 100% type safety and confidence that your link + or navigate call will succeed. +- TanStack Router provides lossless type-inference. It uses countless generic type + parameters to enforce and propagate any type information you give it throughout the + rest of its API and ultimately your app. No other router offers this level of type + safety and developer confidence. + +What does all of that mean for you? + +- Faster feature development with auto-completion and type hints +- Safer and faster refactors +- Confidence that your code will work as expected + +## 1st Class Search Parameters + +Search parameters are often an afterthought, treated like a black box of strings (or +string) that you can parse and update, but not much else. Existing solutions are **not** +type-safe either, adding to the caution that is required to deal with them. Even the +most "modern" frameworks and routers and leave it up to you to figure out how to manage +this state. Sometimes they'll parse the search string into an object for you, or +sometimes you're left to do it yourself with `URLSearchParams`. + +Let's step back and remember that **search params are the most powerful state manager in +your entire application.** They are global, serializable, bookmarkable, and shareable +making them the perfect place to store any kind of state that needs to survive a page +refresh or a social share. + +To live up to that responsibility, search parameters are a first-class citizen in +TanStack Router. While still based on standard URLSearchParams, TanStack Router uses a +powerful parser/serializer to manage deeper and more complex data structures in your +search params, all while keeping them type-safe and easy to work with. + +**It's like having `useState` right in the URL!** + +Search parameters are: + +- Automatically parsed and serialized as JSON +- Validated and typed +- Inherited from parent routes +- Accessible in loaders, components, and hooks +- Easily modified with the useSearch hook, Link, navigate, and router.navigate APIs +- Customizable with a custom search filters and middleware +- Subscribed via fine-grained search param selectors for efficient re-renders + +Once you start using TanStack Router's search parameters, you'll wonder how you ever +lived without them. + +## Built-In Caching and Friendly Data Loading + +Data loading is a critical part of any application and while most existing routers offer +some form of critical data loading APIs, they often fall short when it comes to caching +and data lifecycle management. Existing solutions suffer from a few common problems: + +- No caching at all. Data is always fresh, but your users are left waiting for frequently + accessed data to load over and over again. +- Overly-aggressive caching. Data is cached for too long, leading to stale data and a + poor user experience. +- Blunt invalidation strategies and APIs. Data may be invalidated too often, leading to + unnecessary network requests and wasted resources, or you may not have any fine-grained + control over when data is invalidated at all. + +TanStack Router solves these problems with a two-prong approach to caching and data +loading: + +### Built-in Cache + +TanStack Router provides a light-weight built-in caching layer that works seamlessly with +the Router. This caching layer is loosely based on TanStack Query, but with fewer +features and a much smaller API surface area. Like TanStack Query, sane but powerful +defaults guarantee that your data is cached for reuse, invalidated when necessary, and +garbage collected when not in use. It also provides a simple API for invalidating the +cache manually when needed. + +### Flexible & Powerful Data Lifecycle APIs + +TanStack Router is designed with a flexible and powerful data loading API that more +easily integrates with existing data fetching libraries like TanStack Query, SWR, Apollo, +Relay, or even your own custom data fetching solution. Configurable APIs like `context`, +`beforeLoad`, `loaderDeps` and `loader` work in unison to make it easy to define +declarative data dependencies, prefetch data, and manage the lifecycle of an external +data source with ease. + +## Inherited Route Context + +TanStack Router's router and route context is a powerful feature that allows you to +define context that is specific to a route which is then inherited by all child routes. +Even the router and root routes themselves can provide context. Context can be built up +both synchronously and asynchronously, and can be used to share data, configuration, or +even functions between routes and route configurations. This is especially useful for +scenarios like: + +- Authentication and Authorization +- Hybrid SSR/CSR data fetching and preloading +- Theming +- Singletons and global utilities +- Curried or partial application across preloading, loading, and rendering stages + +Also, what would route context be if it weren't type-safe? TanStack Router's route +context is fully type-safe and inferred at zero cost to you. + +## File-based and/or Code-Based Routing + +TanStack Router supports both file-based and code-based routing at the same time. This +flexibility allows you to choose the approach that best fits your project's needs. + +TanStack Router's file-based routing approach is uniquely user-facing. Route +configuration is generated for you either by the Vite plugin or TanStack Router CLI, +leaving the usage of said generated code up to you! This means that you're always in +total control of your routes and router, even if you use file-based routing. + +## Acknowledgements + +TanStack Router builds on concepts and patterns popularized by many other OSS projects, +including: + +- [TRPC](https://trpc.io/) +- [Remix](https://remix.run) +- [Chicane](https://swan-io.github.io/chicane/) +- [Next.js](https://nextjs.org) + +We acknowledge the investment, risk and research that went into their development, but +are excited to push the bar they have set even higher. + +## Let's go! + +Enough overview, there's so much more to do with TanStack Router. Hit that next button +and let's get started! + +# Quick Start + +If you're feeling impatient and prefer to skip all of our wonderful documentation, here +is the bare minimum to get going with TanStack Router using both file-based route +generation and code-based route configuration: + +## Using File-Based Route Generation + +File based route generation (through Vite, and other supported bundlers) is the +recommended way to use TanStack Router as it provides the best experience, performance, +and ergonomics for the least amount of effort. + +### Scaffolding Your First TanStack Router Project + +```sh +npx create-tsrouter-app@latest my-app --template file-router +``` + +See [create-tsrouter-app](https://github.com/TanStack/create-tsrouter-app/tree/main/cli/create-tsrouter-app) +for more options. + +### Manual Setup + +Alternatively, you can manually setup the project using the following steps: + +#### Install TanStack Router, Vite Plugin, and the Router Devtools + +```sh +npm install @tanstack/react-router @tanstack/react-router-devtools +npm install -D @tanstack/router-plugin +# or +pnpm add @tanstack/react-router @tanstack/react-router-devtools +pnpm add -D @tanstack/router-plugin +# or +yarn add @tanstack/react-router @tanstack/react-router-devtools +yarn add -D @tanstack/router-plugin +# or +bun add @tanstack/react-router @tanstack/react-router-devtools +bun add -D @tanstack/router-plugin +# or +deno add npm:@tanstack/react-router npm:@tanstack/router-plugin npm:@tanstack/react-router-devtools +``` + +#### Configure the Vite Plugin + +```tsx +// vite.config.ts +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { tanstackRouter } from '@tanstack/router-plugin/vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [ + // Please make sure that '@tanstack/router-plugin' is passed before '@vitejs/plugin-react' + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + }), + react(), + // ..., + ], +}) +``` + +> [!TIP] +> If you are not using Vite, or any of the supported bundlers, you can check out +> the [TanStack Router CLI](../routing/installation-with-router-cli.md) guide for more +> info. + +Create the following files: + +- `src/routes/__root.tsx` (with two '`_`' characters) +- `src/routes/index.tsx` +- `src/routes/about.tsx` +- `src/main.tsx` + +#### `src/routes/__root.tsx` + +```tsx +import { createRootRoute, Link, Outlet } from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' + +export const Route = createRootRoute({ + component: () => ( + <> +
+ + Home + {' '} + + About + +
+
+ + + + ), +}) +``` + +#### `src/routes/index.tsx` + +```tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: Index, +}) + +function Index() { + return ( +
+

Welcome Home!

+
+ ) +} +``` + +#### `src/routes/about.tsx` + +```tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/about')({ + component: About, +}) + +function About() { + return
Hello from About!
+} +``` + +#### `src/main.tsx` + +Regardless of whether you are using the `@tanstack/router-plugin` package and running the +`npm run dev`/`npm run build` scripts, or manually running the `tsr watch`/`tsr generate` +commands from your package scripts, the route tree file will be generated at +`src/routeTree.gen.ts`. + +Import the generated route tree and create a new router instance: + +```tsx +import { StrictMode } from 'react' +import ReactDOM from 'react-dom/client' +import { RouterProvider, createRouter } from '@tanstack/react-router' + +// Import the generated route tree +import { routeTree } from './routeTree.gen' + +// Create a new router instance +const router = createRouter({ routeTree }) + +// Register the router instance for type safety +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} + +// Render the app +const rootElement = document.getElementById('root')! +if (!rootElement.innerHTML) { + const root = ReactDOM.createRoot(rootElement) + root.render( + + + , + ) +} +``` + +If you are working with this pattern you should change the `id` of the root `
` on +your `index.html` file to `
` + +## Using Code-Based Route Configuration + +> [!IMPORTANT] +> The following example shows how to configure routes using code, and for simplicity's +> sake is in a single file for this demo. While code-based generation allows you to declare +> many routes and even the router instance in a single file, we recommend splitting your +> routes into separate files for better organization and performance as your application +> grows. + +```tsx +import { StrictMode } from 'react' +import ReactDOM from 'react-dom/client' +import { + Outlet, + RouterProvider, + Link, + createRouter, + createRoute, + createRootRoute, +} from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' + +const rootRoute = createRootRoute({ + component: () => ( + <> +
+ + Home + {' '} + + About + +
+
+ + + + ), +}) + +const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: function Index() { + return ( +
+

Welcome Home!

+
+ ) + }, +}) + +const aboutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/about', + component: function About() { + return
Hello from About!
+ }, +}) + +const routeTree = rootRoute.addChildren([indexRoute, aboutRoute]) + +const router = createRouter({ routeTree }) + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} + +const rootElement = document.getElementById('app')! +if (!rootElement.innerHTML) { + const root = ReactDOM.createRoot(rootElement) + root.render( + + + , + ) +} +``` + +If you glossed over these examples or didn't understand something, we don't blame you, +because there's so much more to learn to really take advantage of TanStack Router! Let's +move on. + +# Devtools + +> Link, take this sword... I mean Devtools!... to help you on your way! + +Wave your hands in the air and shout hooray because TanStack Router comes with dedicated +devtools! 🥳 + +When you begin your TanStack Router journey, you'll want these devtools by your side. +They help visualize all of the inner workings of TanStack Router and will likely save you +hours of debugging if you find yourself in a pinch! + +## Installation + +The devtools are a separate package that you need to install: + +```sh +npm install @tanstack/react-router-devtools +``` + +or + +```sh +pnpm add @tanstack/react-router-devtools +``` + +or + +```sh +yarn add @tanstack/react-router-devtools +``` + +or + +```sh +bun add @tanstack/react-router-devtools +``` + +## Import the Devtools + +```js +import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' +``` + +## Using Devtools in production + +The Devtools, if imported as `TanStackRouterDevtools` will not be shown in production. If +you want to have devtools in an environment with `process.env.NODE_ENV === 'production'`, +use instead `TanStackRouterDevtoolsInProd`, which has all the same options: + +```tsx +import { TanStackRouterDevtoolsInProd } from '@tanstack/react-router-devtools' +``` + +## Using inside of the `RouterProvider` + +The easiest way for the devtools to work is to render them inside of your root route (or +any other route). This will automatically connect the devtools to the router instance. + +```tsx +const rootRoute = createRootRoute({ + component: () => ( + <> + + + + ), +}) + +const routeTree = rootRoute.addChildren([ + // ... other routes +]) + +const router = createRouter({ + routeTree, +}) + +function App() { + return +} +``` + +## Manually passing the Router Instance + +If rendering the devtools inside of the `RouterProvider` isn't your cup of tea, a +`router` prop for the devtools accepts the same `router` instance you pass to the +`Router` component. This makes it possible to place the devtools anywhere on the page, +not just inside the provider: + +```tsx +function App() { + return ( + <> + + + + ) +} +``` + +## Floating Mode + +Floating Mode will mount the devtools as a fixed, floating element in your app and +provide a toggle in the corner of the screen to show and hide the devtools. This toggle +state will be stored and remembered in localStorage across reloads. + +Place the following code as high in your React app as you can. The closer it is to the +root of the page, the better it will work! + +```js +import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' + +function App() { + return ( + <> + + + + ) +} +``` + +### Devtools Options + +- `router: Router` + - The router instance to connect to. +- `initialIsOpen: Boolean` + - Set this `true` if you want the devtools to default to being open. +- `panelProps: PropsObject` + - Use this to add props to the panel. For example, you can add `className`, `style` ( + merge and override default style), etc. +- `closeButtonProps: PropsObject` + - Use this to add props to the close button. For example, you can add `className`, + `style` (merge and override default style), `onClick` (extend default handler), + etc. +- `toggleButtonProps: PropsObject` + - Use this to add props to the toggle button. For example, you can add `className`, + `style` (merge and override default style), `onClick` (extend default handler), + etc. +- `position?: "top-left" | "top-right" | "bottom-left" | "bottom-right"` + - Defaults to `bottom-left`. + - The position of the TanStack Router logo to open and close the devtools panel. +- `shadowDOMTarget?: ShadowRoot` + - Specifies a Shadow DOM target for the devtools. + - By default, devtool styles are applied to the `` tag of the main document ( + light DOM). When a `shadowDOMTarget` is provided, styles will be applied within + this Shadow DOM instead. +- `containerElement?: string | any` + - Use this to render the devtools inside a different type of container element for + ally purposes. + - Any string which corresponds to a valid intrinsic JSX element is allowed. + - Defaults to 'footer'. + +## Fixed Mode + +To control the position of the devtools, import the `TanStackRouterDevtoolsPanel`: + +```js +import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools' +``` + +It can then be attached to provided shadow DOM target: + +```js + +``` + +Click [here](https://tanstack.com/router/latest/docs/framework/react/examples/basic-devtools-panel) +to see a live example of this in StackBlitz. + +## Embedded Mode + +Embedded Mode will embed the devtools as a regular component in your application. You can +style it however you'd like after that! + +```js +import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools' + +function App() { + return ( + <> + + + + ) +} +``` + +### DevtoolsPanel Options + +- `router: Router` + - The router instance to connect to. +- `style: StyleObject` + - The standard React style object used to style a component with inline styles. +- `className: string` + - The standard React className property used to style a component with classes. +- `isOpen?: boolean` + - A boolean variable indicating whether the panel is open or closed. +- `setIsOpen?: (isOpen: boolean) => void` + - A function that toggles the open and close state of the panel. +- `handleDragStart?: (e: any) => void` + - Handles the opening and closing the devtools panel. +- `shadowDOMTarget?: ShadowRoot` + - Specifies a Shadow DOM target for the devtools. + - By default, devtool styles are applied to the `` tag of the main document ( + light DOM). When a `shadowDOMTarget` is provided, styles will be applied within + this Shadow DOM instead. + +# Migration from React Router Checklist + +* +*_If your UI is blank, open the console, and you will probably have some errors that read +something along the lines of `cannot use 'useNavigate' outside of context` . This means +there are React Router api’s that are still imported and referenced that you need to find +and remove. The easiest way to make sure you find all React Router imports is to +uninstall `react-router-dom` and then you should get typescript errors in your files. +Then you will know what to change to a `@tanstack/react-router` import._** + +Here is +the [example repo](https://github.com/Benanna2019/SickFitsForEveryone/tree/migrate-to-tanstack/router/React-Router) + +- [ ] Install Router - `npm i @tanstack/react-router` ( + see [detailed installation guide](./how-to/install.md)) +- [ ] **Optional:** Uninstall React Router to get TypeScript errors on imports. + - At this point I don’t know if you can do a gradual migration, but it seems likely + you could have multiple router providers, not desirable. + - The api’s between React Router and TanStack Router are very similar and could most + likely be handled in a sprint cycle or two if that is your companies way of doing + things. +- [ ] Create Routes for each existing React Router route we have +- [ ] Create root route +- [ ] Create router instance +- [ ] Add global module in main.tsx +- [ ] Remove any React Router (`createBrowserRouter` or `BrowserRouter`), `Routes`, and + `Route` Components from main.tsx +- [ ] **Optional:** Refactor `render` function for custom setup/providers - The repo + referenced above has an example - This was necessary in the case of Supertokens. + Supertoken has a specific setup with React Router and a different setup with all other + React implementations +- [ ] Set RouterProvider and pass it the router as the prop +- [ ] Replace all instances of React Router `Link` component with + `@tanstack/react-router` `Link` component + - [ ] Add `to` prop with literal path + - [ ] Add `params` prop, where necessary with params like so + `params={{ orderId: order.id }}` +- [ ] Replace all instances of React Router `useNavigate` hook with + `@tanstack/react-router` `useNavigate` hook + - [ ] Set `to` property and `params` property where needed +- [ ] Replace any React Router `Outlet`'s with the `@tanstack/react-router` equivalent +- [ ] If you are using `useSearchParams` hook from React Router, move the search params + default value to the validateSearch property on a Route definition. + - [ ] Instead of using the `useSearchParams` hook, use `@tanstack/react-router` + `Link`'s search property to update the search params state + - [ ] To read search params you can do something like the following + - `const { page } = useSearch({ from: productPage.fullPath })` +- [ ] If using React Router’s `useParams` hook, update the import to be from + `@tanstack/react-router` and set the `from` property to the literal path name where you + want to read the params object from + - So say we have a route with the path name `orders/$orderid`. + - In the `useParams` hook we would set up our hook like so: + `const params = useParams({ from: "/orders/$orderId" })` + - Then wherever we wanted to access the order id we would get it off of the params + object `params.orderId` + +# Migration from React Location + +Before you begin your journey in migrating from React Location, it's important that you +have a good understanding of the [Routing Concepts](../routing/routing-concepts.md) +and [Design Decisions](../decisions-on-dx.md) used by TanStack Router. + +## Differences between React Location and TanStack Router + +React Location and TanStack Router share much of same design decisions concepts, but +there are some key differences that you should be aware of. + +- React Location uses _generics_ to infer types for routes, while TanStack Router uses + _module declaration merging_ to infer types. +- Route configuration in React Location is done using a single array of route + definitions, while in TanStack Router, route configuration is done using a tree of + route definitions starting with + the [root route](../routing/routing-concepts.md#the-root-route). +- [File-based routing](../routing/file-based-routing.md) is the recommended way to define + routes in TanStack Router, while React Location only allows you to define routes in a + single file using a code-based approach. + - TanStack Router does support + a [code-based approach](../routing/code-based-routing.md) to defining routes, but + it is not recommended for most use cases. You can read more about why, over + here: [why is file-based routing the preferred way to define routes?](../decisions-on-dx.md#3-why-is-file-based-routing-the-preferred-way-to-define-routes) + +## Migration guide + +In this guide we'll go over the process of migrating +the [React Location Basic example](https://github.com/TanStack/router/tree/react-location/examples/basic) +over to TanStack Router using file-based routing, with the end goal of having the same +functionality as the original example (styling and other non-routing related code will be +omitted). + +> [!TIP] +> To use a code-based approach for defining your routes, you can read +> the [code-based Routing](../routing/code-based-routing.md) guide. + +### Step 1: Swap over to TanStack Router's dependencies + +First, we need to install the dependencies for TanStack Router. For detailed installation +instructions, see our [How to Install TanStack Router](./how-to/install.md) guide. + +```sh +npm install @tanstack/react-router @tanstack/router-devtools +``` + +And remove the React Location dependencies. + +```sh +npm uninstall @tanstack/react-location @tanstack/react-location-devtools +``` + +### Step 2: Use the file-based routing watcher + +If your project uses Vite (or one of the supported bundlers), you can use the TanStack +Router plugin to watch for changes in your routes files and automatically update the +routes configuration. + +Installation of the Vite plugin: + +```sh +npm install -D @tanstack/router-plugin +``` + +And add it to your `vite.config.js`: + +```js +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { tanstackRouter } from '@tanstack/router-plugin/vite' + +export default defineConfig({ + // ... + plugins: [tanstackRouter(), react()], +}) +``` + +However, if your application does not use Vite, you use one of our +other [supported bundlers](../routing/file-based-routing.md#getting-started-with-file-based-routing), +or you can use the `@tanstack/router-cli` package to watch for changes in your routes +files and automatically update the routes configuration. + +### Step 3: Add the file-based configuration file to your project + +Create a `tsr.config.json` file in the root of your project with the following content: + +```json +{ + "routesDirectory": "./src/routes", + "generatedRouteTree": "./src/routeTree.gen.ts" +} +``` + +You can find the full list of options for the `tsr.config.json` +file [here](../../../api/file-based-routing.md). + +### Step 4: Create the routes directory + +Create a `routes` directory in the `src` directory of your project. + +```sh +mkdir src/routes +``` + +### Step 5: Create the root route file + +```tsx +// src/routes/__root.tsx +import { createRootRoute, Outlet, Link } from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/router-devtools' + +export const Route = createRootRoute({ + component: () => { + return ( + <> +
+ + Home + + Posts +
+
+ + + + ) + }, +}) +``` + +### Step 6: Create the index route file + +```tsx +// src/routes/index.tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/')({ + component: Index, +}) +``` + +> You will need to move any related components and logic needed for the index route from +> the `src/index.tsx` file to the `src/routes/index.tsx` file. + +### Step 7: Create the posts route file + +```tsx +// src/routes/posts.tsx +import { createFileRoute, Link, Outlet } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts')({ + component: Posts, + loader: async () => { + const posts = await fetchPosts() + return { + posts, + } + }, +}) + +function Posts() { + const { posts } = Route.useLoaderData() + return ( +
+ + +
+ ) +} +``` + +> You will need to move any related components and logic needed for the posts route from +> the `src/index.tsx` file to the `src/routes/posts.tsx` file. + +### Step 8: Create the posts index route file + +```tsx +// src/routes/posts.index.tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts/')({ + component: PostsIndex, +}) +``` + +> You will need to move any related components and logic needed for the posts index route +> from the `src/index.tsx` file to the `src/routes/posts.index.tsx` file. + +### Step 9: Create the posts id route file + +```tsx +// src/routes/posts.$postId.tsx +import { createFileRoute } from '@tanstack/react-router' + +export const Route = createFileRoute('/posts/$postId')({ + component: PostsId, + loader: async ({ params: { postId } }) => { + const post = await fetchPost(postId) + return { + post, + } + }, +}) + +function PostsId() { + const { post } = Route.useLoaderData() + // ... +} +``` + +> You will need to move any related components and logic needed for the posts id route +> from the `src/index.tsx` file to the `src/routes/posts.$postId.tsx` file. + +### Step 10: Generate the route tree + +If you are using one of the supported bundlers, the route tree will be generated +automatically when you run the dev script. + +If you are not using one of the supported bundlers, you can generate the route tree by +running the following command: + +```sh +npx tsr generate +``` + +### Step 11: Update the main entry file to render the Router + +Once you've generated the route-tree, you can then update the `src/index.tsx` file to +create the router instance and render it. + +```tsx +// src/index.tsx +import React from 'react' +import ReactDOM from 'react-dom' +import { createRouter, RouterProvider } from '@tanstack/react-router' + +// Import the generated route tree +import { routeTree } from './routeTree.gen' + +// Create a new router instance +const router = createRouter({ routeTree }) + +// Register the router instance for type safety +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} + +const domElementId = 'root' // Assuming you have a root element with the id 'root' + +// Render the app +const rootElement = document.getElementById(domElementId) +if (!rootElement) { + throw new Error(`Element with id ${domElementId} not found`) +} + +ReactDOM.createRoot(rootElement).render( + + + , +) +``` + +### Finished! + +You should now have successfully migrated your application from React Location to +TanStack Router using file-based routing. + +React Location also has a few more features that you might be using in your application. +Here are some guides to help you migrate those features: + +- [Search params](../guide/search-params.md) +- [Data loading](../guide/data-loading.md) +- [History types](../guide/history-types.md) +- [Wildcard / Splat / Catch-all routes](../routing/routing-concepts.md#splat--catch-all-routes) +- [Authenticated routes](../guide/authenticated-routes.md) + +TanStack Router also has a few more features that you might want to explore: + +- [Router Context](../guide/router-context.md) +- [Preloading](../guide/preloading.md) +- [Pathless Layout Routes](../routing/routing-concepts.md#pathless-layout-routes) +- [Route masking](../guide/route-masking.md) +- [SSR](../guide/ssr.md) +- ... and more! + +If you are facing any issues or have any questions, feel free to ask for help in the +TanStack Discord. + +# Frequently Asked Questions + +Welcome to the TanStack Router FAQ! Here you'll find answers to common questions about +the TanStack Router. If you have a question that isn't answered here, please feel free to +ask in the [TanStack Discord](https://tlinz.com/discord). + +## Should I commit my `routeTree.gen.ts` file into git? + +Yes! Although the route tree file (i.e. `routeTree.gen.ts`) is generated by the TanStack +Router, it is essentially towards the runtime of your application. It is not a build +artifact. The route tree file is a critical part of your application's source code, and +it is used by the TanStack Router to build your application's routes at runtime. + +You should commit this file into git so that other developers can use it to build your +application. + +## Can I conditionally render the Root Route component? + +No, the root route is always rendered as it is the entry point of your application. + +If you need to conditionally render a route's component, this usually means that the page +content needs to be different based on some condition (e.g. user authentication). For +this use case, you should use +a [Layout Route](../routing/routing-concepts.md#layout-routes) or +a [Pathless Layout Route](../routing/routing-concepts.md#pathless-layout-routes) to +conditionally render the content. + +You can restrict access to these routes using a conditional check in the `beforeLoad` +function of the route. + +
+What does this look like? + +```tsx +// src/routes/_pathless-layout.tsx +import { createFileRoute, Outlet } from '@tanstack/react-router' +import { isAuthenticated } from '../utils/auth' + +export const Route = createFileRoute('/_pathless-layout', { + beforeLoad: async () => { + // Check if the user is authenticated + const authed = await isAuthenticated() + if (!authed) { + // Redirect the user to the login page + return '/login' + } + }, + component: PathlessLayoutRouteComponent, + // ... +}) + +function PathlessLayoutRouteComponent() { + return ( +
+

You are authed

+ +
+ ) +} +``` + +
+ + + + + diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/README.md b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/README.md new file mode 100644 index 0000000..0b66fc0 --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/README.md @@ -0,0 +1,320 @@ +Welcome to your new TanStack app! + +# Getting Started + +To run this application: + +```bash +pnpm install +pnpm start +``` + +# Building For Production + +To build this application for production: + +```bash +pnpm build +``` + +## Testing + +This project uses [Vitest](https://vitest.dev/) for testing. You can run the tests with: + +```bash +pnpm test +``` + +## Styling + +This project uses [Tailwind CSS](https://tailwindcss.com/) for styling. + +## Linting & Formatting + +This project uses [Biome](https://biomejs.dev/) for linting and formatting. The following +scripts are available: + +```bash +pnpm lint +pnpm format +pnpm check +``` + +## Routing + +This project uses [TanStack Router](https://tanstack.com/router). The initial setup is a +file based router. Which means that the routes are managed as files in `src/routes`. + +### Adding A Route + +To add a new route to your application just add another a new file in the `./src/routes` +directory. + +TanStack will automatically generate the content of the route file for you. + +Now that you have two routes you can use a `Link` component to navigate between them. + +### Adding Links + +To use SPA (Single Page Application) navigation you will need to import the `Link` +component from `@tanstack/react-router`. + +```tsx +import { Link } from "@tanstack/react-router"; +``` + +Then anywhere in your JSX you can use it like so: + +```tsx +About +``` + +This will create a link that will navigate to the `/about` route. + +More information on the `Link` component can be found in +the [Link documentation](https://tanstack.com/router/v1/docs/framework/react/api/router/linkComponent). + +### Using A Layout + +In the File Based Routing setup the layout is located in `src/routes/__root.tsx`. +Anything you add to the root route will appear in all the routes. The route content will +appear in the JSX where you use the `` component. + +Here is an example layout that includes a header: + +```tsx +import { Outlet, createRootRoute } from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' + +import { Link } from "@tanstack/react-router"; + +export const Route = createRootRoute({ + component: () => ( + <> +
+ +
+ + + + ), +}) +``` + +The `` component is not required so you can remove it if you +don't want it in your layout. + +More information on layouts can be found in +the [Layouts documentation](https://tanstack.com/router/latest/docs/framework/react/guide/routing-concepts#layouts). + +## Data Fetching + +There are multiple ways to fetch data in your application. You can use TanStack Query to +fetch data from a server. But you can also use the `loader` functionality built into +TanStack Router to load the data for a route before it's rendered. + +For example: + +```tsx +const peopleRoute = createRoute({ + getParentRoute: () => rootRoute, + path: "/people", + loader: async () => { + const response = await fetch("https://swapi.dev/api/people"); + return response.json() as Promise<{ + results: { + name: string; + }[]; + }>; + }, + component: () => { + const data = peopleRoute.useLoaderData(); + return ( +
    + {data.results.map((person) => ( +
  • {person.name}
  • + ))} +
+ ); + }, +}); +``` + +Loaders simplify your data fetching logic dramatically. Check out more information in +the [Loader documentation](https://tanstack.com/router/latest/docs/framework/react/guide/data-loading#loader-parameters). + +### React-Query + +React-Query is an excellent addition or alternative to route loading and integrating it +into you application is a breeze. + +First add your dependencies: + +```bash +pnpm add @tanstack/react-query @tanstack/react-query-devtools +``` + +Next we'll need to create a query client and provider. We recommend putting those in +`main.tsx`. + +```tsx +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; + +// ... + +const queryClient = new QueryClient(); + +// ... + +if (!rootElement.innerHTML) { + const root = ReactDOM.createRoot(rootElement); + + root.render( + + + + ); +} +``` + +You can also add TanStack Query Devtools to the root route (optional). + +```tsx +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; + +const rootRoute = createRootRoute({ + component: () => ( + <> + + + + + ), +}); +``` + +Now you can use `useQuery` to fetch your data. + +```tsx +import { useQuery } from "@tanstack/react-query"; + +import "./App.css"; + +function App() { + const {data} = useQuery({ + queryKey: ["people"], + queryFn: () => + fetch("https://swapi.dev/api/people") + .then((res) => res.json()) + .then((data) => data.results as { name: string }[]), + initialData: [], + }); + + return ( +
+
    + {data.map((person) => ( +
  • {person.name}
  • + ))} +
+
+ ); +} + +export default App; +``` + +You can find out everything you need to know on how to use React-Query in +the [React-Query documentation](https://tanstack.com/query/latest/docs/framework/react/overview). + +## State Management + +Another common requirement for React applications is state management. There are many +options for state management in React. TanStack Store provides a great starting point for +your project. + +First you need to add TanStack Store as a dependency: + +```bash +pnpm add @tanstack/store +``` + +Now let's create a simple counter in the `src/App.tsx` file as a demonstration. + +```tsx +import { useStore } from "@tanstack/react-store"; +import { Store } from "@tanstack/store"; +import "./App.css"; + +const countStore = new Store(0); + +function App() { + const count = useStore(countStore); + return ( +
+ +
+ ); +} + +export default App; +``` + +One of the many nice features of TanStack Store is the ability to derive state from other +state. That derived state will update when the base state updates. + +Let's check this out by doubling the count using derived state. + +```tsx +import { useStore } from "@tanstack/react-store"; +import { Store, Derived } from "@tanstack/store"; +import "./App.css"; + +const countStore = new Store(0); + +const doubledStore = new Derived({ + fn: () => countStore.state * 2, + deps: [countStore], +}); +doubledStore.mount(); + +function App() { + const count = useStore(countStore); + const doubledCount = useStore(doubledStore); + + return ( +
+ +
Doubled - {doubledCount}
+
+ ); +} + +export default App; +``` + +We use the `Derived` class to create a new store that is derived from another store. The +`Derived` class has a `mount` method that will start the derived store updating. + +Once we've created the derived store we can use it in the `App` component just like we +would any other store using the `useStore` hook. + +You can find out everything you need to know on how to use TanStack Store in +the [TanStack Store documentation](https://tanstack.com/store/latest). + +# Demo files + +Files prefixed with `demo` can be safely deleted. They are there to provide a starting +point for you to play around with the features you've installed. + +# Learn More + +You can learn more about all of the offerings from TanStack in +the [TanStack documentation](https://tanstack.com). diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/biome.json b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/biome.json new file mode 100644 index 0000000..fdc472a --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/biome.json @@ -0,0 +1,40 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.1.2/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "includes": [ + "**/src/**/*", + "**/.vscode/**/*", + "**/index.html", + "**/vite.config.js", + "!**/src/routeTree.gen.ts" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "tab" + }, + "assist": { + "actions": { + "source": { + "organizeImports": "on" + } + } + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double" + } + } +} diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/index.html b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/index.html new file mode 100644 index 0000000..7a1ac46 --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/index.html @@ -0,0 +1,31 @@ + + + + + + + + + + + Create TanStack App - a + + + +
+ + + diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/package.json b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/package.json new file mode 100644 index 0000000..c1ec50d --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/package.json @@ -0,0 +1,49 @@ +{ + "name": "data-dashboard-iteration-react", + "version": "0.0.1", + "description": "A React-based data dashboard application with Tanstack.", + "type": "module", + "scripts": { + "dev": "vite --port 3000", + "start": "vite --port 3000", + "build": "vite build && tsc", + "serve": "vite preview", + "test": "vitest run", + "format": "biome format --write", + "lint": "biome lint", + "fix": "biome check --write --unsafe", + "check": "biome check" + }, + "dependencies": { + "@faker-js/faker": "^9.9.0", + "@tailwindcss/vite": "^4.1.11", + "@tanstack/match-sorter-utils": "^8.19.4", + "@tanstack/react-form": "^1.15.0", + "@tanstack/react-query": "^5.83.0", + "@tanstack/react-query-devtools": "^5.83.0", + "@tanstack/react-router": "^1.130.1", + "@tanstack/react-router-devtools": "^1.130.1", + "@tanstack/react-table": "^8.21.3", + "@tanstack/router-plugin": "^1.130.1", + "echarts": "^5.6.0", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "tailwindcss": "^4.1.11", + "zod": "^4.0.10" + }, + "devDependencies": { + "@biomejs/biome": "2.1.2", + "@testing-library/dom": "^10.4.0", + "@testing-library/react": "^16.3.0", + "@types/node": "^24.1.0", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.7.0", + "jsdom": "^26.1.0", + "typescript": "^5.8.3", + "vite": "^7.0.6", + "vitest": "^3.2.4", + "web-vitals": "^5.0.3" + }, + "packageManager": "pnpm@10.13.1+sha512.37ebf1a5c7a30d5fabe0c5df44ee8da4c965ca0c5af3dbab28c3a1681b70a256218d05c81c9c0dcf767ef6b8551eb5b960042b9ed4300c59242336377e01cfad" +} diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/pnpm-lock.yaml b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/pnpm-lock.yaml new file mode 100644 index 0000000..9abd62f --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/pnpm-lock.yaml @@ -0,0 +1,3136 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@faker-js/faker': + specifier: ^9.9.0 + version: 9.9.0 + '@tailwindcss/vite': + specifier: ^4.1.11 + version: 4.1.11(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3)) + '@tanstack/match-sorter-utils': + specifier: ^8.19.4 + version: 8.19.4 + '@tanstack/react-form': + specifier: ^1.15.0 + version: 1.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/react-query': + specifier: ^5.83.0 + version: 5.83.0(react@19.1.0) + '@tanstack/react-query-devtools': + specifier: ^5.83.0 + version: 5.83.0(@tanstack/react-query@5.83.0(react@19.1.0))(react@19.1.0) + '@tanstack/react-router': + specifier: ^1.130.1 + version: 1.130.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/react-router-devtools': + specifier: ^1.130.1 + version: 1.130.1(@tanstack/react-router@1.130.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.130.1)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.7)(tiny-invariant@1.3.3) + '@tanstack/react-table': + specifier: ^8.21.3 + version: 8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/router-plugin': + specifier: ^1.130.1 + version: 1.130.1(@tanstack/react-router@1.130.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3)) + echarts: + specifier: ^5.6.0 + version: 5.6.0 + react: + specifier: ^19.1.0 + version: 19.1.0 + react-dom: + specifier: ^19.1.0 + version: 19.1.0(react@19.1.0) + tailwindcss: + specifier: ^4.1.11 + version: 4.1.11 + zod: + specifier: ^4.0.10 + version: 4.0.10 + devDependencies: + '@biomejs/biome': + specifier: 2.1.2 + version: 2.1.2 + '@testing-library/dom': + specifier: ^10.4.0 + version: 10.4.0 + '@testing-library/react': + specifier: ^16.3.0 + version: 16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@types/node': + specifier: ^24.1.0 + version: 24.1.0 + '@types/react': + specifier: ^19.1.8 + version: 19.1.8 + '@types/react-dom': + specifier: ^19.1.6 + version: 19.1.6(@types/react@19.1.8) + '@vitejs/plugin-react': + specifier: ^4.7.0 + version: 4.7.0(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3)) + jsdom: + specifier: ^26.1.0 + version: 26.1.0 + typescript: + specifier: ^5.8.3 + version: 5.8.3 + vite: + specifier: ^7.0.6 + version: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3) + vitest: + specifier: ^3.2.4 + version: 3.2.4(@types/node@24.1.0)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3) + web-vitals: + specifier: ^5.0.3 + version: 5.0.3 + +packages: + + '@ampproject/remapping@2.3.0': + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + + '@asamuzakjp/css-color@3.2.0': + resolution: {integrity: sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.0': + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.0': + resolution: {integrity: sha512-UlLAnTPrFdNGoFtbSXwcGFQBtQZJCNjaN6hQNP3UPvuNXT1i82N26KL3dZeIpNalWywr9IuQuncaAfUaS1g6sQ==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.0': + resolution: {integrity: sha512-lJjzvrbEeWrhB4P3QBsH7tey117PjLZnDbLiQEKjQ/fNJTjuq4HSqgFA+UNSwZT8D7dxxbnuSBMsa1lrWzKlQg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.27.1': + resolution: {integrity: sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.27.1': + resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.27.3': + resolution: {integrity: sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.27.1': + resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.2': + resolution: {integrity: sha512-/V9771t+EgXz62aCcyofnQhGM8DQACbRhvzKFsXKC9QM+5MadF8ZmIm0crDMaz3+o0h0zXfJnd4EhbYbxsrcFw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.0': + resolution: {integrity: sha512-jVZGvOxOuNSsuQuLRTh13nU0AogFlw32w/MT+LV6D3sP5WdbW61E77RnkbaO2dUvmPAYrBDJXGn5gGS6tH4j8g==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.27.1': + resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.0': + resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.27.1': + resolution: {integrity: sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.2': + resolution: {integrity: sha512-KHp2IflsnGywDjBWDkR9iEqiWSpc8GIi0lgTT3mOElT0PP1tG26P4tmFI2YvAdzgq9RGyoHZQEIEdZy6Ec5xCA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.0': + resolution: {integrity: sha512-mGe7UK5wWyh0bKRfupsUchrQGqvDbZDbKJw+kcRGSmdHVYrv+ltd0pnpDTVpiTqnaBru9iEvA8pz8W46v0Amwg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + + '@biomejs/biome@2.1.2': + resolution: {integrity: sha512-yq8ZZuKuBVDgAS76LWCfFKHSYIAgqkxVB3mGVVpOe2vSkUTs7xG46zXZeNPRNVjiJuw0SZ3+J2rXiYx0RUpfGg==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.1.2': + resolution: {integrity: sha512-leFAks64PEIjc7MY/cLjE8u5OcfBKkcDB0szxsWUB4aDfemBep1WVKt0qrEyqZBOW8LPHzrFMyDl3FhuuA0E7g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.1.2': + resolution: {integrity: sha512-Nmmv7wRX5Nj7lGmz0FjnWdflJg4zii8Ivruas6PBKzw5SJX/q+Zh2RfnO+bBnuKLXpj8kiI2x2X12otpH6a32A==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.1.2': + resolution: {integrity: sha512-qgHvafhjH7Oca114FdOScmIKf1DlXT1LqbOrrbR30kQDLFPEOpBG0uzx6MhmsrmhGiCFCr2obDamu+czk+X0HQ==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.1.2': + resolution: {integrity: sha512-NWNy2Diocav61HZiv2enTQykbPP/KrA/baS7JsLSojC7Xxh2nl9IczuvE5UID7+ksRy2e7yH7klm/WkA72G1dw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.1.2': + resolution: {integrity: sha512-xlB3mU14ZUa3wzLtXfmk2IMOGL+S0aHFhSix/nssWS/2XlD27q+S6f0dlQ8WOCbYoXcuz8BCM7rCn2lxdTrlQA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.1.2': + resolution: {integrity: sha512-Km/UYeVowygTjpX6sGBzlizjakLoMQkxWbruVZSNE6osuSI63i4uCeIL+6q2AJlD3dxoiBJX70dn1enjQnQqwA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.1.2': + resolution: {integrity: sha512-G8KWZli5ASOXA3yUQgx+M4pZRv3ND16h77UsdunUL17uYpcL/UC7RkWTdkfvMQvogVsAuz5JUcBDjgZHXxlKoA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.1.2': + resolution: {integrity: sha512-9zajnk59PMpjBkty3bK2IrjUsUHvqe9HWwyAWQBjGLE7MIBjbX2vwv1XPEhmO2RRuGoTkVx3WCanHrjAytICLA==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@csstools/color-helpers@5.0.2': + resolution: {integrity: sha512-JqWH1vsgdGcw2RR6VliXXdA0/59LttzlU8UlRT/iUUsEeWfYq8I+K0yhihEUTTHLRm1EXvpsCx3083EU15ecsA==} + engines: {node: '>=18'} + + '@csstools/css-calc@2.1.4': + resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-color-parser@3.0.10': + resolution: {integrity: sha512-TiJ5Ajr6WRd1r8HSiwJvZBiJOqtH86aHpUjq5aEKWHiII2Qfjqd/HCWKPOW8EP4vcspXbHnXrwIDlu5savQipg==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-parser-algorithms': ^3.0.5 + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-parser-algorithms@3.0.5': + resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} + engines: {node: '>=18'} + peerDependencies: + '@csstools/css-tokenizer': ^3.0.4 + + '@csstools/css-tokenizer@3.0.4': + resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} + engines: {node: '>=18'} + + '@esbuild/aix-ppc64@0.25.8': + resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.8': + resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.8': + resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.8': + resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.8': + resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.8': + resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.8': + resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.8': + resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.8': + resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.8': + resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.8': + resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.8': + resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.8': + resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.8': + resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.8': + resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.8': + resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.8': + resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.8': + resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.8': + resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.8': + resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.8': + resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.8': + resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.8': + resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.8': + resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.8': + resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.8': + resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@faker-js/faker@9.9.0': + resolution: {integrity: sha512-OEl393iCOoo/z8bMezRlJu+GlRGlsKbUAN7jKB6LhnKoqKve5DXRpalbItIIcwnCjs1k/FOPjFzcA6Qn+H+YbA==} + engines: {node: '>=18.0.0', npm: '>=9.0.0'} + + '@isaacs/fs-minipass@4.0.1': + resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==} + engines: {node: '>=18.0.0'} + + '@jridgewell/gen-mapping@0.3.12': + resolution: {integrity: sha512-OuLGC46TjB5BbN1dH8JULVVZY4WTdkF7tV9Ys6wLL1rubZnCMstOhNHueU5bLCrnRuDhKPDM4g6sw4Bel5Gzqg==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.4': + resolution: {integrity: sha512-VT2+G1VQs/9oz078bLrYbecdZKs912zQlkelYpuf+SXF+QvZDYJlbx/LSx+meSAwdDFnF8FVXW92AVjjkVmgFw==} + + '@jridgewell/trace-mapping@0.3.29': + resolution: {integrity: sha512-uw6guiW/gcAGPDhLmd77/6lW8QLeiV5RUTsAX46Db6oLhGaVj4lhnPwb184s1bkc8kdVg/+h988dro8GRDpmYQ==} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.45.3': + resolution: {integrity: sha512-8oQkCTve4H4B4JpmD2FV7fV2ZPTxJHN//bRhCqPUU8v6c5APlxteAXyc7BFaEb4aGpUzrPLU4PoAcGhwmRzZTA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.45.3': + resolution: {integrity: sha512-StOsmdXHU2hx3UFTTs6yYxCSwSIgLsfjUBICXyWj625M32OOjakXlaZuGKL+jA3Nvv35+hMxrm/64eCoT07SYQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.45.3': + resolution: {integrity: sha512-6CfLF3eqKhCdhK0GUnR5ZS99OFz+dtOeB/uePznLKxjCsk5QjT/V0eSEBb4vj+o/ri3i35MseSEQHCLLAgClVw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.45.3': + resolution: {integrity: sha512-QLWyWmAJG9elNTNLdcSXUT/M+J7DhEmvs1XPHYcgYkse3UHf9iWTJ+yTPlKMIetiQnNi+cNp+gY4gvjDpREfKw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.45.3': + resolution: {integrity: sha512-ZOvBq+5nL0yrZIEo1eq6r7MPvkJ8kC1XATS/yHvcq3WbDNKNKBQ1uIF4hicyzDMoJt72G+sn1nKsFXpifZyRDA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.45.3': + resolution: {integrity: sha512-AYvGR07wecEnyYSovyJ71pTOulbNvsrpRpK6i/IM1b0UGX1vFx51afYuPYPxnvE9aCl5xPnhQicEvdIMxClRgQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.45.3': + resolution: {integrity: sha512-Yx8Cp38tfRRToVLuIWzBHV25/QPzpUreOPIiUuNV7KahNPurYg2pYQ4l7aYnvpvklO1riX4643bXLvDsYSBIrA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.45.3': + resolution: {integrity: sha512-4dIYRNxlXGDKnO6qgcda6LxnObPO6r1OBU9HG8F9pAnHHLtfbiOqCzDvkeHknx+5mfFVH4tWOl+h+cHylwsPWA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.45.3': + resolution: {integrity: sha512-M6uVlWKmhLN7LguLDu6396K1W5IBlAaRonjlHQgc3s4dOGceu0FeBuvbXiUPYvup/6b5Ln7IEX7XNm68DN4vrg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.45.3': + resolution: {integrity: sha512-emaYiOTQJUd6fC9a6jcw9zIWtzaUiuBC+vomggaM4In2iOra/lA6IMHlqZqQZr08NYXrOPMVigreLMeSAwv3Uw==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loongarch64-gnu@4.45.3': + resolution: {integrity: sha512-3P77T5AQ4UfVRJSrTKLiUZDJ6XsxeP80027bp6mOFh8sevSD038mYuIYFiUtrSJxxgFb+NgRJFF9oIa0rlUsmg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.45.3': + resolution: {integrity: sha512-/VPH3ZVeSlmCBPhZdx/+4dMXDjaGMhDsWOBo9EwSkGbw2+OAqaslL53Ao2OqCxR0GgYjmmssJ+OoG+qYGE7IBg==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.45.3': + resolution: {integrity: sha512-Hs5if0PjROl1MGMmZX3xMAIfqcGxQE2SJWUr/CpDQsOQn43Wq4IvXXxUMWtiY/BrzdqCCJlRgJ5DKxzS3qWkCw==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.45.3': + resolution: {integrity: sha512-Qm0WOwh3Lk388+HJFl1ILGbd2iOoQf6yl4fdGqOjBzEA+5JYbLcwd+sGsZjs5pkt8Cr/1G42EiXmlRp9ZeTvFA==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.45.3': + resolution: {integrity: sha512-VJdknTaYw+TqXzlh9c7vaVMh/fV2sU8Khfk4a9vAdYXJawpjf6z3U1k7vDWx2IQ9ZOPoOPxgVpDfYOYhxD7QUA==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.45.3': + resolution: {integrity: sha512-SUDXU5YabLAMl86FpupSQQEWzVG8X0HM+Q/famnJusbPiUgQnTGuSxtxg4UAYgv1ZmRV1nioYYXsgtSokU/7+Q==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.45.3': + resolution: {integrity: sha512-ezmqknOUFgZMN6wW+Avlo4sXF3Frswd+ncrwMz4duyZ5Eqd+dAYgJ+A1MY+12LNZ7XDhCiijJceueYvtnzdviw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-win32-arm64-msvc@4.45.3': + resolution: {integrity: sha512-1YfXoUEE++gIW66zNB9Twd0Ua5xCXpfYppFUxVT/Io5ZT3fO6Se+C/Jvmh3usaIHHyi53t3kpfjydO2GAy5eBA==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.45.3': + resolution: {integrity: sha512-Iok2YA3PvC163rVZf2Zy81A0g88IUcSPeU5pOilcbICXre2EP1mxn1Db/l09Z/SK1vdSLtpJXAnwGuMOyf5O9g==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.45.3': + resolution: {integrity: sha512-HwHCH5GQTOeGYP5wBEBXFVhfQecwRl24Rugoqhh8YwGarsU09bHhOKuqlyW4ZolZCan3eTUax7UJbGSmKSM51A==} + cpu: [x64] + os: [win32] + + '@tailwindcss/node@4.1.11': + resolution: {integrity: sha512-yzhzuGRmv5QyU9qLNg4GTlYI6STedBWRE7NjxP45CsFYYq9taI0zJXZBMqIC/c8fViNLhmrbpSFS57EoxUmD6Q==} + + '@tailwindcss/oxide-android-arm64@4.1.11': + resolution: {integrity: sha512-3IfFuATVRUMZZprEIx9OGDjG3Ou3jG4xQzNTvjDoKmU9JdmoCohQJ83MYd0GPnQIu89YoJqvMM0G3uqLRFtetg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.11': + resolution: {integrity: sha512-ESgStEOEsyg8J5YcMb1xl8WFOXfeBmrhAwGsFxxB2CxY9evy63+AtpbDLAyRkJnxLy2WsD1qF13E97uQyP1lfQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.11': + resolution: {integrity: sha512-EgnK8kRchgmgzG6jE10UQNaH9Mwi2n+yw1jWmof9Vyg2lpKNX2ioe7CJdf9M5f8V9uaQxInenZkOxnTVL3fhAw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.11': + resolution: {integrity: sha512-xdqKtbpHs7pQhIKmqVpxStnY1skuNh4CtbcyOHeX1YBE0hArj2romsFGb6yUmzkq/6M24nkxDqU8GYrKrz+UcA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': + resolution: {integrity: sha512-ryHQK2eyDYYMwB5wZL46uoxz2zzDZsFBwfjssgB7pzytAeCCa6glsiJGjhTEddq/4OsIjsLNMAiMlHNYnkEEeg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': + resolution: {integrity: sha512-mYwqheq4BXF83j/w75ewkPJmPZIqqP1nhoghS9D57CLjsh3Nfq0m4ftTotRYtGnZd3eCztgbSPJ9QhfC91gDZQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': + resolution: {integrity: sha512-m/NVRFNGlEHJrNVk3O6I9ggVuNjXHIPoD6bqay/pubtYC9QIdAMpS+cswZQPBLvVvEF6GtSNONbDkZrjWZXYNQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': + resolution: {integrity: sha512-YW6sblI7xukSD2TdbbaeQVDysIm/UPJtObHJHKxDEcW2exAtY47j52f8jZXkqE1krdnkhCMGqP3dbniu1Te2Fg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.11': + resolution: {integrity: sha512-e3C/RRhGunWYNC3aSF7exsQkdXzQ/M+aYuZHKnw4U7KQwTJotnWsGOIVih0s2qQzmEzOFIJ3+xt7iq67K/p56Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.11': + resolution: {integrity: sha512-Xo1+/GU0JEN/C/dvcammKHzeM6NqKovG+6921MR6oadee5XPBaKOumrJCXvopJ/Qb5TH7LX/UAywbqrP4lax0g==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': + resolution: {integrity: sha512-UgKYx5PwEKrac3GPNPf6HVMNhUIGuUh4wlDFR2jYYdkX6pL/rn73zTq/4pzUm8fOjAn5L8zDeHp9iXmUGOXZ+w==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': + resolution: {integrity: sha512-YfHoggn1j0LK7wR82TOucWc5LDCguHnoS879idHekmmiR7g9HUtMw9MI0NHatS28u/Xlkfi9w5RJWgz2Dl+5Qg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.11': + resolution: {integrity: sha512-Q69XzrtAhuyfHo+5/HMgr1lAiPP/G40OMFAnws7xcFEYqcypZmdW8eGXaOUIeOl1dzPJBPENXgbjsOyhg2nkrg==} + engines: {node: '>= 10'} + + '@tailwindcss/vite@4.1.11': + resolution: {integrity: sha512-RHYhrR3hku0MJFRV+fN2gNbDNEh3dwKvY8XJvTxCSXeMOsCRSr+uKvDWQcbizrHgjML6ZmTE5OwMrl5wKcujCw==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@tanstack/form-core@1.15.0': + resolution: {integrity: sha512-zMNyxb/J/JnFmW4Gzb1TSxaXmwNhvsaF9p3dGRpE93TMGp2ojPKK7V5LZ43ZV7iFTYWTL8NOIU8ZXuf9qZVkmw==} + + '@tanstack/history@1.129.7': + resolution: {integrity: sha512-I3YTkbe4RZQN54Qw4+IUhOjqG2DdbG2+EBWuQfew4MEk0eddLYAQVa50BZVww4/D2eh5I9vEk2Fd1Y0Wty7pug==} + engines: {node: '>=12'} + + '@tanstack/match-sorter-utils@8.19.4': + resolution: {integrity: sha512-Wo1iKt2b9OT7d+YGhvEPD3DXvPv2etTusIMhMUoG7fbhmxcXCtIjJDEygy91Y2JFlwGyjqiBPRozme7UD8hoqg==} + engines: {node: '>=12'} + + '@tanstack/query-core@5.83.0': + resolution: {integrity: sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==} + + '@tanstack/query-devtools@5.81.2': + resolution: {integrity: sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==} + + '@tanstack/react-form@1.15.0': + resolution: {integrity: sha512-bAawFDxR1wLn+eXli6MSyS4Nw0vTyHuW3CybjZGtk7NIcYxyaAm9cW/jPUX2j/KMDtjVNg5RMpfmufxDrsNHyA==} + peerDependencies: + '@tanstack/react-start': ^1.112.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + vinxi: ^0.5.0 + peerDependenciesMeta: + '@tanstack/react-start': + optional: true + vinxi: + optional: true + + '@tanstack/react-query-devtools@5.83.0': + resolution: {integrity: sha512-yfp8Uqd3I1jgx8gl0lxbSSESu5y4MO2ThOPBnGNTYs0P+ZFu+E9g5IdOngyUGuo6Uz6Qa7p9TLdZEX3ntik2fQ==} + peerDependencies: + '@tanstack/react-query': ^5.83.0 + react: ^18 || ^19 + + '@tanstack/react-query@5.83.0': + resolution: {integrity: sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ==} + peerDependencies: + react: ^18 || ^19 + + '@tanstack/react-router-devtools@1.130.1': + resolution: {integrity: sha512-+WERFgdGjrJY4H64RRWIZa5W4tCmDWJa1cMXFwGoeKQIPXWqmjje27UhF+374HxSmRz1v+XjuPmfq76gq6rmMQ==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/react-router': ^1.130.1 + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-router@1.130.1': + resolution: {integrity: sha512-tFJQAUrzA8xgCPHttTYw4dXrq3FfhQmcnR7BmUa+pTBAKKKc8PO+v4Uwz4AlBxPpCLAzdkC1AuHrMyWSDwBlNA==} + engines: {node: '>=12'} + peerDependencies: + react: '>=18.0.0 || >=19.0.0' + react-dom: '>=18.0.0 || >=19.0.0' + + '@tanstack/react-store@0.7.3': + resolution: {integrity: sha512-3Dnqtbw9P2P0gw8uUM8WP2fFfg8XMDSZCTsywRPZe/XqqYW8PGkXKZTvP0AHkE4mpqP9Y43GpOg9vwO44azu6Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@tanstack/react-table@8.21.3': + resolution: {integrity: sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==} + engines: {node: '>=12'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + '@tanstack/router-core@1.130.1': + resolution: {integrity: sha512-xtuBT++KaHXsO8ZaHg4xtHRlUPw0ThuQDxNKlvjMt2IeU+P5v6lcuLb1dn9rhFOrkPpevQZtyGOSf0XbwbXJPA==} + engines: {node: '>=12'} + + '@tanstack/router-devtools-core@1.130.1': + resolution: {integrity: sha512-/W0y7lcBFbpVX1ZZpvnqf+ybO6xSwReDf9SJpIj1tuqqrAfcxTypq3/LTMQ2sr/mlwCcZ+bgKNmoQ87jRacdQg==} + engines: {node: '>=12'} + peerDependencies: + '@tanstack/router-core': ^1.130.1 + csstype: ^3.0.10 + solid-js: '>=1.9.5' + tiny-invariant: ^1.3.3 + peerDependenciesMeta: + csstype: + optional: true + + '@tanstack/router-generator@1.130.1': + resolution: {integrity: sha512-9FYC2wu5QPTdqVzO1LBoDkuElOJAhlIE6afk2Hqsi9p+a3/czYsOePVMxYeKn86w77E5g3LaKY1Cg58cCMUrCQ==} + engines: {node: '>=12'} + + '@tanstack/router-plugin@1.130.1': + resolution: {integrity: sha512-vrYyFvEq9fTiInpH8WZCPYHOmFLSEl1GPT+xix4IC0Jp6miqzxpoU2s7zoFpu5X/ZLnL/S6XsdfCm3qlDRf69w==} + engines: {node: '>=12'} + peerDependencies: + '@rsbuild/core': '>=1.0.2' + '@tanstack/react-router': ^1.130.1 + vite: '>=5.0.0 || >=6.0.0' + vite-plugin-solid: ^2.11.2 + webpack: '>=5.92.0' + peerDependenciesMeta: + '@rsbuild/core': + optional: true + '@tanstack/react-router': + optional: true + vite: + optional: true + vite-plugin-solid: + optional: true + webpack: + optional: true + + '@tanstack/router-utils@1.129.7': + resolution: {integrity: sha512-I2OyQF5U6sxHJApXKCUmCncTHKcpj4681FwyxpYg5QYOatHcn/zVMl7Rj4h36fu8/Lo2ZRLxUMd5kmXgp5Pb/A==} + engines: {node: '>=12'} + + '@tanstack/store@0.7.2': + resolution: {integrity: sha512-RP80Z30BYiPX2Pyo0Nyw4s1SJFH2jyM6f9i3HfX4pA+gm5jsnYryscdq2aIQLnL4TaGuQMO+zXmN9nh1Qck+Pg==} + + '@tanstack/table-core@8.21.3': + resolution: {integrity: sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==} + engines: {node: '>=12'} + + '@tanstack/virtual-file-routes@1.129.7': + resolution: {integrity: sha512-a+MxoAXG+Sq94Jp67OtveKOp2vQq75AWdVI8DRt6w19B0NEqpfm784FTLbVp/qdR1wmxCOmKAvElGSIiBOx5OQ==} + engines: {node: '>=12'} + + '@testing-library/dom@10.4.0': + resolution: {integrity: sha512-pemlzrSESWbdAloYml3bAJMEfNh1Z7EduzqPKprCH5S341frlpYnUEW0H72dLxa6IsYr+mPno20GiSm+h9dEdQ==} + engines: {node: '>=18'} + + '@testing-library/react@16.3.0': + resolution: {integrity: sha512-kFSyxiEDwv1WLl2fgsq6pPBbw5aWKrsY2/noi1Id0TK0UParSF62oFQFGHXIyaG4pp2tEub/Zlel+fjjZILDsw==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.7': + resolution: {integrity: sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==} + + '@types/chai@5.2.2': + resolution: {integrity: sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/node@24.1.0': + resolution: {integrity: sha512-ut5FthK5moxFKH2T1CUOC6ctR67rQRvvHdFLCD2Ql6KXmMuCrjsSsRI9UsLCm9M18BMwClv4pn327UvB7eeO1w==} + + '@types/react-dom@19.1.6': + resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==} + peerDependencies: + '@types/react': ^19.0.0 + + '@types/react@19.1.8': + resolution: {integrity: sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/expect@3.2.4': + resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==} + + '@vitest/mocker@3.2.4': + resolution: {integrity: sha512-46ryTE9RZO/rfDd7pEqFl7etuyzekzEhUbTW3BvmeO/BcCMEgq59BKhek3dXDWgAj4oMK6OZi+vRr1wPW6qjEQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^5.0.0 || ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@3.2.4': + resolution: {integrity: sha512-IVNZik8IVRJRTr9fxlitMKeJeXFFFN0JaB9PHPGQ8NKQbGpfjlTx9zO4RefN8gp7eqjNy8nyK3NZmBzOPeIxtA==} + + '@vitest/runner@3.2.4': + resolution: {integrity: sha512-oukfKT9Mk41LreEW09vt45f8wx7DordoWUZMYdY/cyAk7w5TWkTRCNZYF7sX7n2wB7jyGAl74OxgwhPgKaqDMQ==} + + '@vitest/snapshot@3.2.4': + resolution: {integrity: sha512-dEYtS7qQP2CjU27QBC5oUOxLE/v5eLkGqPE0ZKEIDGMs4vKWe7IjgLOeauHsR0D5YuuycGRO5oSRXnwnmA78fQ==} + + '@vitest/spy@3.2.4': + resolution: {integrity: sha512-vAfasCOe6AIK70iP5UD11Ac4siNUNJ9i/9PZ3NKx07sG6sUxeag1LWdNrMWeKKYBLlzuK+Gn65Yd5nyL6ds+nw==} + + '@vitest/utils@3.2.4': + resolution: {integrity: sha512-fB2V0JFrQSMsCo9HiSq3Ezpdv4iYaXRG1Sx8edX3MwxfyNn83mKiGzOcH+Fkxt4MHxr3y42fQi1oeAInqgX2QA==} + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansis@4.1.0: + resolution: {integrity: sha512-BGcItUBWSMRgOCe+SVZJ+S7yTRG0eGt9cXAHev72yuGcY23hnLA7Bky5L/xLyPINoSN95geovfBkqoTlNZYa7w==} + engines: {node: '>=14'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + ast-types@0.16.1: + resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==} + engines: {node: '>=4'} + + babel-dead-code-elimination@1.0.10: + resolution: {integrity: sha512-DV5bdJZTzZ0zn0DC24v3jD7Mnidh6xhKa4GfKCbq3sfW8kaWhDdZjP3i81geA8T33tdYqWKw4D3fVv0CwEgKVA==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.25.1: + resolution: {integrity: sha512-KGj0KoOMXLpSNkkEI6Z6mShmQy0bc1I+T7K9N81k4WWMrfz+6fQ6es80B/YLAeRoKvjYE1YSHHOW1qe9xIVzHw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + caniuse-lite@1.0.30001727: + resolution: {integrity: sha512-pB68nIHmbN6L/4C6MH1DokyR3bYqFwjaSs/sWDHGj4CTcFtQUQMuJftVwWkXq7mNWOybD3KhUv3oWHoGxgP14Q==} + + chai@5.2.1: + resolution: {integrity: sha512-5nFxhUrX0PqtyogoYOA8IPswy5sZFTOsBFl/9bNsmDLgsxYTzSZQJDPppDnZPTQbzSEm0hqGjWPzRemQCYbD6A==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + check-error@2.1.1: + resolution: {integrity: sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==} + engines: {node: '>= 16'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@3.0.0: + resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==} + engines: {node: '>=18'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + + cssstyle@4.6.0: + resolution: {integrity: sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==} + engines: {node: '>=18'} + + csstype@3.1.3: + resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + + data-urls@5.0.0: + resolution: {integrity: sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==} + engines: {node: '>=18'} + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + decode-formdata@0.9.0: + resolution: {integrity: sha512-q5uwOjR3Um5YD+ZWPOF/1sGHVW9A5rCrRwITQChRXlmPkxDFBqCm4jNTIVdGHNH9OnR+V9MoZVgRhsFb+ARbUw==} + + deep-eql@5.0.2: + resolution: {integrity: sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==} + engines: {node: '>=6'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.0.4: + resolution: {integrity: sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==} + engines: {node: '>=8'} + + devalue@5.1.1: + resolution: {integrity: sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==} + + diff@8.0.2: + resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + engines: {node: '>=0.3.1'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + echarts@5.6.0: + resolution: {integrity: sha512-oTbVTsXfKuEhxftHqL5xprgLoc0k7uScAwtryCgWF6hPYFLRwOUHiFmHGCBKP5NPFNkDVopOieyUqYGH8Fa3kA==} + + electron-to-chromium@1.5.191: + resolution: {integrity: sha512-xcwe9ELcuxYLUFqZZxL19Z6HVKcvNkIwhbHUz7L3us6u12yR+7uY89dSl570f/IqNthx8dAw3tojG7i4Ni4tDA==} + + enhanced-resolve@5.18.2: + resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==} + engines: {node: '>=10.13.0'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esbuild@0.25.8: + resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.2.2: + resolution: {integrity: sha512-JhFGDVJ7tmDJItKhYgJCGLOWjuK9vPxiXoUFLwLDc99NlmklilbiQJwoctZtt13+xMw91MCk/REan6MWHqDjyA==} + engines: {node: '>=12.0.0'} + + fdir@6.4.6: + resolution: {integrity: sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-tsconfig@4.10.1: + resolution: {integrity: sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + goober@2.1.16: + resolution: {integrity: sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==} + peerDependencies: + csstype: ^3.0.10 + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + html-encoding-sniffer@4.0.0: + resolution: {integrity: sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==} + engines: {node: '>=18'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + isbot@5.1.28: + resolution: {integrity: sha512-qrOp4g3xj8YNse4biorv6O5ZShwsJM0trsoda4y7j/Su7ZtTTfVXFzbKkpgcSoDrHS8FcTuUwcU04YimZlZOxw==} + engines: {node: '>=18'} + + jiti@2.5.1: + resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-tokens@9.0.1: + resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + + jsdom@26.1.0: + resolution: {integrity: sha512-Cvc9WUhxSMEo4McES3P7oK3QaXldCfNWp7pl2NNeiIFlCoLr3kfq9kb1fxftiwk1FLV7CvpvDfonxtzUDeSOPg==} + engines: {node: '>=18'} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lightningcss-darwin-arm64@1.30.1: + resolution: {integrity: sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.1: + resolution: {integrity: sha512-k1EvjakfumAQoTfcXUcHQZhSpLlkAuEkdMBsI/ivWw9hL+7FtilQc0Cy3hrx0AAQrVtQAbMI7YjCgYgvn37PzA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.1: + resolution: {integrity: sha512-kmW6UGCGg2PcyUE59K5r0kWfKPAVy4SltVeut+umLCFoJ53RdCUWxcRDzO1eTaxf/7Q2H7LTquFHPL5R+Gjyig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.1: + resolution: {integrity: sha512-MjxUShl1v8pit+6D/zSPq9S9dQ2NPFSQwGvxBCYaBYLPlCWuPh9/t1MRS8iUaR8i+a6w7aps+B4N0S1TYP/R+Q==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.1: + resolution: {integrity: sha512-gB72maP8rmrKsnKYy8XUuXi/4OctJiuQjcuqWNlJQ6jZiWqtPvqFziskH3hnajfvKB27ynbVCucKSm2rkQp4Bw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.1: + resolution: {integrity: sha512-jmUQVx4331m6LIX+0wUhBbmMX7TCfjF5FoOH6SD1CttzuYlGNVpA7QnrmLxrsub43ClTINfGSYyHe2HWeLl5CQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.1: + resolution: {integrity: sha512-piWx3z4wN8J8z3+O5kO74+yr6ze/dKmPnI7vLqfSqI8bccaTGY5xiSGVIJBDd5K5BHlvVLpUB3S2YCfelyJ1bw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.1: + resolution: {integrity: sha512-rRomAK7eIkL+tHY0YPxbc5Dra2gXlI63HL+v1Pdi1a3sC+tJTcFrHX+E86sulgAXeI7rSzDYhPSeHHjqFhqfeQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.1: + resolution: {integrity: sha512-mSL4rqPi4iXq5YVqzSsJgMVFENoa4nGTT/GjO2c0Yl9OuQfPsIfncvLrEW6RbbB24WtZ3xP/2CCmI3tNkNV4oA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.1: + resolution: {integrity: sha512-PVqXh48wh4T53F/1CCu8PIPCxLzWyCnn/9T5W1Jpmdy5h9Cwd+0YQS6/LwhHXSafuc61/xg9Lv5OrCby6a++jg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.1: + resolution: {integrity: sha512-xi6IyHML+c9+Q3W0S4fCQJOym42pyurFiJUHEcEyHS0CeKzia4yZDEsLlqOFykxOdHpNy0NmvVO31vcSqAxJCg==} + engines: {node: '>= 12.0.0'} + + loupe@3.2.0: + resolution: {integrity: sha512-2NCfZcT5VGVNX9mSZIxLRkEAegDGBpuQZBy13desuHeVORmBDyAET4TkJr4SjqQy3A8JDofMN6LpkK8Xcm/dlw==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.17: + resolution: {integrity: sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==} + + minipass@7.1.2: + resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@3.0.2: + resolution: {integrity: sha512-oG62iEk+CYt5Xj2YqI5Xi9xWUeZhDI8jjQmC5oThVH5JGCTgIjr7ciJDzC7MBzYd//WvR1OTmP5Q38Q8ShQtVA==} + engines: {node: '>= 18'} + + mkdirp@3.0.1: + resolution: {integrity: sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==} + engines: {node: '>=10'} + hasBin: true + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + nwsapi@2.2.21: + resolution: {integrity: sha512-o6nIY3qwiSXl7/LuOU0Dmuctd34Yay0yeuZRLFmDPrrdHpXKFndPj3hM+YEPVHYC5fx2otBx4Ilc/gyYSAUaIA==} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + pathval@2.0.1: + resolution: {integrity: sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==} + engines: {node: '>= 14.16'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.1.0: + resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==} + peerDependencies: + react: ^19.1.0 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + recast@0.23.11: + resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==} + engines: {node: '>= 4'} + + remove-accents@0.5.0: + resolution: {integrity: sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + rollup@4.45.3: + resolution: {integrity: sha512-STwyHZF3G+CrmZhB+qDiROq9s8B5PrOCYN6dtmOvwz585XBnyeHk1GTEhHJtUVb355/9uZhOazyVclTt5uahzA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + rrweb-cssom@0.8.0: + resolution: {integrity: sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + seroval-plugins@1.3.2: + resolution: {integrity: sha512-0QvCV2lM3aj/U3YozDiVwx9zpH0q8A60CTWIv4Jszj/givcudPb48B+rkU5D51NJ0pTpweGMttHjboPa9/zoIQ==} + engines: {node: '>=10'} + peerDependencies: + seroval: ^1.0 + + seroval@1.3.2: + resolution: {integrity: sha512-RbcPH1n5cfwKrru7v7+zrZvjLurgHhGyso3HTyGtRivGWgYjbOmGuivCQaORNELjNONoK35nj28EoWul9sb1zQ==} + engines: {node: '>=10'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + solid-js@1.9.7: + resolution: {integrity: sha512-/saTKi8iWEM233n5OSi1YHCCuh66ZIQ7aK2hsToPe4tqGm7qAejU1SwNuTPivbWAYq7SjuHVVYxxuZQNRbICiw==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.9.0: + resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} + + strip-literal@3.0.0: + resolution: {integrity: sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tailwindcss@4.1.11: + resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==} + + tapable@2.2.2: + resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==} + engines: {node: '>=6'} + + tar@7.4.3: + resolution: {integrity: sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw==} + engines: {node: '>=18'} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tiny-warning@1.0.3: + resolution: {integrity: sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@0.3.2: + resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==} + + tinyglobby@0.2.14: + resolution: {integrity: sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==} + engines: {node: '>=12.0.0'} + + tinypool@1.1.1: + resolution: {integrity: sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==} + engines: {node: ^18.0.0 || >=20.0.0} + + tinyrainbow@2.0.0: + resolution: {integrity: sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==} + engines: {node: '>=14.0.0'} + + tinyspy@4.0.3: + resolution: {integrity: sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==} + engines: {node: '>=14.0.0'} + + tldts-core@6.1.86: + resolution: {integrity: sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA==} + + tldts@6.1.86: + resolution: {integrity: sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ==} + hasBin: true + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tough-cookie@5.1.2: + resolution: {integrity: sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A==} + engines: {node: '>=16'} + + tr46@5.1.1: + resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==} + engines: {node: '>=18'} + + tslib@2.3.0: + resolution: {integrity: sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.20.3: + resolution: {integrity: sha512-qjbnuR9Tr+FJOMBqJCW5ehvIo/buZq7vH7qD7JziU98h6l3qGy0a/yPFjwO+y0/T7GFpNgNAvEcPPVfyT8rrPQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.8.0: + resolution: {integrity: sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==} + + unplugin@2.3.5: + resolution: {integrity: sha512-RyWSb5AHmGtjjNQ6gIlA67sHOsWpsbWpwDokLwTcejVdOjEkJZh7QKu14J00gDDVSh8kGH4KYC/TNBceXFZhtw==} + engines: {node: '>=18.12.0'} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + use-sync-external-store@1.5.0: + resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@7.0.6: + resolution: {integrity: sha512-MHFiOENNBd+Bd9uvc8GEsIzdkn1JxMmEeYX35tI3fv0sJBUTfW5tQsoaOwuY4KhBI09A3dUJ/DXf2yxPVPUceg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@3.2.4: + resolution: {integrity: sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@types/debug': ^4.1.12 + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + '@vitest/browser': 3.2.4 + '@vitest/ui': 3.2.4 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@types/debug': + optional: true + '@types/node': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + web-vitals@5.0.3: + resolution: {integrity: sha512-4KmOFYxj7qT6RAdCH0SWwq8eKeXNhAFXR4PmgF6nrWFmrJ41n7lq3UCA6UK0GebQ4uu+XP8e8zGjaDO3wZlqTg==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + whatwg-encoding@3.1.1: + resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==} + engines: {node: '>=18'} + + whatwg-mimetype@4.0.0: + resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} + engines: {node: '>=18'} + + whatwg-url@14.2.0: + resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==} + engines: {node: '>=18'} + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + ws@8.18.3: + resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@5.0.0: + resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==} + engines: {node: '>=18'} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zod@4.0.10: + resolution: {integrity: sha512-3vB+UU3/VmLL2lvwcY/4RV2i9z/YU0DTV/tDuYjrwmx5WeJ7hwy+rGEEx8glHp6Yxw7ibRbKSaIFBgReRPe5KA==} + + zrender@5.6.1: + resolution: {integrity: sha512-OFXkDJKcrlx5su2XbzJvj/34Q3m6PvyCZkVPHGYpcCJ52ek4U/ymZyfuV1nKE23AyBJ51E/6Yr0mhZ7xGTO4ag==} + +snapshots: + + '@ampproject/remapping@2.3.0': + dependencies: + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + + '@asamuzakjp/css-color@3.2.0': + dependencies: + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-color-parser': 3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + lru-cache: 10.4.3 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.0': {} + + '@babel/core@7.28.0': + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) + '@babel/helpers': 7.28.2 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.0': + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.12 + '@jridgewell/trace-mapping': 0.3.29 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.2 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.27.1': + dependencies: + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.27.3(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.2 + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.2': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + + '@babel/parser@7.28.0': + dependencies: + '@babel/types': 7.28.2 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-module-transforms': 7.27.3(@babel/core@7.28.0) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.27.1(@babel/core@7.28.0) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.27.1(@babel/core@7.28.0)': + dependencies: + '@babel/core': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.0) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.0) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.28.2': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 + + '@babel/traverse@7.28.0': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.0 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@biomejs/biome@2.1.2': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.1.2 + '@biomejs/cli-darwin-x64': 2.1.2 + '@biomejs/cli-linux-arm64': 2.1.2 + '@biomejs/cli-linux-arm64-musl': 2.1.2 + '@biomejs/cli-linux-x64': 2.1.2 + '@biomejs/cli-linux-x64-musl': 2.1.2 + '@biomejs/cli-win32-arm64': 2.1.2 + '@biomejs/cli-win32-x64': 2.1.2 + + '@biomejs/cli-darwin-arm64@2.1.2': + optional: true + + '@biomejs/cli-darwin-x64@2.1.2': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.1.2': + optional: true + + '@biomejs/cli-linux-arm64@2.1.2': + optional: true + + '@biomejs/cli-linux-x64-musl@2.1.2': + optional: true + + '@biomejs/cli-linux-x64@2.1.2': + optional: true + + '@biomejs/cli-win32-arm64@2.1.2': + optional: true + + '@biomejs/cli-win32-x64@2.1.2': + optional: true + + '@csstools/color-helpers@5.0.2': {} + + '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-color-parser@3.0.10(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/color-helpers': 5.0.2 + '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) + '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + dependencies: + '@csstools/css-tokenizer': 3.0.4 + + '@csstools/css-tokenizer@3.0.4': {} + + '@esbuild/aix-ppc64@0.25.8': + optional: true + + '@esbuild/android-arm64@0.25.8': + optional: true + + '@esbuild/android-arm@0.25.8': + optional: true + + '@esbuild/android-x64@0.25.8': + optional: true + + '@esbuild/darwin-arm64@0.25.8': + optional: true + + '@esbuild/darwin-x64@0.25.8': + optional: true + + '@esbuild/freebsd-arm64@0.25.8': + optional: true + + '@esbuild/freebsd-x64@0.25.8': + optional: true + + '@esbuild/linux-arm64@0.25.8': + optional: true + + '@esbuild/linux-arm@0.25.8': + optional: true + + '@esbuild/linux-ia32@0.25.8': + optional: true + + '@esbuild/linux-loong64@0.25.8': + optional: true + + '@esbuild/linux-mips64el@0.25.8': + optional: true + + '@esbuild/linux-ppc64@0.25.8': + optional: true + + '@esbuild/linux-riscv64@0.25.8': + optional: true + + '@esbuild/linux-s390x@0.25.8': + optional: true + + '@esbuild/linux-x64@0.25.8': + optional: true + + '@esbuild/netbsd-arm64@0.25.8': + optional: true + + '@esbuild/netbsd-x64@0.25.8': + optional: true + + '@esbuild/openbsd-arm64@0.25.8': + optional: true + + '@esbuild/openbsd-x64@0.25.8': + optional: true + + '@esbuild/openharmony-arm64@0.25.8': + optional: true + + '@esbuild/sunos-x64@0.25.8': + optional: true + + '@esbuild/win32-arm64@0.25.8': + optional: true + + '@esbuild/win32-ia32@0.25.8': + optional: true + + '@esbuild/win32-x64@0.25.8': + optional: true + + '@faker-js/faker@9.9.0': {} + + '@isaacs/fs-minipass@4.0.1': + dependencies: + minipass: 7.1.2 + + '@jridgewell/gen-mapping@0.3.12': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + '@jridgewell/trace-mapping': 0.3.29 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.4': {} + + '@jridgewell/trace-mapping@0.3.29': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.4 + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.45.3': + optional: true + + '@rollup/rollup-android-arm64@4.45.3': + optional: true + + '@rollup/rollup-darwin-arm64@4.45.3': + optional: true + + '@rollup/rollup-darwin-x64@4.45.3': + optional: true + + '@rollup/rollup-freebsd-arm64@4.45.3': + optional: true + + '@rollup/rollup-freebsd-x64@4.45.3': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.45.3': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.45.3': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.45.3': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.45.3': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.45.3': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.45.3': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.45.3': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.45.3': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.45.3': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.45.3': + optional: true + + '@rollup/rollup-linux-x64-musl@4.45.3': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.45.3': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.45.3': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.45.3': + optional: true + + '@tailwindcss/node@4.1.11': + dependencies: + '@ampproject/remapping': 2.3.0 + enhanced-resolve: 5.18.2 + jiti: 2.5.1 + lightningcss: 1.30.1 + magic-string: 0.30.17 + source-map-js: 1.2.1 + tailwindcss: 4.1.11 + + '@tailwindcss/oxide-android-arm64@4.1.11': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.11': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.11': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.11': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.11': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.11': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.11': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.11': + optional: true + + '@tailwindcss/oxide@4.1.11': + dependencies: + detect-libc: 2.0.4 + tar: 7.4.3 + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-arm64': 4.1.11 + '@tailwindcss/oxide-darwin-x64': 4.1.11 + '@tailwindcss/oxide-freebsd-x64': 4.1.11 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.11 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.11 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.11 + '@tailwindcss/oxide-linux-x64-musl': 4.1.11 + '@tailwindcss/oxide-wasm32-wasi': 4.1.11 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.11 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.11 + + '@tailwindcss/vite@4.1.11(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3))': + dependencies: + '@tailwindcss/node': 4.1.11 + '@tailwindcss/oxide': 4.1.11 + tailwindcss: 4.1.11 + vite: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3) + + '@tanstack/form-core@1.15.0': + dependencies: + '@tanstack/store': 0.7.2 + + '@tanstack/history@1.129.7': {} + + '@tanstack/match-sorter-utils@8.19.4': + dependencies: + remove-accents: 0.5.0 + + '@tanstack/query-core@5.83.0': {} + + '@tanstack/query-devtools@5.81.2': {} + + '@tanstack/react-form@1.15.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/form-core': 1.15.0 + '@tanstack/react-store': 0.7.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + decode-formdata: 0.9.0 + devalue: 5.1.1 + react: 19.1.0 + transitivePeerDependencies: + - react-dom + + '@tanstack/react-query-devtools@5.83.0(@tanstack/react-query@5.83.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/query-devtools': 5.81.2 + '@tanstack/react-query': 5.83.0(react@19.1.0) + react: 19.1.0 + + '@tanstack/react-query@5.83.0(react@19.1.0)': + dependencies: + '@tanstack/query-core': 5.83.0 + react: 19.1.0 + + '@tanstack/react-router-devtools@1.130.1(@tanstack/react-router@1.130.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(@tanstack/router-core@1.130.1)(csstype@3.1.3)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(solid-js@1.9.7)(tiny-invariant@1.3.3)': + dependencies: + '@tanstack/react-router': 1.130.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/router-devtools-core': 1.130.1(@tanstack/router-core@1.130.1)(csstype@3.1.3)(solid-js@1.9.7)(tiny-invariant@1.3.3) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + transitivePeerDependencies: + - '@tanstack/router-core' + - csstype + - solid-js + - tiny-invariant + + '@tanstack/react-router@1.130.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/history': 1.129.7 + '@tanstack/react-store': 0.7.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@tanstack/router-core': 1.130.1 + isbot: 5.1.28 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + '@tanstack/react-store@0.7.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/store': 0.7.2 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + use-sync-external-store: 1.5.0(react@19.1.0) + + '@tanstack/react-table@8.21.3(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@tanstack/table-core': 8.21.3 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + + '@tanstack/router-core@1.130.1': + dependencies: + '@tanstack/history': 1.129.7 + '@tanstack/store': 0.7.2 + cookie-es: 1.2.2 + seroval: 1.3.2 + seroval-plugins: 1.3.2(seroval@1.3.2) + tiny-invariant: 1.3.3 + tiny-warning: 1.0.3 + + '@tanstack/router-devtools-core@1.130.1(@tanstack/router-core@1.130.1)(csstype@3.1.3)(solid-js@1.9.7)(tiny-invariant@1.3.3)': + dependencies: + '@tanstack/router-core': 1.130.1 + clsx: 2.1.1 + goober: 2.1.16(csstype@3.1.3) + solid-js: 1.9.7 + tiny-invariant: 1.3.3 + optionalDependencies: + csstype: 3.1.3 + + '@tanstack/router-generator@1.130.1': + dependencies: + '@tanstack/router-core': 1.130.1 + '@tanstack/router-utils': 1.129.7 + '@tanstack/virtual-file-routes': 1.129.7 + prettier: 3.6.2 + recast: 0.23.11 + source-map: 0.7.6 + tsx: 4.20.3 + zod: 3.25.76 + transitivePeerDependencies: + - supports-color + + '@tanstack/router-plugin@1.130.1(@tanstack/react-router@1.130.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3))': + dependencies: + '@babel/core': 7.28.0 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.0) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.0) + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + '@tanstack/router-core': 1.130.1 + '@tanstack/router-generator': 1.130.1 + '@tanstack/router-utils': 1.129.7 + '@tanstack/virtual-file-routes': 1.129.7 + babel-dead-code-elimination: 1.0.10 + chokidar: 3.6.0 + unplugin: 2.3.5 + zod: 3.25.76 + optionalDependencies: + '@tanstack/react-router': 1.130.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + vite: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3) + transitivePeerDependencies: + - supports-color + + '@tanstack/router-utils@1.129.7': + dependencies: + '@babel/core': 7.28.0 + '@babel/generator': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/preset-typescript': 7.27.1(@babel/core@7.28.0) + ansis: 4.1.0 + diff: 8.0.2 + transitivePeerDependencies: + - supports-color + + '@tanstack/store@0.7.2': {} + + '@tanstack/table-core@8.21.3': {} + + '@tanstack/virtual-file-routes@1.129.7': {} + + '@testing-library/dom@10.4.0': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/runtime': 7.28.2 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + '@testing-library/react@16.3.0(@testing-library/dom@10.4.0)(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@babel/runtime': 7.28.2 + '@testing-library/dom': 10.4.0 + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.7 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.2 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.0 + '@babel/types': 7.28.2 + + '@types/babel__traverse@7.20.7': + dependencies: + '@babel/types': 7.28.2 + + '@types/chai@5.2.2': + dependencies: + '@types/deep-eql': 4.0.2 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.8': {} + + '@types/node@24.1.0': + dependencies: + undici-types: 7.8.0 + + '@types/react-dom@19.1.6(@types/react@19.1.8)': + dependencies: + '@types/react': 19.1.8 + + '@types/react@19.1.8': + dependencies: + csstype: 3.1.3 + + '@vitejs/plugin-react@4.7.0(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3))': + dependencies: + '@babel/core': 7.28.0 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.0) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.0) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@3.2.4': + dependencies: + '@types/chai': 5.2.2 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.1 + tinyrainbow: 2.0.0 + + '@vitest/mocker@3.2.4(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3))': + dependencies: + '@vitest/spy': 3.2.4 + estree-walker: 3.0.3 + magic-string: 0.30.17 + optionalDependencies: + vite: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3) + + '@vitest/pretty-format@3.2.4': + dependencies: + tinyrainbow: 2.0.0 + + '@vitest/runner@3.2.4': + dependencies: + '@vitest/utils': 3.2.4 + pathe: 2.0.3 + strip-literal: 3.0.0 + + '@vitest/snapshot@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + magic-string: 0.30.17 + pathe: 2.0.3 + + '@vitest/spy@3.2.4': + dependencies: + tinyspy: 4.0.3 + + '@vitest/utils@3.2.4': + dependencies: + '@vitest/pretty-format': 3.2.4 + loupe: 3.2.0 + tinyrainbow: 2.0.0 + + acorn@8.15.0: {} + + agent-base@7.1.4: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansis@4.1.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + assertion-error@2.0.1: {} + + ast-types@0.16.1: + dependencies: + tslib: 2.8.1 + + babel-dead-code-elimination@1.0.10: + dependencies: + '@babel/core': 7.28.0 + '@babel/parser': 7.28.0 + '@babel/traverse': 7.28.0 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + binary-extensions@2.3.0: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.25.1: + dependencies: + caniuse-lite: 1.0.30001727 + electron-to-chromium: 1.5.191 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.1) + + cac@6.7.14: {} + + caniuse-lite@1.0.30001727: {} + + chai@5.2.1: + dependencies: + assertion-error: 2.0.1 + check-error: 2.1.1 + deep-eql: 5.0.2 + loupe: 3.2.0 + pathval: 2.0.1 + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + check-error@2.1.1: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chownr@3.0.0: {} + + clsx@2.1.1: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + convert-source-map@2.0.0: {} + + cookie-es@1.2.2: {} + + cssstyle@4.6.0: + dependencies: + '@asamuzakjp/css-color': 3.2.0 + rrweb-cssom: 0.8.0 + + csstype@3.1.3: {} + + data-urls@5.0.0: + dependencies: + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + decode-formdata@0.9.0: {} + + deep-eql@5.0.2: {} + + dequal@2.0.3: {} + + detect-libc@2.0.4: {} + + devalue@5.1.1: {} + + diff@8.0.2: {} + + dom-accessibility-api@0.5.16: {} + + echarts@5.6.0: + dependencies: + tslib: 2.3.0 + zrender: 5.6.1 + + electron-to-chromium@1.5.191: {} + + enhanced-resolve@5.18.2: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.2 + + entities@6.0.1: {} + + es-module-lexer@1.7.0: {} + + esbuild@0.25.8: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.8 + '@esbuild/android-arm': 0.25.8 + '@esbuild/android-arm64': 0.25.8 + '@esbuild/android-x64': 0.25.8 + '@esbuild/darwin-arm64': 0.25.8 + '@esbuild/darwin-x64': 0.25.8 + '@esbuild/freebsd-arm64': 0.25.8 + '@esbuild/freebsd-x64': 0.25.8 + '@esbuild/linux-arm': 0.25.8 + '@esbuild/linux-arm64': 0.25.8 + '@esbuild/linux-ia32': 0.25.8 + '@esbuild/linux-loong64': 0.25.8 + '@esbuild/linux-mips64el': 0.25.8 + '@esbuild/linux-ppc64': 0.25.8 + '@esbuild/linux-riscv64': 0.25.8 + '@esbuild/linux-s390x': 0.25.8 + '@esbuild/linux-x64': 0.25.8 + '@esbuild/netbsd-arm64': 0.25.8 + '@esbuild/netbsd-x64': 0.25.8 + '@esbuild/openbsd-arm64': 0.25.8 + '@esbuild/openbsd-x64': 0.25.8 + '@esbuild/openharmony-arm64': 0.25.8 + '@esbuild/sunos-x64': 0.25.8 + '@esbuild/win32-arm64': 0.25.8 + '@esbuild/win32-ia32': 0.25.8 + '@esbuild/win32-x64': 0.25.8 + + escalade@3.2.0: {} + + esprima@4.0.1: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + expect-type@1.2.2: {} + + fdir@6.4.6(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-tsconfig@4.10.1: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + goober@2.1.16(csstype@3.1.3): + dependencies: + csstype: 3.1.3 + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + html-encoding-sniffer@4.0.0: + dependencies: + whatwg-encoding: 3.1.1 + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + is-potential-custom-element-name@1.0.1: {} + + isbot@5.1.28: {} + + jiti@2.5.1: {} + + js-tokens@4.0.0: {} + + js-tokens@9.0.1: {} + + jsdom@26.1.0: + dependencies: + cssstyle: 4.6.0 + data-urls: 5.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.21 + parse5: 7.3.0 + rrweb-cssom: 0.8.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 5.1.2 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.2.0 + ws: 8.18.3 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lightningcss-darwin-arm64@1.30.1: + optional: true + + lightningcss-darwin-x64@1.30.1: + optional: true + + lightningcss-freebsd-x64@1.30.1: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.1: + optional: true + + lightningcss-linux-arm64-gnu@1.30.1: + optional: true + + lightningcss-linux-arm64-musl@1.30.1: + optional: true + + lightningcss-linux-x64-gnu@1.30.1: + optional: true + + lightningcss-linux-x64-musl@1.30.1: + optional: true + + lightningcss-win32-arm64-msvc@1.30.1: + optional: true + + lightningcss-win32-x64-msvc@1.30.1: + optional: true + + lightningcss@1.30.1: + dependencies: + detect-libc: 2.0.4 + optionalDependencies: + lightningcss-darwin-arm64: 1.30.1 + lightningcss-darwin-x64: 1.30.1 + lightningcss-freebsd-x64: 1.30.1 + lightningcss-linux-arm-gnueabihf: 1.30.1 + lightningcss-linux-arm64-gnu: 1.30.1 + lightningcss-linux-arm64-musl: 1.30.1 + lightningcss-linux-x64-gnu: 1.30.1 + lightningcss-linux-x64-musl: 1.30.1 + lightningcss-win32-arm64-msvc: 1.30.1 + lightningcss-win32-x64-msvc: 1.30.1 + + loupe@3.2.0: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lz-string@1.5.0: {} + + magic-string@0.30.17: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.4 + + minipass@7.1.2: {} + + minizlib@3.0.2: + dependencies: + minipass: 7.1.2 + + mkdirp@3.0.1: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + node-releases@2.0.19: {} + + normalize-path@3.0.0: {} + + nwsapi@2.2.21: {} + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + pathe@2.0.3: {} + + pathval@2.0.1: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.6.2: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + punycode@2.3.1: {} + + react-dom@19.1.0(react@19.1.0): + dependencies: + react: 19.1.0 + scheduler: 0.26.0 + + react-is@17.0.2: {} + + react-refresh@0.17.0: {} + + react@19.1.0: {} + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + recast@0.23.11: + dependencies: + ast-types: 0.16.1 + esprima: 4.0.1 + source-map: 0.6.1 + tiny-invariant: 1.3.3 + tslib: 2.8.1 + + remove-accents@0.5.0: {} + + resolve-pkg-maps@1.0.0: {} + + rollup@4.45.3: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.45.3 + '@rollup/rollup-android-arm64': 4.45.3 + '@rollup/rollup-darwin-arm64': 4.45.3 + '@rollup/rollup-darwin-x64': 4.45.3 + '@rollup/rollup-freebsd-arm64': 4.45.3 + '@rollup/rollup-freebsd-x64': 4.45.3 + '@rollup/rollup-linux-arm-gnueabihf': 4.45.3 + '@rollup/rollup-linux-arm-musleabihf': 4.45.3 + '@rollup/rollup-linux-arm64-gnu': 4.45.3 + '@rollup/rollup-linux-arm64-musl': 4.45.3 + '@rollup/rollup-linux-loongarch64-gnu': 4.45.3 + '@rollup/rollup-linux-ppc64-gnu': 4.45.3 + '@rollup/rollup-linux-riscv64-gnu': 4.45.3 + '@rollup/rollup-linux-riscv64-musl': 4.45.3 + '@rollup/rollup-linux-s390x-gnu': 4.45.3 + '@rollup/rollup-linux-x64-gnu': 4.45.3 + '@rollup/rollup-linux-x64-musl': 4.45.3 + '@rollup/rollup-win32-arm64-msvc': 4.45.3 + '@rollup/rollup-win32-ia32-msvc': 4.45.3 + '@rollup/rollup-win32-x64-msvc': 4.45.3 + fsevents: 2.3.3 + + rrweb-cssom@0.8.0: {} + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.26.0: {} + + semver@6.3.1: {} + + seroval-plugins@1.3.2(seroval@1.3.2): + dependencies: + seroval: 1.3.2 + + seroval@1.3.2: {} + + siginfo@2.0.0: {} + + solid-js@1.9.7: + dependencies: + csstype: 3.1.3 + seroval: 1.3.2 + seroval-plugins: 1.3.2(seroval@1.3.2) + + source-map-js@1.2.1: {} + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + stackback@0.0.2: {} + + std-env@3.9.0: {} + + strip-literal@3.0.0: + dependencies: + js-tokens: 9.0.1 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + symbol-tree@3.2.4: {} + + tailwindcss@4.1.11: {} + + tapable@2.2.2: {} + + tar@7.4.3: + dependencies: + '@isaacs/fs-minipass': 4.0.1 + chownr: 3.0.0 + minipass: 7.1.2 + minizlib: 3.0.2 + mkdirp: 3.0.1 + yallist: 5.0.0 + + tiny-invariant@1.3.3: {} + + tiny-warning@1.0.3: {} + + tinybench@2.9.0: {} + + tinyexec@0.3.2: {} + + tinyglobby@0.2.14: + dependencies: + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + + tinypool@1.1.1: {} + + tinyrainbow@2.0.0: {} + + tinyspy@4.0.3: {} + + tldts-core@6.1.86: {} + + tldts@6.1.86: + dependencies: + tldts-core: 6.1.86 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tough-cookie@5.1.2: + dependencies: + tldts: 6.1.86 + + tr46@5.1.1: + dependencies: + punycode: 2.3.1 + + tslib@2.3.0: {} + + tslib@2.8.1: {} + + tsx@4.20.3: + dependencies: + esbuild: 0.25.8 + get-tsconfig: 4.10.1 + optionalDependencies: + fsevents: 2.3.3 + + typescript@5.8.3: {} + + undici-types@7.8.0: {} + + unplugin@2.3.5: + dependencies: + acorn: 8.15.0 + picomatch: 4.0.3 + webpack-virtual-modules: 0.6.2 + + update-browserslist-db@1.1.3(browserslist@4.25.1): + dependencies: + browserslist: 4.25.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + use-sync-external-store@1.5.0(react@19.1.0): + dependencies: + react: 19.1.0 + + vite-node@3.2.4(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3): + dependencies: + cac: 6.7.14 + debug: 4.4.1 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3): + dependencies: + esbuild: 0.25.8 + fdir: 6.4.6(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.45.3 + tinyglobby: 0.2.14 + optionalDependencies: + '@types/node': 24.1.0 + fsevents: 2.3.3 + jiti: 2.5.1 + lightningcss: 1.30.1 + tsx: 4.20.3 + + vitest@3.2.4(@types/node@24.1.0)(jiti@2.5.1)(jsdom@26.1.0)(lightningcss@1.30.1)(tsx@4.20.3): + dependencies: + '@types/chai': 5.2.2 + '@vitest/expect': 3.2.4 + '@vitest/mocker': 3.2.4(vite@7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3)) + '@vitest/pretty-format': 3.2.4 + '@vitest/runner': 3.2.4 + '@vitest/snapshot': 3.2.4 + '@vitest/spy': 3.2.4 + '@vitest/utils': 3.2.4 + chai: 5.2.1 + debug: 4.4.1 + expect-type: 1.2.2 + magic-string: 0.30.17 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.9.0 + tinybench: 2.9.0 + tinyexec: 0.3.2 + tinyglobby: 0.2.14 + tinypool: 1.1.1 + tinyrainbow: 2.0.0 + vite: 7.0.6(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3) + vite-node: 3.2.4(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.20.3) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.1.0 + jsdom: 26.1.0 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + web-vitals@5.0.3: {} + + webidl-conversions@7.0.0: {} + + webpack-virtual-modules@0.6.2: {} + + whatwg-encoding@3.1.1: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@4.0.0: {} + + whatwg-url@14.2.0: + dependencies: + tr46: 5.1.1 + webidl-conversions: 7.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + ws@8.18.3: {} + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@3.1.1: {} + + yallist@5.0.0: {} + + zod@3.25.76: {} + + zod@4.0.10: {} + + zrender@5.6.1: + dependencies: + tslib: 2.3.0 diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/favicon.ico b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3491717f6f86af7d7728cd81fcff9f05d9063251 GIT binary patch literal 4286 zcmeHLTS!z<6g_4dGQCL1zZm+6D6%ZAV4#Ol&iKHXK|!G|LS=mv1qE3_eN;b2LD5Gz zP#-~41XB4C88JOR!Xzpp$e;|Q2MC!tfW-W(yhwj|D#kuo?Xc+lJKS ziWSy^0)bx;7TC=@)?O&VMNV=%1TBKGdYOLb_mbNX4CwNg6)J37ks55%wUgj9{jJrn zyl4jY`j(?-kc1PHF}qUI%$j{D7sp)=3c2#zoh^l-gRR5cLYO`a1tnETb<}o zS91(n+_dGExivhC@cqkYSQWNWhq{{l+IrLc)AJvXzizAWli2Kr;m%O}$(r)H=HlMX z%Xr-x#H)@+Nuca*;1ay9Iq9rF?f5s>Q{|c*RIkfR3AUMf|8=duzu7(?+*_;H=AnG` z%#8XumH&9Y86GE|J-m&8{|st3<|EBBZ^HAz@7})Nf%ZFBW9x5ZB#MqFZP-^+I7MT~ z!jRe^~Z| zgW~g9+I>^-FxO)apq+ov2K`|og-zY@kkb$_2VyRy+s`gN=6vM-H~(aAXqSB}JIrzU sy|Mh0NpNBe%!v&FeZpMXs{cggjC0f9fAjC4QrV~Y7r_)4at4L_4={HB-~a#s literal 0 HcmV?d00001 diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/icon/search.svg b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/icon/search.svg new file mode 100644 index 0000000..a6e7d16 --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/icon/search.svg @@ -0,0 +1,6 @@ + + + diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/logo.svg b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/logo.svg new file mode 100644 index 0000000..f36e501 --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/logo.svg @@ -0,0 +1,8 @@ + + + + + + diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/logo192.png b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/logo192.png new file mode 100644 index 0000000000000000000000000000000000000000..f47f0d33feecb58fecbb1003110979c672982a71 GIT binary patch literal 2860 zcmb_e`8(7L7yphKGjWvTw=FauY6!k(n{5kP_L$L?LRF-yhC7&-t7m&gY!-ob#MCM+X~Gp+iCd0EpV6t)2I5 z*KZMk?rG<#E3iEy7>V|b1_0sxzXb#oJ(S%8LD9}OmH=r;Zh6mv1Y4Z50DzbGggF=a z0YIqR*4n}~4)lZOf5kQQVDEacs%JV{qX+#?80t=t(Gv62qSPM0m}6P*sC8CC3)m0Y zFG9NjPOA?$)Xln}>U8wBpuzoI>;3PG1fba;;rYU%6;m2^0WFHOb9a>6;X?I8ugNF% zR~ut07e74I*IzW5IZxdM52(=T9psJIiyedL+IheCX)!I$AKa(hKT*Bi-@&O*9Wlk~ ziJXkS9Kw9v!V`Wysz$54QMC;HT3Ps5U5drsLIVjc$U7%ilqJJmhzePU1}qTi`xIIP z%|%8oWtSCX7rfmMM9kvmh-`$c1c@1P2hJ(fy}v8otBbUT15mTT+>c>LQsc zj5q()G~X{o!3tfpk}v^8uu4{nX9WoS`0jf_Y7o(G4rnbRxJ7Xfx1Tobd9`8K1In|I zSsGT z+eOSoYH%$#@~329`#X!jg|ozHV|I^%?G^G&?mLjAFfCzQH555z3ZGkI))|u2cew)Y zV4*&csZIS+=o#=)*hCN+~XeXTRwj2Ky?AAlD$_y6Itdu z_f!CNIbY#`uYG=8L>9v+1Dj{yyA-Uu+H}+4=pP;jYXD|ZY=VLZlCI=)ozknpAjz__i4+UA8Bvy zaNRKUlH_jJ;ba=mR!tt1*CYcbGjNb*d=FW!IBKl!azJor0!^5Zx88H3GKNGJwXkZO zA7-fBdCD@&ik%dXdj<0P(r;`8mTDlhg`TW1Oq}ShYZNg)#Yrb#F#hW>i8n(xn^X`u zXJ49e%r9rENE`iX3Ff61eLex7OQ4ATTw=M!*CWl4ndOL(+(0*Q*Utz6Ow-5g;Cc2; zwyLiQugC~aEYw390-hEMUW}=hwE%ZVY2%^*xQ#1_lmv4*jurJ z&CZ!qf-+0Q!cjiT=M6uLGg{EGrCvi~xO^B-Fl_XQ zTl^b!7TCn;fcS_hKFBqb{#= zr~CT+no*}#Nk(ao0j1J+fQaL)+$HKh{R!gqO5aoF)tJldB!xU~gHW9nGn4&?#jTGv zfhe^&=VBUh33e!d8n`jk^8?&wuJfXz$>^uDXBb##lgAExH_I!T(sr#Hg`$eB!lg7- z&701cOZbK5TT~%-&&!))H4^>3wGYH-a+O%ZV~S&JU_4@)F*NzoS$>Jv1vXmo?cDxb zpSyR>k* z<^D##j|5N6?M6BP3H*L71j)tcC9*^|O*3_*fy1(se%$+yX%oM^2>~BWB?~)akBxEh zR{@5%YO?j&hM9JjPBZ#ez}BN*rpdi7W8zB}I<3?pC{ncNAa6)Vx`$xG6L7Y#!*&3a+P~KjN@v)PXFB=8bafRKvA(1CY zN>#tZ@*vev7ItilIKW_&8#IMUjI`=wq1^8siO>A<#y zZ^0TzhZ}IJ@pD$dWi=M~IkvPQyXvHB(t^%*+3w$~@q3e92!vK!vS4R+en1A>x$tHG@J9xzI|gC%liWaGHwgoO%Nfz89c#+bCdd@@iMBFZO$7o+@PpKrZ0 zz}#C!8-wQ${l-ZxGOEPtpEBx*5xwJBUkZi^fR?+)VzcX$@Q_zSF2O1f?Dg>&P=j-%Tal)LqKSkQAug2NawE0uF)jp%CDI zbmZpJa_nB+NRVnpTo^JR_IKq`FCfC|) zu~cX$6&Fq3k%2Vr!^i(#JEhzqpXyBDLq#yx%eH2!Xpw&}A~kY!(h2_mVENyd846TF zbkc#YdOi^T4?jSNufX*N5!7<5x+Zeh*S3c9{1m}U&loJVL8v)1)1ht~8#PVPw=4 zs5~9zI+K4j)g#6M@K#}#An=?C*KIf+MB=nMQ_ibL+-z6E3u3n`)cIy}EuKLYe@w}i zVcX0&Q9y@Yb(}Na71@?ql2#?N<4Fd`+!bg5sNpS)j6zVr0aT4NIk$MG{$#1Zr2{B+ z>7b8e{;?(-_5ICNPZBndvOLRxW!+U0&;2q^-(~Y#>p3IlE1=&!i}US5G?&<8wO(jy zsg~lZy5+{UwHY(l-=^EzDJuCp*_YL@Qm>EhROrRC+k}%G4nMF&m*-qvLy$HJz#^aK zp-6gM2-Ma^BL0NIOvZvZzSHQ%xwts_ey6qXy!68}VS19TB<&SHlK4;%e~3RnZjC&( zR-+OJNGYNckSPZ_>QkSH)BdZPYSNGp?d=!I{;_C}oaU;>?173S2(4@W;S$7eOdfAJ z0?qsUI7NyCDbgG_n&+ikfV`}VeVT`G@z&o*Jpidhkk?Tkv)Ys$Yu?`_>jDLmVf;-vdRY|dF%M<(ON?aLhK62nrjcLG@5u94;EZb-ucp0U9 zc>Xh`$7~5{s0}oHJKioK)i6gELB12Wb_*#Vxl%LtTw$7CN%^`hGY|J2d^&F9jC_6( U+r08@@3{qRPdiwXECUGt0SGfe8UO$Q literal 0 HcmV?d00001 diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/logo512.png b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/logo512.png new file mode 100644 index 0000000000000000000000000000000000000000..41a9019a2a2fac0da03d7be112a40c12ab60ac35 GIT binary patch literal 7837 zcmeHM`9IX(_dlR%7ru|j_lJ4Ry{~)kx#!$_?pdDay4618MG^`U0I;QNg zwSaeA1VCxC$?hH20oX`~eZ;Z8gf)L2F1)RE{ejM-{Z98+WUM@1Yr(R5g_qsmpc?a0 z<8`_G^(wi$v9t1Lp2>SYkQ`Av8+LTV{>`nOt8Eom9Z#SBS1)nL@_+wrt~$2bL43E! z+3poPHJZe9Zyk2s-6|y9rY-ETUcgb2qb4S(t0WRxa8 z*HaOAsC9RDOI0~%&j!@g1dTbnjQNZuh~t5F$EMe6Z=u4Z)elg_+mXp`^}w~$<$ADSul z4!^@^&X0j~3}MH^0DU{_1-6YLnJYXJKr5?1C84{nATQv&^Z4CGdO`TJy)u^$KV25I zsyq25T}vKgsC+bdUKKyk-+k0})`fs;3@oiEv6C7zR2X?^_d1b;f9Z6bqFl2#q<`_d zdZ|DhOeQm%^N+0On1xk7pjaJA?$h#mAx2EDJywpf+6sifo>x5&df=Z)p&Qy(WojN0 z*|#TiVgYo73U|%4ju0iw9*Osd>-^k(_Ryt^1bSXLZR9WIS|}A?puMlpSKmqq4empF zOWyNf;p!-5t1f8h7#~|JE6BKlf!*OTWvdD_Vbn}dunU7-k6a|PETN&D%%1fv-ofIh zrR|rV7K1V$fpO&vL#Tdq&_g@w>(7bTZe-lSg^A=Nf?6i0h9I}+kdCKm{FK3AeM1@0 zI{I;RQTKWKZ~yvere2rGaS&nWQgLrrnHoajktYiIDmm#T!=9y*vP^jI{IX=L&WvJ& zM7EI#tK}#2gAO0=*!cw#-&3L3f5lnr=oYlM< zLxbribvsA;g+K`GxtKgdv@+gO7cWt7WOB!O31hOW<8W!Rrcyi!nh!p$)>&MgyUual zn<@4n8Sl3+zVWPf9B}>L*X@}y=SEXgZmK7qg@FwM9}F%eB%c?%>j3$)#OT88Yxp1u z!fCMj-~#M61(2{dt{N*D&oFj31>6$?#O@x0fsfNnWxxxTuKaO3`Jb7IPtF9jE#mpx zdRYAWI~`193@WDGn`Jr%=RB7;2s1Q=r#w6-op>dLoo_If4m1G5ox z4$x7;`AC9%nJpbUFLd=@E}EJp(0#_9dYL&wYp&BG{-nw#wVomgR@*ogF}$#cbFM@G z^ppi&<|A|z0j*Wc;;r95y>Py26h!(H@|g-+o8_WPsf@P?NBxdW z;$m-7z9!%j>RZQ>+Eo}m&`{Qsn#}~OO;z2(OW$rfcMo*mwXxe18) z4esXxWL?U>rg~UmUPv(Owmrjt$InPY+$kbtz!Sv0oa|o-!8e4N&b%1E%CwY6OY(8^4q(ID>RincXNNQ8-H7waN0oV1f_YmA~9RJ zi|0SKY@NyMuSfvXpFiXbm(R2pkCp9Vsw5v_N*hzRuXvckGlAWS3tK~mn!co#YJsnr z$jwm5+&D5t)eZFg;Nb7)aY%wtGWIHut5n+&S}5+2MrMS$3-6Qu%47)Sl(?NQWreFl z!$QNT@>y=sY~OSF5%yIKiiO9&M+VQ;2k>5dzO9eC_|LV*1z6V-0-kh)+09cyFYDtf zD3xSer;BjM$rXkZB6=%`$AaqQN5ASG0J?EL-=!c3!Xg?&#QTAH#WUQ zaj~+qnHl#;$SV!dKD(IFZFFd&tG2vj+uQqg9qrN+egY}skP`k4*{QB9{{i+1*?woo zqJuOz@#tL7`>0L|bW93cm$7%J-#2{YJLh?1Xp_842DbJ|awD%j$e54L7_olfz&qy8 zFpBxfm807f2)e}qFNGd`QtX>p#Fun4!LZ38G9RMTGTb?z4vXRmP<_f)RXCjq1zJd2FGAii zM@NbKbzH6U5&bKZ>mU*sdk?A4UviwEZdOnzkoyJ@$=hU=+gF0#TCqv-@ zDDX4+&Dpl*h6LMg3+7=KJ1-eijt^G;%&iAWct!%ed_1Hoyom%0(SfWqBMHa_{7Lbz zufefH6J0qkW@+A2oiH@?AR*kTWzEcE#YJL|HIv6_(|m5vXhpgD&^{V8e`^x%$8c== z)Eu)S4jt0~8UM}+kOBoNg|W*|bIx-&n$(n`N3c1fG52tzu*b1zi+2VUhO)->{&S30ngnSA<`0(yBTJJmRClo(Zy7SPxi*|P~tw55VuYS`qw+W!e0%S zf;kwgn*`4?dTEO2EY1+%=z$4pN(f}V)Fv}FDoeXIa#DqTX6L$?v%`#lY(jK-CQUFn ziCKqBz&E6*l#rj#Uk{)|3ogl{uxt^jhU7k>SITqVnxD7Qgh!DrF;__piYK^f#6FA) zp4(U=5I>#DKZ&$Io{%MK0L_P)aTRr6)SO!!dG3gXm64F8swy08pum@(Vz{k|UW1DN zih+&0&1<@cJ(n6|juG{bD#Tf(BydyFxt|eJVK1CQ-fO~#MBgwa%Dwoh+i^*@u#!(; zwUa>ONlPEo#XpW2xbFhG3vV>(r21TZY6z^=lmQKscNGeO{&u{)oGn&6`@u@xb>-Zc zaPaTl5-$DN#A^zvFTS66Y~m8-E(KIoz-k$~g65eqe{QElv0%v3FA570FdfBNii((( zcww;Mc}EO$GH2aox9x)1l6Fln-3_c}j=kVv`GrXRei1{3UNS@0aL_5 zdDBIFiZ7B`rwFE1>xKGgug`11N&)RPd&RME_|tA%-a)aw$KkHNG2Gdyux9;X7CM(4ms+S1qZAR5&F-!E^Tx9ZX?7j{#LZz^ zoDW*3G$7(wabtK4?TslP0*x3K7ZRM(o9ahGYs!~vNsP)tPa1(;JGF-UIt$&=xD?1I z1f4A_nhoVQ;`_JHhJLELZ>JMenI#SpyT4}-{&hKqm>fX8x4->T7t6;tTO~%uYsXa% zASQPY@xsoT9?CS1R9cLpUNBnaCw%iNgS!_|Zj=Oh@rPk;2wSqTNIie7QfyQ?~t7Ca<;Vd5itXTKKvWw9Xe&bu+1Wwyt-}CD^zceaNOV zgg-q+&97*{V+&xcYVzI62lKk}Z_5e)qyqcx0GVNXYGa!WdyWKZrZo8M_3=p%KVl9PnH8hnVb(-3|t43V9upNWiMk9NhMEe4P;is1(uDC~dIoz-O2Y?6SkjyO>q z<=4L({UqzP62qI&=-Q0bjJeP)7~-5|Jw?=#m7Uja z=l*9=q8@uZo2R;rxW6@tt#>6LPwn4zTscfO8)wnU8V%I;;3Y;e>FIowZdRY0t^F=s<2F`L^l zw1vjV7EBTIPmDwHg}09b{=af%bx#8fTiI2Ck(M=&A!`Z-vZ_(Rd$~smOQAhS&?V0s|rIj*oE6pM!0_Wy*IQ7w-9$ zN|ZN1_HE5HG2M)vmFKHMRb|R+&Jb8J?V7BVz{1cFt2C-PE&;LWR8~l!s*t;0ptTZF z|6<5BZRaN6-Z4FGCdej$`8A+t43e{6hQtfocReKDU60}K&0MPciPljuH1l|%_f2y# z--sh&sF$h>%{vn*e`*RlH&a=?2llpctMY2HnX@MFIfFnygWQC1>Wln4u}1|~8wpV0 zJ+2NJ+z2_@Do50Rqpa*U_QA}h zr>Xm&ADl-tnTW$Vxjb&3LBmK9Gj5@1DVPeZ zf+L~U$d=ck$@mBLsK`q;Qu2e*1)S|lD!)db)+H$Gaj=*53~Cnj3SQgv;UqO2g^LdU zHj=|;s=}ZOpQe$Z``4_Y_Q3adnV~nQr%QMn1Xh*=_-4AG4L$PLp?7%q1&Ua7fiEfl z&!55v7rw|`ilO!H7Qr7+kIH#f%)$L$N|6-QB8^dH*51+u$+8a5$Vu!#Uypm2fs79& zIdWT9pdNm40K-ofp?mKLQTXlLLb>2gf;#UZvQ`~c5*VSyGsLN49#b-`oKL(N6^(34 zzNtMIJMgOL=gT+aQ$Q2s`JByUvsJQ|EhpI{TmlUJ3iS=2GObTdRn}bsl1ZynHKM;^4CFDqzb_pYQC7a5YgC z9y@DGiilEz)@vwgNDiRUJ@Y@c6qhjZ>VXAM>6R6<@IuDBc)$z&nDnV=fSVVA%r&!K#GmW87lM7@TQ=SFFW}2`h~nRP1cdA0v7n6 zl+dt>ljC_$r^+la%77Fo2c8u6A38#sj@wBe8UYgg+wT}$nZPt!yrT68>5?V6cONJR z)s%Z1N5@-_UuqYJ6B}ar$=qLSf(C2n^6))x-e0; z#?w;{V;Q6d!W0&*8cr{t*LdZF;f-g@wu$FUN8fiz4cb(D5Wy^1g4@Gwbr@st7q)Es zbnliODJeFyN9mdWoy5N&ZoYH=QY1EU`@L{-1BQF}H6?|J&_UVEb)0rC(6F|yF>D{X z*WDE%3aZH6CF))He!7%O* z%~c|O!V@*yHAA!kS9q1rOMNU2ibSAWJTA)ie9ZN(0kHCOj}zWrF)Z9-wuXQJwt_n0 zJ5h`g<_svMPaEdHMhQPZO?y+84A5l?9`Jo#%4qE@LPH;_mT0)TzFEiFDh^bSJ;lMw zV5OSuG{baDlk-r7?~~z)7{>D6I?~hjTa4xPZrn-)p@H8nPL^MQfy0=&C<%A7*m_byP0EU~W!y_^UP~{-W{-7Aj zEq;^;Pb`qZbBh4ppEir-JV7}3Gc7p))06IK=#dOwN&Z!N3}9{eK96%VsU8M!cd;0# zKARdi>@PG?d6`z^>GGE9xHbjB#9AAYPt*J^c)Zyf2@f1aUKs$c{#~HTXyciJSjI*IE_a&=>$<{?53{u+kR5cXk*H{ zGZP7`Na^lUNf<=f6b>^_`1h}9l%jW$DSPs^pmaBR-@1cwED0F=xu4tm#RYea<@2O2x0>iIAc;os7TmyOl)VD>sc z&*kllfuZ-ks++mC`ou+y<^P7ozppL{FLR!kUW@!VkOW8EM-$WqTe8)?TmX_xmJhfa zcT5$2myd!tKOv#VViQ=$q!ay(5Y}?a?P8! zbQp3yccIpDoheHnQy#pz$l*Wt-E92OhUahTRN2KsRR%AsvZt?0-q@3{tf=$fWQFO+ zWboL;pwDx)1{i$tTh&eRX*|2Z=-r)du=-=!pkZwdD!KgXJWbP?K2*&{cg<-mv~vnhEqUy-C&?Fe(LEg?eq1RbxaXPMt{nc zDxvrGC0^Oea5AQTHY%>Dx5Bo7KyT7p5|AFSbvbASKk55vn4A?oB!ZVaTwxTOGSq-E zp;0*On9VbQVWcd}$}liDdDJ>Rw&6VA(s<9ILPGw5ylp@BW{HB9Lvo)tbDc)662{6a za><=t*CBfuRW4%tXH)+Tm<(7J#_=#NL;Ias5>eEr4Dd!{M7!d6vnEW56JX)VL2KV6 zUe1>eS{6qXH{5{`PFI<5VdM6Dlb-3QSYU~Agy8H_;pwM;sCmPY%mP$)bM%b1N?hsX zb6S>EYd@066^(TSn{59%MvmAAQP6eWlUI}%+g8~X?+*h(!hj#uRx*(FsINnyH;JJC zX8)li62>U#Px;n8vg0_gWH~O}Nk7k_HVsrxtVCAG??#QZEFVD=SKKmpHV zqI!r|xUqz&SyhdmVL9*IGoLR+R{i!zDRSW#(gI4k!=2pG%Z~a2X3b8TgOG4K!ZR0n SQ!5lwgUOzKyK{FkF8>!P27BxP literal 0 HcmV?d00001 diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/manifest.json b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/manifest.json new file mode 100644 index 0000000..ec02fb8 --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/manifest.json @@ -0,0 +1,25 @@ +{ + "short_name": "Learning Blocks", + "name": "Learning Blocks student information system", + "icons": [ + { + "src": "favicon.ico", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/x-icon" + }, + { + "src": "logo192.png", + "type": "image/png", + "sizes": "192x192" + }, + { + "src": "logo512.png", + "type": "image/png", + "sizes": "512x512" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" +} diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/robots.txt b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/robots.txt new file mode 100644 index 0000000..e9e57dc --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/public/robots.txt @@ -0,0 +1,3 @@ +# https://www.robotstxt.org/robotstxt.html +User-agent: * +Disallow: diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/SearchBar.css b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/SearchBar.css new file mode 100644 index 0000000..4ec55fb --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/SearchBar.css @@ -0,0 +1,72 @@ +/* SearchBar component styles */ +.searchbar-root { + position: relative; + min-width: 220px; + max-width: 400px; +} + +@media screen and (min-width: 1200px) { + .searchbar-root { + min-width: 400px; + } +} + +.searchbar-form { + display: flex; + flex-direction: column; +} + +.searchbar-input { + padding: 6px 6px 6px 39px; + border-radius: 4px; + border: 1px solid #ccc; + font-size: 16px; + background-image: url("/search.svg"); + background-repeat: no-repeat; + background-position: 10px center; + background-size: 18px 18px; +} + +.searchbar-dropdown { + position: absolute; + top: 100%; + left: 0; + right: 0; + background: #fff; + border: 1px solid #eee; + border-radius: 4px; + margin-top: 4px; + z-index: 10; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); + max-height: 300px; + overflow-y: auto; +} + +.searchbar-category-block { + padding: 8px 0; +} + +.searchbar-category-heading { + font-weight: 600; + font-size: 13px; + color: #888; + padding: 4px 16px 2px 16px; +} + +.searchbar-result-item { + display: block; + width: 100%; + text-align: left; + background: none; + border: none; + padding: 6px 16px; + cursor: pointer; + font-size: 15px; + color: #222; + transition: background 0.15s; +} + +.searchbar-result-item:hover, +.searchbar-result-item:focus { + background: #f5f5f5; +} diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/SearchBar.tsx b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/SearchBar.tsx new file mode 100644 index 0000000..44867b0 --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/SearchBar.tsx @@ -0,0 +1,97 @@ +import { useForm } from "@tanstack/react-form"; +import * as React from "react"; +import "./SearchBar.css"; + +// Example preview data +const previewResults = [ + { + category: "Users", + items: [ + { id: 1, label: "Alice" }, + { id: 2, label: "Bob" }, + ], + }, + { + category: "Projects", + items: [ + { id: 3, label: "Apollo" }, + { id: 4, label: "Gemini" }, + ], + }, +]; + +export default function SearchBar({ className }: { className?: string }) { + const [showDropdown, setShowDropdown] = React.useState(false); + const [query, setQuery] = React.useState(""); + + const form = useForm({ + defaultValues: { search: "" }, + onSubmit: ({ value }) => { + // handle search submit + setQuery(value.search); + setShowDropdown(true); + }, + }); + + // Filter preview results by query + const filteredResults = React.useMemo(() => { + if (!query) return previewResults; + return previewResults + .map((cat) => ({ + ...cat, + items: cat.items.filter((item) => + item.label.toLowerCase().includes(query.toLowerCase()), + ), + })) + .filter((cat) => cat.items.length > 0); + }, [query]); + + return ( +
+
+ { + form.setFieldValue("search", e.target.value); + setQuery(e.target.value); + setShowDropdown(true); + }} + onFocus={() => { + setShowDropdown(true); + }} + onBlur={() => setTimeout(() => setShowDropdown(false), 150)} + placeholder="Search..." + className="searchbar-input" + /> +
+ {showDropdown && filteredResults.length > 0 && ( +
+ {filteredResults.map((cat) => ( +
+
{cat.category}
+ {cat.items.map((item) => ( + + ))} +
+ ))} +
+ )} +
+ ); +} diff --git a/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/demo.FormComponents.tsx b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/demo.FormComponents.tsx new file mode 100644 index 0000000..4a4959f --- /dev/null +++ b/LB_Community_Edition_Non_Enterprise/Pilot_Iteration_Files/data-dashboard-iteration-react/src/components/demo.FormComponents.tsx @@ -0,0 +1,128 @@ +import { useStore } from "@tanstack/react-form"; + +import { useFieldContext, useFormContext } from "../hooks/demo.form-context"; + +export function SubscribeButton({ label }: { label: string }) { + const form = useFormContext(); + return ( + state.isSubmitting}> + {(isSubmitting) => ( + + )} + + ); +} + +function ErrorMessages({ + errors, +}: { + errors: Array; +}) { + return ( + <> + {errors.map((error) => ( +
+ {typeof error === "string" ? error : error.message} +
+ ))} + + ); +} + +export function TextField({ + label, + placeholder, +}: { + label: string; + placeholder?: string; +}) { + const field = useFieldContext(); + const errors = useStore(field.store, (state) => state.meta.errors); + + return ( +
+ + {field.state.meta.isTouched && } +
+ ); +} + +export function TextArea({ + label, + rows = 3, +}: { + label: string; + rows?: number; +}) { + const field = useFieldContext(); + const errors = useStore(field.store, (state) => state.meta.errors); + + return ( +
+