diff --git a/docs/router/config.json b/docs/router/config.json index 0b56fa735d6..da02430fe73 100644 --- a/docs/router/config.json +++ b/docs/router/config.json @@ -17,10 +17,6 @@ "label": "Overview", "to": "framework/react/overview" }, - { - "label": "Installation", - "to": "framework/react/installation" - }, { "label": "Quick Start", "to": "framework/react/quick-start" @@ -29,18 +25,6 @@ "label": "Devtools", "to": "framework/react/devtools" }, - { - "label": "Comparison", - "to": "framework/react/comparison" - }, - { - "label": "Migrate from React Router", - "to": "framework/react/migrate-from-react-router" - }, - { - "label": "Migrate from React Location", - "to": "framework/react/migrate-from-react-location" - }, { "label": "Decisions on DX", "to": "framework/react/decisions-on-dx" @@ -58,10 +42,6 @@ "label": "Overview", "to": "framework/solid/overview" }, - { - "label": "Installation", - "to": "framework/solid/installation" - }, { "label": "Quick Start", "to": "framework/solid/quick-start" @@ -82,6 +62,58 @@ } ] }, + { + "label": "Installation Guides", + "children": [], + "frameworks": [ + { + "label": "react", + "children": [ + { + "label": "Manual Setup", + "to": "framework/react/installation/manual" + }, + { + "label": "Vite", + "to": "framework/react/installation/with-vite" + }, + { + "label": "Rspack/Rsbuild", + "to": "framework/react/installation/with-rspack" + }, + { + "label": "Webpack", + "to": "framework/react/installation/with-webpack" + }, + { + "label": "ESbuild", + "to": "framework/react/installation/with-esbuild" + }, + { + "label": "Router CLI", + "to": "framework/react/installation/with-router-cli" + }, + { + "label": "Migrate from React Router", + "to": "framework/react/installation/migrate-from-react-router" + }, + { + "label": "Migrate from React Location", + "to": "framework/react/installation/migrate-from-react-location" + } + ] + }, + { + "label": "solid", + "children": [ + { + "label": "Manual Setup", + "to": "framework/solid/installation/manual" + } + ] + } + ] + }, { "label": "Routing", "children": [], @@ -113,26 +145,6 @@ "label": "Code-Based Routing", "to": "framework/react/routing/code-based-routing" }, - { - "label": "Installation with Vite", - "to": "framework/react/routing/installation-with-vite" - }, - { - "label": "Installation with Rspack/Rsbuild", - "to": "framework/react/routing/installation-with-rspack" - }, - { - "label": "Installation with Webpack", - "to": "framework/react/routing/installation-with-webpack" - }, - { - "label": "Installation with Esbuild", - "to": "framework/react/routing/installation-with-esbuild" - }, - { - "label": "Installation with the Router CLI", - "to": "framework/react/routing/installation-with-router-cli" - }, { "label": "File Naming Conventions", "to": "framework/react/routing/file-naming-conventions" @@ -418,25 +430,6 @@ } ] }, - { - "label": "How-To", - "children": [], - "frameworks": [ - { - "label": "react", - "children": [ - { - "label": "Install TanStack Router", - "to": "framework/react/how-to/install" - } - ] - }, - { - "label": "solid", - "children": [] - } - ] - }, { "label": "API", "children": [ diff --git a/docs/router/framework/react/comparison.md b/docs/router/framework/react/comparison.md deleted file mode 100644 index d6f5449bb92..00000000000 --- a/docs/router/framework/react/comparison.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -title: Comparison | TanStack Router & TanStack Start vs Next.js vs React Router / Remix -toc: false ---- - -Before you commit to a new tool, it's always nice to know how it stacks up against the competition! - -> This comparison table strives to be as accurate and as unbiased as possible. If you use any of these libraries and feel the information could be improved, feel free to suggest changes (with notes or evidence of claims) using the "Edit this page on GitHub" link at the bottom of this page. - -Feature/Capability Key: - -- βœ… 1st-class, built-in, and ready to use with no added configuration or code -- 🟑 Partial Support (on a scale of 5) -- 🟠 Supported via addon/community package -- πŸ”Ά Possible, but requires custom code/implementation/casting -- πŸ›‘ Not officially supported - -| | TanStack Router / Start | React Router DOM [_(Website)_][router] | Next.JS [_(Website)_][nextjs] | -| ---------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------- | ----------------------------------------------------- | -| Github Repo / Stars | [![][stars-tanstack-router]][gh-tanstack-router] | [![][stars-router]][gh-router] | [![][stars-nextjs]][gh-nextjs] | -| Bundle Size | [![][bp-tanstack-router]][bpl-tanstack-router] | [![][bp-router]][bpl-router] | ❓ | -| History, Memory & Hash Routers | βœ… | βœ… | πŸ›‘ | -| Nested / Layout Routes | βœ… | βœ… | 🟑 | -| Suspense-like Route Transitions | βœ… | βœ… | βœ… | -| Typesafe Routes | βœ… | 🟑 (1/5) | 🟑 | -| Code-based Routes | βœ… | βœ… | πŸ›‘ | -| File-based Routes | βœ… | βœ… | βœ… | -| Virtual/Programmatic File-based Routes | βœ… | βœ… | πŸ›‘ | -| Router Loaders | βœ… | βœ… | βœ… | -| SWR Loader Caching | βœ… | πŸ›‘ | βœ… | -| Route Prefetching | βœ… | βœ… | βœ… | -| Auto Route Prefetching | βœ… | βœ… | βœ… | -| Route Prefetching Delay | βœ… | πŸ”Ά | πŸ›‘ | -| Path Params | βœ… | βœ… | βœ… | -| Typesafe Path Params | βœ… | βœ… | πŸ›‘ | -| Typesafe Route Context | βœ… | πŸ›‘ | πŸ›‘ | -| Path Param Validation | βœ… | πŸ›‘ | πŸ›‘ | -| Custom Path Param Parsing/Serialization | βœ… | πŸ›‘ | πŸ›‘ | -| Ranked Routes | βœ… | βœ… | βœ… | -| Active Link Customization | βœ… | βœ… | βœ… | -| Optimistic UI | βœ… | βœ… | πŸ”Ά | -| Typesafe Absolute + Relative Navigation | βœ… | 🟑 (1/5 via `buildHref` util) | 🟠 (IDE plugin) | -| Route Mount/Transition/Unmount Events | βœ… | πŸ›‘ | πŸ›‘ | -| Devtools | βœ… | 🟠 | πŸ›‘ | -| Basic Search Params | βœ… | βœ… | βœ… | -| Search Param Hooks | βœ… | βœ… | βœ… | -| ``/`useNavigate` Search Param API | βœ… | 🟑 (search-string only via the `to`/`search` options) | 🟑 (search-string only via the `to`/`search` options) | -| JSON Search Params | βœ… | πŸ”Ά | πŸ”Ά | -| TypeSafe Search Params | βœ… | πŸ›‘ | πŸ›‘ | -| Search Param Schema Validation | βœ… | πŸ›‘ | πŸ›‘ | -| Search Param Immutability + Structural Sharing | βœ… | πŸ”Ά | πŸ›‘ | -| Custom Search Param parsing/serialization | βœ… | πŸ”Ά | πŸ›‘ | -| Search Param Middleware | βœ… | πŸ›‘ | πŸ›‘ | -| Suspense Route Elements | βœ… | βœ… | βœ… | -| Route Error Elements | βœ… | βœ… | βœ… | -| Route Pending Elements | βœ… | βœ… | βœ… | -| ``/`useBlocker` | βœ… | πŸ”Ά (no hard reloads or cross-origin navigation) | πŸ›‘ | -| Deferred Primitives | βœ… | βœ… | βœ… | -| Navigation Scroll Restoration | βœ… | βœ… | ❓ | -| ElementScroll Restoration | βœ… | πŸ›‘ | πŸ›‘ | -| Async Scroll Restoration | βœ… | πŸ›‘ | πŸ›‘ | -| Router Invalidation | βœ… | βœ… | βœ… | -| Runtime Route Manipulation (Fog of War) | πŸ›‘ | βœ… | βœ… | -| Parallel Routes | πŸ›‘ | πŸ›‘ | βœ… | -| -- | -- | -- | -- | -| **Full Stack** | -- | -- | -- | -| SSR | βœ… | βœ… | βœ… | -| Streaming SSR | βœ… | βœ… | βœ… | -| Generic RPCs | βœ… | πŸ›‘ | πŸ›‘ | -| Generic RPC Middleware | βœ… | πŸ›‘ | πŸ›‘ | -| React Server Functions | βœ… | πŸ›‘ | βœ… | -| React Server Function Middleware | βœ… | πŸ›‘ | πŸ›‘ | -| API Routes | βœ… | βœ… | βœ… | -| API Middleware | βœ… | βœ… | βœ… | -| React Server Components | πŸ›‘ | 🟑 (Experimental) | βœ… | -| `
` API | πŸ›‘ | βœ… | βœ… | - -[bp-tanstack-router]: https://badgen.net/bundlephobia/minzip/@tanstack/react-router -[bpl-tanstack-router]: https://bundlephobia.com/result?p=@tanstack/react-router -[gh-tanstack-router]: https://github.com/tanstack/router -[stars-tanstack-router]: https://img.shields.io/github/stars/tanstack/router?label=%F0%9F%8C%9F -[_]: _ -[router]: https://github.com/remix-run/react-router -[bp-router]: https://badgen.net/bundlephobia/minzip/react-router -[gh-router]: https://github.com/remix-run/react-router -[stars-router]: https://img.shields.io/github/stars/remix-run/react-router?label=%F0%9F%8C%9F -[bpl-router]: https://bundlephobia.com/result?p=react-router -[bpl-history]: https://bundlephobia.com/result?p=history -[_]: _ -[nextjs]: https://nextjs.org/docs/routing/introduction -[bp-nextjs]: https://badgen.net/bundlephobia/minzip/next.js?label=All -[gh-nextjs]: https://github.com/vercel/next.js -[stars-nextjs]: https://img.shields.io/github/stars/vercel/next.js?label=%F0%9F%8C%9F -[bpl-nextjs]: https://bundlephobia.com/result?p=next diff --git a/docs/router/framework/react/faq.md b/docs/router/framework/react/faq.md index 4bed5d4a64b..4be46ab9372 100644 --- a/docs/router/framework/react/faq.md +++ b/docs/router/framework/react/faq.md @@ -4,6 +4,21 @@ title: 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). +## Why should you choose TanStack Router over another router? + +To answer this question, it's important to view the other options in the space. There are many alternatives to choose from, but only a couple that are widely adopted and actively maintained: + +- **Next.js** - Widely regarded as the leading framework for starting new React projects. Its design focuses on performance, development workflows, and cutting-edge technology. The framework's APIs and abstractions, while powerful, can sometimes present as non-standard. Rapid growth and industry adoption have resulted in a feature-rich experience, sometimes leading to a steeper learning curve and increased overhead. +- **Remix / React Router** - Based on the historically successful React Router, Remix delivers a powerful developer and user experience. Its API and architectural vision are firmly rooted in web standards such as Request/Response, with an emphasis on adaptability across various JavaScript environments. Many of its APIs and abstractions are well-designed and have influenced more than a few of TanStack Router's APIs. However, its rigid design, the integration of type safety as an add-on, and sometimes strict adherence to platform APIs can present limitations for some developers. + +These frameworks and routers have their strengths, but they also come with trade-offs that may not align with every project's needs. TanStack Router aims to strike a balance by offering routing APIs designed to improving the developer experience without sacrificing flexibility or performance. + +## Is TanStack Router 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). + ## Should I commit my `routeTree.gen.ts` file into git? Yes! Although the route tree file (i.e., `routeTree.gen.ts`) is generated by TanStack Router, it is essentially part of your application’s runtime, not a build artifact. The route tree file is a critical part of your application’s source code, and it is used by TanStack Router to build your application’s routes at runtime. @@ -50,3 +65,103 @@ function PathlessLayoutRouteComponent() { ``` + +## 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 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. diff --git a/docs/router/framework/react/guide/navigation.md b/docs/router/framework/react/guide/navigation.md index 308a8f8f9ce..42e0784d8ac 100644 --- a/docs/router/framework/react/guide/navigation.md +++ b/docs/router/framework/react/guide/navigation.md @@ -185,6 +185,11 @@ By default, all links are absolute unless a `from` route path is provided. This Relative links can be combined with a `from` route path. If a from route path isn't provided, relative paths default to the current active location. +> [!NOTE] +> Keep in mind that when calling useNavigate as a method on the route, for example `Route.useNavigate`, then the `from` location is predefined to be the route it's called on. +> +> Another common pitfall is when using this in a pathless layout route, since the pathless layout route does not have an actual path, the `from` location is regarded as the parent of the pathless layout route. Hence relative routing will be resolved from this parent. + ```tsx const postIdRoute = createRoute({ path: '/blog/post/$postId', diff --git a/docs/router/framework/react/guide/ssr.md b/docs/router/framework/react/guide/ssr.md index 9a931c4e340..2a52914199d 100644 --- a/docs/router/framework/react/guide/ssr.md +++ b/docs/router/framework/react/guide/ssr.md @@ -70,9 +70,9 @@ declare module '@tanstack/react-router' { ### 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: +Now that you have a router instance that has loaded all the critical data for the current URL, you can render your application on the server: -using `defaultRenderToString` +using `defaultRenderHandler` ```tsx // src/entry-server.tsx @@ -85,7 +85,7 @@ import { createRouter } from './router' export async function render({ request }: { request: Request }) { const handler = createRequestHandler({ request, createRouter }) - return await handler(defaultRenderToString) + return await handler(defaultRenderHandler) } ``` @@ -144,7 +144,7 @@ With this setup, your application will be rendered on the server and then hydrat ## 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. +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 lower 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. diff --git a/docs/router/framework/react/how-to/README.md b/docs/router/framework/react/how-to/README.md deleted file mode 100644 index e850612620d..00000000000 --- a/docs/router/framework/react/how-to/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# How-To Guides - -This directory contains focused, step-by-step instructions for common TanStack Router tasks. Each guide is designed to be: - -- **Task-focused**: Covers one specific goal from start to finish -- **Self-contained**: All necessary steps included, no prerequisites assumed -- **Copy-paste ready**: Working code examples you can use immediately -- **Problem-solving oriented**: Addresses real-world scenarios and common issues - -## Available Guides - -- [Install TanStack Router](./install.md) - Basic installation steps -- [Use Environment Variables](./use-environment-variables.md) - Configure and use environment variables across different bundlers -- [Deploy to Production](./deploy-to-production.md) - Deploy your app to hosting platforms -- [Set Up Server-Side Rendering (SSR)](./setup-ssr.md) - Implement SSR with TanStack Router -- [Migrate from React Router v7](./migrate-from-react-router.md) - Complete migration guide from React Router v7 - -### Authentication - -- [Set Up Basic Authentication](./setup-authentication.md) - Implement basic auth with React Context -- [Integrate Authentication Providers](./setup-auth-providers.md) - Use Auth0, Clerk, or Supabase -- [Set Up Role-Based Access Control](./setup-rbac.md) - Add permission-based routing - -### Testing & Debugging - -- [Set Up Testing with Code-Based Routing](./setup-testing.md) - Comprehensive testing setup for manually defined routes -- [Test File-Based Routing](./test-file-based-routing.md) - Specific patterns for testing file-based routing applications -- [Debug Router Issues](./debug-router-issues.md) - Troubleshoot common routing problems and performance issues - -### UI Library Integration - -- [Integrate with Shadcn/ui](./integrate-shadcn-ui.md) - Set up Shadcn/ui components with animations and styling -- [Integrate with Material-UI (MUI)](./integrate-material-ui.md) - Configure MUI components with proper theming and TypeScript -- [Integrate with Framer Motion](./integrate-framer-motion.md) - Add smooth animations and transitions to your routes -- [Integrate with Chakra UI](./integrate-chakra-ui.md) - Build responsive, accessible UIs with Chakra UI components - -### Search Parameters & URL State (Progressive Series) - -**Foundation Level (Start Here):** - -- [x] [Set Up Basic Search Parameters](./setup-basic-search-params.md) - Create type-safe search validation and reading -- [x] [Navigate with Search Parameters](./navigate-with-search-params.md) - Update and manage search params with Links and navigation - -**Intermediate Level (Common Patterns):** - -- [x] [Validate Search Parameters with Schemas](./validate-search-params.md) - Use schema validation libraries for robust validation and type safety -- [x] [Work with Arrays, Objects, and Dates](./arrays-objects-dates-search-params.md) - Handle arrays, objects, dates, and nested data structures -- [x] [Share Search Parameters Across Routes](./share-search-params-across-routes.md) - Inherit and manage search params across route hierarchies - -**Advanced Level (Power User Patterns):** - -- [ ] Build Advanced Search Parameter Middleware - Create custom middleware for search param processing -- [ ] Optimize Search Parameter Performance - Minimize re-renders and improve performance with selectors -- [ ] Customize Search Parameter Serialization - Implement custom serialization for compression and compatibility - -**Specialized Use Cases:** - -- [ ] Build Search-Based Filtering Systems - Create complex filtering UIs with URL state -- [ ] Handle Search Parameters in Forms - Synchronize form state with URL search parameters -- [ ] Debug Search Parameter Issues - Troubleshoot common search param problems and performance issues -- [ ] Use Search Parameters with Data Loading - Integrate search params with loaders and data fetching - -## Using These Guides - -Each guide follows a consistent structure: - -1. **Quick Start** - Brief overview of what you'll accomplish -2. **Step-by-step instructions** - Platform-specific or scenario-specific guidance -3. **Production checklist** - Verification steps (where applicable) -4. **Common problems** - Troubleshooting for typical issues -5. **Common next steps** - Optional related tasks you might want to tackle - -## Contributing - -When adding new how-to guides: - -1. Focus on a single, well-defined task -2. Use clear headings and numbered steps -3. Include complete, working code examples -4. Address the most frequent problems at the end -5. Comment out "Common next steps" links until those guides are created -6. In "Related Resources", link to specific relevant resources, not generic documentation diff --git a/docs/router/framework/react/how-to/arrays-objects-dates-search-params.md b/docs/router/framework/react/how-to/arrays-objects-dates-search-params.md deleted file mode 100644 index af31fb8a0b6..00000000000 --- a/docs/router/framework/react/how-to/arrays-objects-dates-search-params.md +++ /dev/null @@ -1,768 +0,0 @@ ---- -title: Work with Arrays, Objects, and Dates in Search Parameters ---- - -Learn to handle arrays, objects, dates, and nested data structures in search parameters while maintaining type safety and URL compatibility. - -## Quick Start - -Complex search parameters go beyond simple strings and numbers. TanStack Router's JSON-first approach makes it easy to handle arrays, objects, dates, and nested structures: - -```tsx -// Example of complex search parameters -const complexSearch = { - tags: ['typescript', 'react', 'router'], // Array - filters: { - // Nested object - category: 'web', - minRating: 4.5, - active: true, - }, - dateRange: { - // Date objects - start: new Date('2024-01-01'), - end: new Date('2024-12-31'), - }, - pagination: { - // Nested pagination - page: 1, - size: 20, - sort: { field: 'name', direction: 'asc' }, - }, -} -``` - -## Working with Arrays - -Arrays are commonly used for filters, tags, categories, and multi-select options. - -### Basic Array Validation - -```tsx -// routes/products.tsx -import { createFileRoute } from '@tanstack/react-router' -import { z } from 'zod' - -const searchSchema = z.object({ - categories: z.array(z.string()).default([]), - tags: z.array(z.string()).optional(), - priceRange: z.array(z.number()).length(2).optional(), // [min, max] -}) - -export const Route = createFileRoute('/products')({ - validateSearch: searchSchema, - component: ProductsComponent, -}) - -function ProductsComponent() { - const { categories, tags, priceRange } = Route.useSearch() - - return ( -
-

Active Categories: {categories.join(', ')}

- {tags &&

Tags: {tags.join(', ')}

} - {priceRange && ( -

- Price: ${priceRange[0]} - ${priceRange[1]} -

- )} -
- ) -} -``` - -### Navigating with Arrays - -```tsx -import { Link } from '@tanstack/react-router' - -function FilterControls() { - return ( -
- {/* Add to existing array */} - ({ - ...prev, - categories: [...(prev.categories || []), 'electronics'], - })} - > - Add Electronics - - - {/* Replace entire array */} - - Books & Music Only - - - {/* Remove from array */} - ({ - ...prev, - categories: - prev.categories?.filter((cat) => cat !== 'electronics') || [], - })} - > - Remove Electronics - - - {/* Clear array */} - ({ ...prev, categories: [] })}> - Clear All - -
- ) -} -``` - -### Advanced Array Patterns - -```tsx -// routes/search.tsx -const advancedArraySchema = z.object({ - // Array of objects - filters: z - .array( - z.object({ - field: z.string(), - operator: z.enum(['eq', 'gt', 'lt', 'contains']), - value: z.union([z.string(), z.number(), z.boolean()]), - }), - ) - .default([]), - - // Array with constraints - selectedIds: z.array(z.string().uuid()).max(10).default([]), - - // Array with transformation - sortFields: z - .array(z.string()) - .transform((arr) => - arr.filter((field) => ['name', 'date', 'price'].includes(field)), - ) - .default(['name']), -}) - -export const Route = createFileRoute('/search')({ - validateSearch: advancedArraySchema, - component: SearchComponent, -}) -``` - -## Working with Objects - -Objects are useful for grouped parameters, complex filters, and nested configurations. - -### Basic Object Validation - -```tsx -// routes/dashboard.tsx -const dashboardSchema = z.object({ - view: z - .object({ - layout: z.enum(['grid', 'list', 'cards']).default('grid'), - columns: z.number().min(1).max(6).default(3), - showDetails: z.boolean().default(false), - }) - .default({}), - - filters: z - .object({ - status: z.enum(['active', 'inactive', 'pending']).optional(), - dateCreated: z - .object({ - after: z.string().optional(), - before: z.string().optional(), - }) - .optional(), - metadata: z.record(z.string()).optional(), // Dynamic object keys - }) - .default({}), -}) - -export const Route = createFileRoute('/dashboard')({ - validateSearch: dashboardSchema, - component: DashboardComponent, -}) - -function DashboardComponent() { - const { view, filters } = Route.useSearch() - - return ( -
-
- {/* Render based on complex object state */} -
- - {filters.status &&

Status: {filters.status}

} - {filters.dateCreated?.after && ( -

Created after: {filters.dateCreated.after}

- )} -
- ) -} -``` - -### Navigating with Objects - -```tsx -function ViewControls() { - return ( -
- {/* Update nested object property */} - ({ - ...prev, - view: { - ...prev.view, - layout: 'list', - }, - })} - > - List View - - - {/* Update multiple nested properties */} - ({ - ...prev, - view: { - ...prev.view, - layout: 'grid', - columns: 4, - showDetails: true, - }, - })} - > - 4-Column Grid with Details - - - {/* Deep merge with library for complex updates */} - - merge(prev, { - filters: { - dateCreated: { after: '2024-01-01' }, - }, - }) - } - > - Filter Recent Items - -
- ) -} - -// For deep merging, use a well-tested library: - -// Option 1: Lodash (most popular, full-featured) -// npm install lodash-es -// import { merge } from 'lodash-es' - -// Option 2: deepmerge (lightweight, focused) -// npm install deepmerge -// import merge from 'deepmerge' - -// Option 3: Ramda (functional programming style) -// npm install ramda -// import { mergeDeepRight as merge } from 'ramda' - -// Example with deepmerge (recommended for most cases): -import merge from 'deepmerge' - -// Handles arrays intelligently - combines by default -const result = merge( - { filters: { tags: ['react'] } }, - { filters: { tags: ['typescript'] } }, -) -// Result: { filters: { tags: ['react', 'typescript'] } } - -// Override array merging behavior if needed -const overwriteResult = merge( - { filters: { tags: ['react'] } }, - { filters: { tags: ['typescript'] } }, - { arrayMerge: (dest, source) => source }, // Overwrite instead of combine -) -// Result: { filters: { tags: ['typescript'] } } -``` - -## Working with Dates - -Dates require special handling for URL serialization and validation. - -### Date Validation and Serialization - -```tsx -// routes/events.tsx -const eventSchema = z.object({ - // ISO string dates - startDate: z.string().datetime().optional(), - endDate: z.string().datetime().optional(), - - // Date range as object - dateRange: z - .object({ - start: z.string().datetime(), - end: z.string().datetime(), - }) - .optional(), - - // Transform string to Date object - selectedDate: z - .string() - .datetime() - .transform((str) => new Date(str)) - .optional(), - - // Relative dates - timeFilter: z.enum(['today', 'week', 'month', 'year']).default('week'), -}) - -export const Route = createFileRoute('/events')({ - validateSearch: eventSchema, - component: EventsComponent, -}) - -function EventsComponent() { - const search = Route.useSearch() - - // Convert string dates back to Date objects for display - const startDate = search.startDate ? new Date(search.startDate) : null - const endDate = search.endDate ? new Date(search.endDate) : null - - return ( -
- {startDate &&

Events from: {startDate.toLocaleDateString()}

} - {search.selectedDate && ( -

Selected: {search.selectedDate.toLocaleDateString()}

- )} -
- ) -} -``` - -### Date Navigation Patterns - -```tsx -function DateControls() { - const navigate = useNavigate() - - const setDateRange = (start: Date, end: Date) => { - navigate({ - to: '/events', - search: (prev) => ({ - ...prev, - dateRange: { - start: start.toISOString(), - end: end.toISOString(), - }, - }), - }) - } - - const setRelativeDate = (period: string) => { - const now = new Date() - let start: Date - - switch (period) { - case 'today': - start = new Date(now.getFullYear(), now.getMonth(), now.getDate()) - break - case 'week': - start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) - break - case 'month': - start = new Date(now.getFullYear(), now.getMonth() - 1, now.getDate()) - break - default: - start = now - } - - setDateRange(start, now) - } - - return ( -
- - - - - {/* Date picker integration */} - { - const date = new Date(e.target.value) - navigate({ - to: '/events', - search: (prev) => ({ - ...prev, - selectedDate: date.toISOString(), - }), - }) - }} - /> -
- ) -} -``` - -## Nested Data Structures - -Complex applications often need deeply nested search parameters. - -### Complex Nested Schema - -```tsx -// routes/analytics.tsx -const analyticsSchema = z.object({ - dashboard: z - .object({ - widgets: z - .array( - z.object({ - id: z.string(), - type: z.enum(['chart', 'table', 'metric']), - config: z.object({ - title: z.string(), - dataSource: z.string(), - filters: z.array( - z.object({ - field: z.string(), - operator: z.string(), - value: z.any(), - }), - ), - visualization: z - .object({ - chartType: z.enum(['line', 'bar', 'pie']).optional(), - colors: z.array(z.string()).optional(), - axes: z - .object({ - x: z.string(), - y: z.array(z.string()), - }) - .optional(), - }) - .optional(), - }), - }), - ) - .default([]), - - layout: z - .object({ - columns: z.number().min(1).max(12).default(2), - gap: z.number().default(16), - responsive: z.boolean().default(true), - }) - .default({}), - - timeRange: z - .object({ - preset: z.enum(['1h', '24h', '7d', '30d', 'custom']).default('24h'), - custom: z - .object({ - start: z.string().datetime(), - end: z.string().datetime(), - }) - .optional(), - }) - .default({}), - }) - .default({}), -}) - -export const Route = createFileRoute('/analytics')({ - validateSearch: analyticsSchema, - component: AnalyticsComponent, -}) -``` - -### Managing Complex State Updates - -```tsx -function AnalyticsControls() { - const search = Route.useSearch() - const navigate = useNavigate() - - // Helper to update nested widget config - const updateWidgetConfig = (widgetId: string, configUpdate: any) => { - navigate({ - to: '/analytics', - search: (prev) => ({ - ...prev, - dashboard: { - ...prev.dashboard, - widgets: prev.dashboard.widgets.map((widget) => - widget.id === widgetId - ? { - ...widget, - config: { ...widget.config, ...configUpdate }, - } - : widget, - ), - }, - }), - }) - } - - // Helper to add new widget - const addWidget = (widget: any) => { - navigate({ - to: '/analytics', - search: (prev) => ({ - ...prev, - dashboard: { - ...prev.dashboard, - widgets: [...prev.dashboard.widgets, widget], - }, - }), - }) - } - - // Helper to update layout - const updateLayout = (layoutUpdate: any) => { - navigate({ - to: '/analytics', - search: (prev) => ({ - ...prev, - dashboard: { - ...prev.dashboard, - layout: { ...prev.dashboard.layout, ...layoutUpdate }, - }, - }), - }) - } - - return ( -
- - - -
- ) -} -``` - -## Performance Optimization - -### Selective Updates with Selectors - -```tsx -// Only re-render when specific nested values change -function WidgetComponent({ widgetId }: { widgetId: string }) { - // Use selector to avoid unnecessary re-renders - const widget = Route.useSearch({ - select: (search) => search.dashboard.widgets.find((w) => w.id === widgetId), - }) - - const layout = Route.useSearch({ - select: (search) => search.dashboard.layout, - }) - - if (!widget) return null - - return ( -
-

{widget.config.title}

- {/* Widget content */} -
- ) -} -``` - -### Memoization for Complex Transforms - -```tsx -import { useMemo } from 'react' - -function ComplexDataComponent() { - const search = Route.useSearch() - - // Memoize expensive transformations - const processedData = useMemo(() => { - return search.dashboard.widgets - .filter((widget) => widget.type === 'chart') - .map((widget) => ({ - ...widget, - computedMetrics: expensiveCalculation(widget.config), - })) - }, [search.dashboard.widgets]) - - return ( -
- {processedData.map((widget) => ( - - ))} -
- ) -} -``` - -## Production Checklist - -- [ ] **Array bounds validation** - Use `.min()`, `.max()`, `.length()` constraints -- [ ] **Date format consistency** - Stick to ISO strings for URL compatibility -- [ ] **Object depth limits** - Avoid excessively nested structures for URL length -- [ ] **Performance testing** - Test with large arrays/objects in search params -- [ ] **URL length limits** - Most browsers limit URLs to ~2000 characters -- [ ] **Fallback values** - Provide sensible defaults for all complex types -- [ ] **Type safety** - Ensure schemas match your component expectations -- [ ] **Serialization testing** - Verify round-trip serialization works correctly - -## Common Problems - -### Problem: Array Parameters Not Updating - -**Symptoms:** Link clicks don't update array search parameters. - -**Cause:** Directly mutating arrays instead of creating new ones. - -**Solution:** Always create new arrays when updating: - -```tsx -// ❌ Wrong - mutates existing array -search={(prev) => { - prev.categories.push('new-item') - return prev -}} - -// βœ… Correct - creates new array -search={(prev) => ({ - ...prev, - categories: [...prev.categories, 'new-item'] -})} -``` - -### Problem: Dates Not Serializing Correctly - -**Symptoms:** Date objects become `[object Object]` in URL. - -**Cause:** Attempting to serialize Date objects directly. - -**Solution:** Convert dates to ISO strings: - -```tsx -// ❌ Wrong - Date objects don't serialize -search={{ - startDate: new Date() // Becomes "[object Object]" -}} - -// βœ… Correct - Use ISO strings -search={{ - startDate: new Date().toISOString() -}} -``` - -### Problem: Deep Object Updates Not Working - -**Symptoms:** Nested object properties don't update as expected. - -**Cause:** Shallow merging doesn't update nested properties. - -**Solution:** Use proper deep merging or spread operators: - -```tsx -// ❌ Wrong - shallow merge loses nested properties -search={(prev) => ({ - ...prev, - filters: { category: 'new' } // Loses other filter properties -})} - -// βœ… Correct - preserve nested properties -search={(prev) => ({ - ...prev, - filters: { - ...prev.filters, - category: 'new' - } -})} -``` - -### Problem: URL Too Long Error - -**Symptoms:** Browser errors with very complex search parameters. - -**Cause:** Exceeding browser URL length limits (~2000 characters). - -**Solutions:** - -1. **Simplify data structures** - Remove unnecessary nesting -2. **Use compression** - Implement custom serialization -3. **Store in session** - Keep complex state in sessionStorage with URL key -4. **Pagination** - Break large arrays into pages - -```tsx -// Option 3: Session storage approach -const sessionKey = Route.useSearch({ select: (s) => s.sessionKey }) -const complexData = useMemo(() => { - if (sessionKey) { - return JSON.parse(sessionStorage.getItem(sessionKey) || '{}') - } - return {} -}, [sessionKey]) -``` - -### Problem: Performance Issues with Large Objects - -**Symptoms:** Slow navigation and re-renders with complex search parameters. - -**Cause:** Large objects causing expensive serialization and comparison operations. - -**Solutions:** - -1. **Use selectors** to limit re-renders -2. **Memoize expensive calculations** -3. **Consider alternatives** like context or state management - -```tsx -// Use selector to minimize re-renders -const onlyNeededData = Route.useSearch({ - select: (search) => ({ - currentPage: search.pagination.page, - pageSize: search.pagination.size, - }), -}) -``` - -## Common Next Steps - - - - - - -## Related Resources - -- [Search Params Guide](../../guide/search-params.md) - Core concepts and JSON-first approach -- [Zod Documentation](https://zod.dev/) - Schema validation library -- [MDN Date.toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString) - Date serialization reference -- [URLSearchParams Limits](https://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers) - Browser URL length limits - -**Deep Merging Libraries:** - -- [deepmerge](https://www.npmjs.com/package/deepmerge) - Lightweight, focused deep merging utility -- [Lodash merge](https://lodash.com/docs#merge) - Full-featured utility library with deep merging -- [Ramda mergeDeepRight](https://ramdajs.com/docs/#mergeDeepRight) - Functional programming approach diff --git a/docs/router/framework/react/how-to/debug-router-issues.md b/docs/router/framework/react/how-to/debug-router-issues.md deleted file mode 100644 index 63b3da61264..00000000000 --- a/docs/router/framework/react/how-to/debug-router-issues.md +++ /dev/null @@ -1,768 +0,0 @@ ---- -title: How to Debug Common Router Issues ---- - -This guide covers debugging common TanStack Router problems, from route matching failures to navigation issues and performance problems. - -## Quick Start - -Use TanStack Router DevTools for real-time debugging, add strategic console logging, and follow systematic troubleshooting patterns to identify and resolve router issues quickly. - ---- - -## Essential Debugging Tools - -### 1. TanStack Router DevTools - -Install and configure the DevTools for the best debugging experience: - -```bash -npm install @tanstack/router-devtools -``` - -```tsx -// src/App.tsx -import { TanStackRouterDevtools } from '@tanstack/router-devtools' - -function App() { - return ( -
- - {/* Only shows in development */} - -
- ) -} -``` - -**DevTools Features:** - -- **Route Tree Visualization** - See your entire route structure -- **Current Route State** - Inspect active route data, params, and search -- **Navigation History** - Track navigation events and timing -- **Route Matching** - See which routes match current URL -- **Performance Metrics** - Monitor route load times and re-renders - -### 2. Debug Mode Configuration - -Enable debug mode for detailed console logging: - -```tsx -const router = createRouter({ - routeTree, - defaultPreload: 'intent', - context: { - // your context - }, - // Enable debug mode - debug: true, -}) -``` - -### 3. Browser DevTools Setup - -**Add router to global scope for debugging:** - -```tsx -// In development only -if (import.meta.env.DEV) { - window.router = router -} - -// Console debugging commands: -// router.state - current router state -// router.navigate() - programmatic navigation -// router.history - navigation history -``` - ---- - -## Route Matching Issues - -### Problem: Route Not Found (404) - -**Symptoms:** - -- Route exists but shows 404 or "Not Found" -- Console shows route matching failures - -**Debugging Steps:** - -1. **Check Route Path Definition** - -```tsx -// ❌ Common mistake - missing leading slash -const route = createRoute({ - path: 'about', // Should be '/about' - // ... -}) - -// βœ… Correct -const route = createRoute({ - path: '/about', - // ... -}) -``` - -2. **Verify Route Tree Structure** - -```tsx -// Debug route tree in console -console.log('Route tree:', router.routeTree) -console.log('All routes:', router.flatRoutes) -``` - -3. **Check Parent Route Configuration** - -```tsx -// Ensure parent route is properly defined -const childRoute = createRoute({ - getParentRoute: () => parentRoute, // Must return correct parent - path: '/child', - // ... -}) -``` - -### Problem: Route Parameters Not Working - -**Symptoms:** - -- `useParams()` returns undefined or wrong values -- Route params not being parsed correctly - -**Debugging Steps:** - -1. **Verify Parameter Syntax** - -```tsx -// ❌ Wrong parameter syntax -path: '/users/{id}' // Should use $ - -// βœ… Correct parameter syntax -path: '/users/$userId' -``` - -2. **Check Parameter Parsing** - -```tsx -const route = createRoute({ - path: '/users/$userId', - // Add parameter validation/parsing - params: { - parse: (params) => ({ - userId: Number(params.userId), // Convert to number - }), - stringify: (params) => ({ - userId: String(params.userId), // Convert back to string - }), - }, - component: () => { - const { userId } = Route.useParams() - console.log('User ID:', userId, typeof userId) // Debug output - return
User {userId}
- }, -}) -``` - -3. **Debug Current URL and Params** - -```tsx -function DebugParams() { - const location = useLocation() - const params = Route.useParams() - - console.log('Current pathname:', location.pathname) - console.log('Parsed params:', params) - - return null // Just for debugging -} -``` - ---- - -## Navigation Issues - -### Problem: Navigation Not Working - -**Symptoms:** - -- Links don't navigate -- Programmatic navigation fails silently -- Browser URL doesn't update - -**Debugging Steps:** - -1. **Check Link Configuration** - -```tsx -// ❌ Common mistakes -About // Missing leading slash -About // Wrong prop (href instead of to) - -// βœ… Correct -About -``` - -2. **Debug Navigation Calls** - -```tsx -function NavigationDebug() { - const navigate = useNavigate() - - const handleNavigate = () => { - console.log('Attempting navigation...') - navigate({ - to: '/dashboard', - search: { tab: 'settings' }, - }) - .then(() => console.log('Navigation successful')) - .catch((err) => console.error('Navigation failed:', err)) - } - - return -} -``` - -3. **Check Router Context** - -```tsx -// Ensure component is inside RouterProvider -function ComponentWithNavigation() { - const router = useRouter() // Will throw error if outside provider - console.log('Router state:', router.state) - - return
...
-} -``` - -### Problem: Navigation Redirects Unexpectedly - -**Symptoms:** - -- Navigating to one route but ending up somewhere else -- Infinite redirect loops - -**Debugging Steps:** - -1. **Check Route Guards** - -```tsx -const route = createRoute({ - path: '/dashboard', - beforeLoad: ({ context, location }) => { - console.log('Before load - location:', location.pathname) - console.log('Auth state:', context.auth) - - if (!context.auth.isAuthenticated) { - console.log('Redirecting to login...') - throw redirect({ to: '/login' }) - } - }, - // ... -}) -``` - -2. **Debug Redirect Chains** - -```tsx -// Add to router configuration -const router = createRouter({ - routeTree, - context: { - /* ... */ - }, - // Log all navigation events - onNavigate: ({ location, type }) => { - console.log(`Navigation (${type}):`, location.pathname) - }, -}) -``` - ---- - -## Data Loading Problems - -### Problem: Route Data Not Loading - -**Symptoms:** - -- `useLoaderData()` returns undefined -- Loading states not working correctly -- Data not refreshing - -**Debugging Steps:** - -1. **Check Loader Implementation** - -```tsx -const route = createRoute({ - path: '/posts', - loader: async ({ params, context }) => { - console.log('Loader called with params:', params) - - try { - const data = await fetchPosts() - console.log('Loader data:', data) - return data - } catch (error) { - console.error('Loader error:', error) - throw error - } - }, - component: () => { - const data = Route.useLoaderData() - console.log('Component data:', data) - - return
{/* render data */}
- }, -}) -``` - -2. **Debug Loading States** - -```tsx -function DataLoadingDebug() { - const location = useLocation() - - console.log('Route status:', { - isLoading: location.isLoading, - isTransitioning: location.isTransitioning, - }) - - return null -} -``` - -3. **Check Loader Dependencies** - -```tsx -const route = createRoute({ - path: '/posts/$postId', - loader: async ({ params }) => { - // Loader will re-run when params change - console.log('Loading post:', params.postId) - return fetchPost(params.postId) - }, - // Add dependencies for explicit re-loading - loaderDeps: ({ search }) => ({ - refresh: search.refresh, - }), -}) -``` - ---- - -## Search Parameters Issues - -### Problem: Search Params Not Updating - -**Symptoms:** - -- URL search params don't update -- `useSearch()` returns stale data -- Search validation errors - -**Debugging Steps:** - -1. **Check Search Validation Schema** - -```tsx -const route = createRoute({ - path: '/search', - validateSearch: (search) => { - console.log('Raw search params:', search) - - const validated = { - q: (search.q as string) || '', - page: Number(search.page) || 1, - } - - console.log('Validated search params:', validated) - return validated - }, - component: () => { - const search = Route.useSearch() - console.log('Component search:', search) - - return
Query: {search.q}
- }, -}) -``` - -2. **Debug Search Navigation** - -```tsx -function SearchDebug() { - const navigate = useNavigate() - const currentSearch = Route.useSearch() - - const updateSearch = (newSearch: any) => { - console.log('Current search:', currentSearch) - console.log('New search:', newSearch) - - navigate({ - to: '.', - search: (prev) => { - const updated = { ...prev, ...newSearch } - console.log('Final search:', updated) - return updated - }, - }) - } - - return ( - - ) -} -``` - ---- - -## Performance Issues - -### Problem: Excessive Re-renders - -**Symptoms:** - -- Components re-rendering too often -- Performance lag during navigation -- Memory usage increasing - -**Debugging Steps:** - -1. **Use React DevTools Profiler** - -```tsx -// Wrap your app for profiling -import { Profiler } from 'react' - -function App() { - return ( - { - console.log(`${id} ${phase} took ${actualDuration}ms`) - }} - > - - - ) -} -``` - -2. **Optimize Route Subscriptions** - -```tsx -// ❌ Subscribes to all search params -function MyComponent() { - const search = Route.useSearch() - return
{search.someSpecificField}
-} - -// βœ… Subscribe only to specific field -function MyComponent() { - const someSpecificField = Route.useSearch({ - select: (search) => search.someSpecificField, - }) - return
{someSpecificField}
-} -``` - -3. **Monitor Route State Changes** - -```tsx -// Add to router configuration -const router = createRouter({ - routeTree, - context: { - /* ... */ - }, - onUpdate: (router) => { - console.log('Router state updated:', { - pathname: router.state.location.pathname, - isLoading: router.state.isLoading, - matches: router.state.matches.length, - }) - }, -}) -``` - -### Problem: Memory Leaks - -**Symptoms:** - -- Memory usage constantly increasing -- Browser becomes slow over time -- Route components not cleaning up - -**Debugging Steps:** - -1. **Check Component Cleanup** - -```tsx -function MyComponent() { - const [data, setData] = useState(null) - - useEffect(() => { - const subscription = someService.subscribe(setData) - - // βœ… Always clean up subscriptions - return () => { - subscription.unsubscribe() - } - }, []) - - return
{data}
-} -``` - -2. **Monitor Route Unmounting** - -```tsx -function DebuggableComponent() { - useEffect(() => { - console.log('Component mounted') - - return () => { - console.log('Component unmounted') - } - }, []) - - return
Content
-} -``` - ---- - -## TypeScript Issues - -### Problem: Type Errors with Router - -**Symptoms:** - -- TypeScript errors in route definitions -- Type inference not working -- Parameter types incorrect - -**Debugging Steps:** - -1. **Check Route Tree Type Registration** - -```tsx -// Ensure this declaration exists -declare module '@tanstack/react-router' { - interface Register { - router: typeof router - } -} -``` - -2. **Debug Route Type Generation** - -```bash -# Check if route types are being generated -ls src/routeTree.gen.ts - -# Regenerate route types if needed -npx @tanstack/router-cli generate -``` - -3. **Use Type Assertions for Debugging** - -```tsx -function TypeDebugComponent() { - const params = Route.useParams() - const search = Route.useSearch() - - // Add type assertions to check what TypeScript infers - console.log('Params type:', params as any) - console.log('Search type:', search as any) - - return null -} -``` - ---- - -## Systematic Debugging Process - -### 1. Information Gathering - -When debugging any router issue, start by collecting this information: - -```tsx -function RouterDebugInfo() { - const router = useRouter() - const location = useLocation() - - useEffect(() => { - console.group('πŸ› Router Debug Info') - console.log('Current pathname:', location.pathname) - console.log('Search params:', location.search) - console.log('Router state:', router.state) - console.log('Active matches:', router.state.matches) - console.log('Route tree:', router.routeTree) - console.groupEnd() - }, [location.pathname]) - - return null -} - -// Add to your app during debugging -; -``` - -### 2. Isolation Testing - -Create minimal reproduction: - -```tsx -// Minimal route for testing -const testRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/debug', - component: () => { - console.log('Test route rendered') - return
Debug Route
- }, -}) - -// Add to route tree temporarily -const routeTree = rootRoute.addChildren([ - // ... other routes - testRoute, // Add test route -]) -``` - -### 3. Step-by-Step Debugging - -1. **Verify basic setup** - Router provider, route tree structure -2. **Check route definitions** - Paths, parent routes, configuration -3. **Test navigation** - Links, programmatic navigation -4. **Validate data flow** - Loaders, search params, context -5. **Monitor performance** - Re-renders, memory usage - ---- - -## Browser Debugging Tips - -### Console Commands - -```js -// In browser console (when router is on window) - -// Current router state -router.state - -// Navigate programmatically -router.navigate({ to: '/some-path' }) - -// Get route by path -router.getRoute('/users/$userId') - -// Check if route exists -router.buildLocation({ to: '/some-path' }) - -// View all registered routes -Object.keys(router.routesById) -``` - -### Network Tab - -Monitor these requests when debugging: - -- **Route code chunks** - Check if lazy routes are loading -- **Loader data requests** - Verify API calls from loaders -- **Failed requests** - Look for 404s or failed API calls - -### React DevTools - -1. **Components Tab** - Find router components and inspect props -2. **Profiler Tab** - Identify performance bottlenecks -3. **Search for components** - Find specific route components quickly - ---- - -## Common Error Messages - -### "Route not found" - -- Check route path spelling and case sensitivity -- Verify route is added to route tree -- Ensure parent routes are properly configured - -### "Cannot read property 'useParams' of undefined" - -- Component is likely outside RouterProvider -- Route might not be properly registered -- Check if using correct Route object - -### "Invalid search params" - -- Check validateSearch schema -- Verify search param types match schema -- Look for required vs optional parameters - -### "Navigation was interrupted" - -- Usually caused by redirect in beforeLoad -- Check for redirect loops -- Verify authentication logic - ---- - -## Performance Monitoring - -### Enable Performance Tracking - -```tsx -const router = createRouter({ - routeTree, - context: { - /* ... */ - }, - onUpdate: (router) => { - performance.mark('router-update') - }, - onLoad: (router) => { - performance.mark('router-load') - performance.measure('router-load-time', 'router-update', 'router-load') - }, -}) -``` - -### Monitor Route Loading Times - -```tsx -const route = createRoute({ - path: '/slow-route', - loader: async () => { - const start = performance.now() - const data = await fetchData() - const end = performance.now() - - console.log(`Loader took ${end - start}ms`) - return data - }, -}) -``` - ---- - -## Common Next Steps - -After debugging router issues, you might want to: - -- [How to Set Up Testing](../setup-testing.md) - Add tests to prevent regressions -- [How to Deploy to Production](../deploy-to-production.md) - Ensure issues don't occur in production - - - -## Related Resources - -- [TanStack Router DevTools](https://github.com/TanStack/router/tree/main/packages/router-devtools) - Official debugging tools -- [React DevTools](https://react.dev/learn/react-developer-tools) - React-specific debugging -- [Router Core Documentation](../guide/router-overview.md) - Understanding router internals diff --git a/docs/router/framework/react/how-to/deploy-to-production.md b/docs/router/framework/react/how-to/deploy-to-production.md deleted file mode 100644 index 7be04ff682e..00000000000 --- a/docs/router/framework/react/how-to/deploy-to-production.md +++ /dev/null @@ -1,448 +0,0 @@ ---- -title: How to Deploy TanStack Router to Production ---- - -This guide covers deploying TanStack Router applications to popular hosting platforms. - -## Quick Start - -Single Page Applications (SPAs) need special server configuration to handle client-side routing. Configure your hosting platform to serve `index.html` for all routes, allowing TanStack Router to handle navigation. - ---- - -## Netlify Deployment - -### 1. Create `_redirects` File - -Create a `public/_redirects` file (or `_redirects` in your build output): - -``` -/* /index.html 200 -``` - -### 2. Alternative: `netlify.toml` - -Create a `netlify.toml` file in your project root: - -```toml -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 - -[build] - publish = "dist" - command = "npm run build" -``` - -### 3. For TanStack Start (SSR) - -```toml -[build] - publish = ".output/public" - command = "npm run build" - -[functions] - directory = ".output/server" - -[[redirects]] - from = "/api/*" - to = "/.netlify/functions/server" - status = 200 - -[[redirects]] - from = "/*" - to = "/index.html" - status = 200 -``` - ---- - -## Cloudflare Pages - -### 1. Create `_redirects` File - -Create a `public/_redirects` file: - -``` -/* /index.html 200 -``` - -### 2. Alternative: `_routes.json` - -Create a `public/_routes.json` file for more control: - -```json -{ - "version": 1, - "include": ["/*"], - "exclude": ["/api/*"] -} -``` - -### 3. For TanStack Start (SSR) - -Create `functions/_middleware.ts` for SSR support: - -```ts -export const onRequest: PagesFunction = async (context) => { - // Handle SSR requests - return await handleSSR(context) -} -``` - -### 4. Deploy via Git - -1. Connect your GitHub repository to Cloudflare Pages -2. Set build settings: - - **Build command:** `npm run build` - - **Build output directory:** `dist` - - **Root directory:** (leave empty) - -### 5. Deploy via Wrangler CLI - -```bash -# Install Wrangler -npm install -g wrangler - -# Deploy -wrangler pages publish dist --project-name=my-app -``` - ---- - -## Vercel Deployment - -### 1. Create `vercel.json` - -Create a `vercel.json` file in your project root: - -```json -{ - "rewrites": [ - { - "source": "/(.*)", - "destination": "/index.html" - } - ] -} -``` - -### 2. For TanStack Start (SSR) Applications - -If using TanStack Start with SSR, use this configuration instead: - -```json -{ - "functions": { - "app/server.ts": { - "runtime": "nodejs18.x" - } - }, - "routes": [ - { - "src": "/(.*)", - "dest": "/api/server" - } - ] -} -``` - -### 3. Build Configuration - -Ensure your `package.json` has the correct build script: - -```json -{ - "scripts": { - "build": "vite build", - "preview": "vite preview" - } -} -``` - -### 4. Deploy - -```bash -# Install Vercel CLI -npm i -g vercel - -# Deploy -vercel -``` - ---- - -## GitHub Pages - -### 1. Create `404.html` - -GitHub Pages requires a `404.html` file that duplicates `index.html`: - -```bash -# After building -cp dist/index.html dist/404.html -``` - -### 2. Update `vite.config.js` - -```js -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import { tanstackRouter } from '@tanstack/router-plugin/vite' - -export default defineConfig({ - base: '/your-repo-name/', // Replace with your repository name - plugins: [ - tanstackRouter({ - target: 'react', - autoCodeSplitting: true, - }), - react(), - ], - build: { - outDir: 'dist', - }, -}) -``` - -### 3. GitHub Actions Workflow - -Create `.github/workflows/deploy.yml`: - -```yaml -name: Deploy to GitHub Pages - -on: - push: - branches: [main] - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Setup Node.js - uses: actions/setup-node@v3 - with: - node-version: '18' - cache: 'npm' - - - name: Install dependencies - run: npm ci - - - name: Build - run: npm run build - - - name: Create 404.html - run: cp dist/index.html dist/404.html - - - name: Deploy to GitHub Pages - uses: peaceiris/actions-gh-pages@v3 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./dist -``` - ---- - -## Firebase Hosting - -### 1. Create `firebase.json` - -```json -{ - "hosting": { - "public": "dist", - "ignore": ["firebase.json", "**/.*", "**/node_modules/**"], - "rewrites": [ - { - "source": "**", - "destination": "/index.html" - } - ] - } -} -``` - -### 2. Deploy - -```bash -# Install Firebase CLI -npm install -g firebase-tools - -# Login and initialize -firebase login -firebase init hosting - -# Build and deploy -npm run build -firebase deploy -``` - ---- - -## Apache Server - -Create a `.htaccess` file in your build output directory: - -```apache - - RewriteEngine On - RewriteBase / - RewriteRule ^index\.html$ - [L] - RewriteCond %{REQUEST_FILENAME} !-f - RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule . /index.html [L] - -``` - ---- - -## Nginx - -Add this configuration to your Nginx server block: - -```nginx -server { - listen 80; - server_name your-domain.com; - root /path/to/your/dist; - index index.html; - - location / { - try_files $uri $uri/ /index.html; - } - - # Optional: Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - } -} -``` - ---- - -## Docker Deployment - -### 1. Create `Dockerfile` - -```dockerfile -# Build stage -FROM node:18-alpine AS build -WORKDIR /app -COPY package*.json ./ -RUN npm ci -COPY . . -RUN npm run build - -# Production stage -FROM nginx:alpine -COPY --from=build /app/dist /usr/share/nginx/html -COPY nginx.conf /etc/nginx/conf.d/default.conf -EXPOSE 80 -CMD ["nginx", "-g", "daemon off;"] -``` - -### 2. Create `nginx.conf` - -```nginx -server { - listen 80; - server_name localhost; - root /usr/share/nginx/html; - index index.html; - - location / { - try_files $uri $uri/ /index.html; - } -} -``` - -### 3. Build and Run - -```bash -docker build -t my-tanstack-app . -docker run -p 80:80 my-tanstack-app -``` - ---- - -## Production Checklist - -Before deploying, ensure you have: - -- [ ] Created hosting platform configuration file -- [ ] Set correct base path if deploying to subdirectory -- [ ] Configured environment variables with `VITE_` prefix -- [ ] Tested all routes by direct URL access -- [ ] Verified static assets load correctly - ---- - -## Common Problems - -### 404 Errors on Page Refresh - -**Problem:** Routes work when navigating within the app, but refreshing the page shows 404. - -**Cause:** The server looks for files like `/about/index.html` which don't exist in SPAs. - -**Solution:** Add the configuration files shown above for your hosting platform. - -### App Works Locally But Breaks When Deployed - -**Problem:** App works in development but shows errors in production. - -**Solutions:** - -- **Subdirectory deployment:** Configure base path in `vite.config.js`: - ```js - export default defineConfig({ - base: '/my-app/', // Match your deployment path - }) - ``` -- **Build output mismatch:** Ensure build directory matches hosting config: - ```js - export default defineConfig({ - build: { - outDir: 'dist', // Must match hosting platform setting - }, - }) - ``` -- **Environment variables:** Prefix with `VITE_` and rebuild: - ```bash - # .env - VITE_API_URL=https://api.example.com - ``` - -### Assets Not Loading (CSS/JS 404s) - -**Problem:** App loads but styling is broken or JavaScript fails to load. - -**Solutions:** - -- Check build output directory in hosting configuration -- Verify public path configuration in Vite -- Ensure static file serving is properly configured - ---- - -## Common Next Steps - -After deployment, you might want to: - -- [How to Set Up Basic Authentication](../setup-authentication.md) - Secure your application with auth -- [Migrate from React Router v7](../migrate-from-react-router.md) - Complete migration guide if you're coming from React Router -- [How to Set Up Server-Side Rendering (SSR)](../setup-ssr.md) - - - -## Related Resources - -- [Deployment Examples](https://github.com/TanStack/router/tree/main/examples) - Official examples diff --git a/docs/router/framework/react/how-to/drafts/README.md b/docs/router/framework/react/how-to/drafts/README.md deleted file mode 100644 index 5c00005ad15..00000000000 --- a/docs/router/framework/react/how-to/drafts/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# How-To Guide Drafts - -This directory contains draft content for upcoming how-to guides in the Progressive Search Parameters Series. Each draft file contains: - -- **Metadata** about the final destination and dependencies -- **Staged content** extracted from other guides to avoid scope creep -- **Implementation notes** for future development -- **Cross-reference planning** for proper guide linking - -## File Naming Convention - -- `{guide-name}.draft.md` - Draft content for upcoming guides -- Clear metadata header with destination path and status - -## Current Drafts - -### ⏳ Ready for Implementation (Substantial Content Available) - -1. **`validate-search-params.draft.md`** - - **Destination:** `validate-search-params.md` - - **Position:** Intermediate Level - Guide #3 - - **Content:** Schema validation, type safety, error handling - -2. **`build-search-filtering-systems.draft.md`** - - **Destination:** `build-search-filtering-systems.md` - - **Position:** Specialized Use Cases - Guide #9 - - **Content:** Complete filtering UIs, search results, pagination - -3. **`search-params-in-forms.draft.md`** - - **Destination:** `search-params-in-forms.md` - - **Position:** Specialized Use Cases - Guide #10 - - **Content:** Form synchronization, state management - -4. **`optimize-search-param-performance.draft.md`** - - **Destination:** `optimize-search-param-performance.md` - - **Position:** Advanced Level - Guide #7 - - **Content:** Performance optimization, memoization patterns - -## Implementation Workflow - -When implementing a guide from a draft: - -1. **Copy the staged content** to the final destination -2. **Expand with additional examples** specific to the guide's focus -3. **Add comprehensive troubleshooting** for the domain -4. **Update cross-references** in related guides -5. **Update the main README** to mark guide as completed -6. **Delete the draft file** once fully implemented - -## Benefits of This System - -- **Prevents scope creep** in individual guides -- **Preserves valuable content** during refactoring -- **Enables focused guide development** -- **Maintains clear progression** through the series -- **Facilitates parallel development** of multiple guides - -## Content Sources - -Most staged content originates from: - -- `navigate-with-search-params.md` - Content moved to maintain focus -- Implementation planning sessions -- User feedback and common questions diff --git a/docs/router/framework/react/how-to/drafts/build-search-filtering-systems.draft.md b/docs/router/framework/react/how-to/drafts/build-search-filtering-systems.draft.md deleted file mode 100644 index 7ce8d807054..00000000000 --- a/docs/router/framework/react/how-to/drafts/build-search-filtering-systems.draft.md +++ /dev/null @@ -1,192 +0,0 @@ -# DRAFT: Build Search-Based Filtering Systems - -**Final Destination:** `docs/router/framework/react/how-to/build-search-filtering-systems.md` -**Progressive Series Position:** Specialized Use Cases - Guide #9 -**Depends On:** `setup-basic-search-params.md`, `navigate-with-search-params.md`, `validate-search-params.md` -**Status:** Ready for implementation - comprehensive UI patterns available - ---- - -## Content Staged from navigate-with-search-params.md - -### Search Result Navigation - -Handle search result navigation with query preservation: - -```tsx -import { Link, useSearch } from '@tanstack/react-router' - -function SearchResults() { - const search = useSearch({ from: '/search' }) - - return ( -
- {/* Preserve search query, change view */} - - - {/* Pagination with query preservation */} -
- {search.page > 1 && ( - ({ ...prev, page: prev.page - 1 })}> - Previous - - )} - - ({ ...prev, page: (prev.page || 1) + 1 })}> - Next - -
- - {/* Related searches */} -
- Related searches: - {['laptops gaming', 'laptops business', 'laptops student'].map( - (suggestion) => ( - ({ ...prev, query: suggestion, page: 1 })} - > - {suggestion} - - ), - )} -
-
- ) -} -``` - -### Filter Navigation - -Build filtering interfaces with search parameter navigation: - -```tsx -import { Link, useSearch } from '@tanstack/react-router' - -const categories = ['electronics', 'clothing', 'books', 'home'] -const sortOptions = [ - { value: 'relevance', label: 'Relevance' }, - { value: 'price-asc', label: 'Price: Low to High' }, - { value: 'price-desc', label: 'Price: High to Low' }, - { value: 'rating', label: 'Customer Rating' }, -] - -function FilterNavigation() { - const search = useSearch({ from: '/products' }) - - return ( - - ) -} -``` - -### Programmatic Search Controls - -Navigate programmatically with search parameter updates: - -```tsx -import { useNavigate } from '@tanstack/react-router' - -function SearchControls() { - const navigate = useNavigate() - - const handleSortChange = (sortBy: string) => { - navigate({ - search: (prev) => ({ ...prev, sort: sortBy, page: 1 }), - }) - } - - const handleClearFilters = () => { - navigate({ - search: (prev) => { - const { category, minPrice, maxPrice, ...rest } = prev - return rest - }, - }) - } - - return ( -
- - - -
- ) -} -``` - ---- - -## Implementation Notes - -### Additional Content Needed: - -- [ ] Complete e-commerce filtering example -- [ ] Advanced filter combinations (price ranges, multi-select) -- [ ] Filter state persistence and sharing -- [ ] Search result highlighting and sorting -- [ ] Infinite scroll pagination patterns -- [ ] Filter URL state management best practices -- [ ] Accessibility considerations for filter UIs -- [ ] Mobile-responsive filter patterns - -### Cross-References to Add: - -- Link to `setup-basic-search-params.md` for foundation -- Link to `navigate-with-search-params.md` for navigation patterns -- Link to `validate-search-params.md` for filter validation -- Forward link to `search-params-with-data-loading.md` for data integration - -### README Update Required: - -- [ ] Mark guide as completed in progressive series -- [ ] Uncomment "Common Next Steps" in related guides diff --git a/docs/router/framework/react/how-to/drafts/optimize-search-param-performance.draft.md b/docs/router/framework/react/how-to/drafts/optimize-search-param-performance.draft.md deleted file mode 100644 index abe4d1afa7b..00000000000 --- a/docs/router/framework/react/how-to/drafts/optimize-search-param-performance.draft.md +++ /dev/null @@ -1,90 +0,0 @@ -# DRAFT: Optimize Search Parameter Performance - -**Final Destination:** `docs/router/framework/react/how-to/optimize-search-param-performance.md` -**Progressive Series Position:** Advanced Level (Power User Patterns) - Guide #7 -**Depends On:** `setup-basic-search-params.md`, `navigate-with-search-params.md` -**Status:** Ready for implementation - performance patterns available - ---- - -## Content Staged from navigate-with-search-params.md - -### Performance Issues with Functional Updates - -**Problem:** Complex functional updates cause unnecessary re-renders. - -```tsx -// ❌ Wrong - complex computation in render - { - // Expensive computation on every render - const result = expensiveCalculation(prev) - return { ...prev, computed: result } -}}> - Update - - -// βœ… Correct - memoize or use callback -const updateSearch = useCallback((prev) => { - const result = expensiveCalculation(prev) - return { ...prev, computed: result } -}, []) - -Update -``` - -### Navigation During Render - -**Problem:** Calling navigate during component render causes infinite loops. - -```tsx -function ProblematicComponent() { - const navigate = useNavigate() - - // ❌ Wrong - navigate during render - if (someCondition) { - navigate({ search: { redirect: true } }) - } - - return
Content
-} - -function FixedComponent() { - const navigate = useNavigate() - - // βœ… Correct - navigate in effect - useEffect(() => { - if (someCondition) { - navigate({ search: { redirect: true } }) - } - }, [someCondition, navigate]) - - return
Content
-} -``` - ---- - -## Implementation Notes - -### Additional Content Needed: - -- [ ] Search parameter selectors to prevent unnecessary re-renders -- [ ] Debouncing search input updates -- [ ] Memoization strategies for expensive search computations -- [ ] React.memo usage with search parameters -- [ ] useMemo patterns for derived search state -- [ ] Search parameter batching techniques -- [ ] Performance monitoring and profiling search params -- [ ] Bundle size optimization strategies - -### Cross-References to Add: - -- Link to `setup-basic-search-params.md` for foundation -- Link to `navigate-with-search-params.md` for navigation patterns -- Link to `search-params-in-forms.md` for debouncing forms -- Forward link to `debug-search-param-issues.md` for debugging performance - -### README Update Required: - -- [ ] Mark guide as completed in progressive series -- [ ] Uncomment "Common Next Steps" in related guides diff --git a/docs/router/framework/react/how-to/drafts/search-params-in-forms.draft.md b/docs/router/framework/react/how-to/drafts/search-params-in-forms.draft.md deleted file mode 100644 index 72315c53bb0..00000000000 --- a/docs/router/framework/react/how-to/drafts/search-params-in-forms.draft.md +++ /dev/null @@ -1,157 +0,0 @@ -# DRAFT: Handle Search Parameters in Forms - -**Final Destination:** `docs/router/framework/react/how-to/search-params-in-forms.md` -**Progressive Series Position:** Specialized Use Cases - Guide #10 -**Depends On:** `setup-basic-search-params.md`, `navigate-with-search-params.md`, `validate-search-params.md` -**Status:** Ready for implementation - form synchronization patterns available - ---- - -## Content Staged from navigate-with-search-params.md - -### Navigation with State Synchronization - -Keep component state in sync with URL search parameters: - -```tsx -import { useState, useEffect } from 'react' -import { useNavigate, useSearch } from '@tanstack/react-router' - -function SynchronizedForm() { - const navigate = useNavigate() - const search = useSearch({ from: '/products' }) - - // Local state synced with URL - const [localFilters, setLocalFilters] = useState({ - minPrice: search.minPrice || 0, - maxPrice: search.maxPrice || 1000, - inStock: search.inStock || false, - }) - - // Update local state when URL changes - useEffect(() => { - setLocalFilters({ - minPrice: search.minPrice || 0, - maxPrice: search.maxPrice || 1000, - inStock: search.inStock || false, - }) - }, [search.minPrice, search.maxPrice, search.inStock]) - - const applyFilters = () => { - navigate({ - search: (prev) => ({ - ...prev, - ...localFilters, - page: 1, // Reset pagination - }), - }) - } - - const resetFilters = () => { - const defaultFilters = { minPrice: 0, maxPrice: 1000, inStock: false } - setLocalFilters(defaultFilters) - - navigate({ - search: (prev) => { - const { minPrice, maxPrice, inStock, ...rest } = prev - return rest - }, - }) - } - - return ( -
- - - - - - - - -
- ) -} -``` - -### Form with Search Parameter Validation - -```tsx -const handleFormSubmit = (formData: FormData) => { - const query = formData.get('query') as string - const page = parseInt(formData.get('page') as string) || 1 - - safeNavigate({ query, page }) -} - -return ( - - - - - -) -``` - ---- - -## Implementation Notes - -### Additional Content Needed: - -- [ ] Uncontrolled form patterns with search params -- [ ] Controlled form patterns with real-time updates -- [ ] Form library integration (React Hook Form, Formik) -- [ ] Debounced form updates to URL -- [ ] Form reset and default value handling -- [ ] Multi-step form with URL state -- [ ] File upload forms with search state -- [ ] Form validation error handling with URL feedback - -### Cross-References to Add: - -- Link to `setup-basic-search-params.md` for foundation -- Link to `navigate-with-search-params.md` for navigation patterns -- Link to `validate-search-params.md` for form validation -- Forward link to `optimize-search-param-performance.md` for debouncing - -### README Update Required: - -- [ ] Mark guide as completed in progressive series -- [ ] Uncomment "Common Next Steps" in related guides diff --git a/docs/router/framework/react/how-to/install.md b/docs/router/framework/react/how-to/install.md deleted file mode 100644 index f325e0b505a..00000000000 --- a/docs/router/framework/react/how-to/install.md +++ /dev/null @@ -1,47 +0,0 @@ ---- -title: How to Install TanStack Router ---- - -## Prerequisites - -- React 18.x.x or 19.x.x -- ReactDOM 18.x.x or 19.x.x with `createRoot` support -- TypeScript 5.3.x or higher (recommended) - -## Installation Steps - -1. **Install the package** - - Choose your package manager: - - ```sh - npm install @tanstack/react-router - ``` - - ```sh - pnpm add @tanstack/react-router - ``` - - ```sh - yarn add @tanstack/react-router - ``` - - ```sh - bun add @tanstack/react-router - ``` - - ```sh - deno add npm:@tanstack/react-router - ``` - -2. **Verify installation** - - Check that the package appears in your `package.json`: - - ```json - { - "dependencies": { - "@tanstack/react-router": "^x.x.x" - } - } - ``` diff --git a/docs/router/framework/react/how-to/integrate-chakra-ui.md b/docs/router/framework/react/how-to/integrate-chakra-ui.md deleted file mode 100644 index 91908788831..00000000000 --- a/docs/router/framework/react/how-to/integrate-chakra-ui.md +++ /dev/null @@ -1,823 +0,0 @@ ---- -title: How to Integrate TanStack Router with Chakra UI ---- - -This guide covers setting up Chakra UI with TanStack Router, including theme configuration and creating responsive, accessible components. - -## Quick Start - -**Time Required:** 30-40 minutes -**Difficulty:** Beginner to Intermediate -**Prerequisites:** Existing TanStack Router project - -### What You'll Accomplish - -- Install and configure Chakra UI with TanStack Router -- Set up theme provider and custom theming -- Create type-safe router-compatible Chakra components -- Implement responsive navigation patterns -- Build accessible UI components with router integration - ---- - -## Installation and Setup - -### Step 1: Install Chakra UI Dependencies - -```bash -npm install @chakra-ui/react @emotion/react @emotion/styled framer-motion -``` - -### Step 2: Set Up Chakra Provider - -```tsx -// src/components/chakra-provider.tsx -import { ChakraProvider, extendTheme, type ThemeConfig } from '@chakra-ui/react' -import { ReactNode } from 'react' - -// Extend the theme with custom colors and configurations -const config: ThemeConfig = { - initialColorMode: 'light', - useSystemColorMode: true, -} - -const theme = extendTheme({ - config, - colors: { - brand: { - 50: '#e3f2fd', - 100: '#bbdefb', - 200: '#90caf9', - 300: '#64b5f6', - 400: '#42a5f5', - 500: '#2196f3', - 600: '#1e88e5', - 700: '#1976d2', - 800: '#1565c0', - 900: '#0d47a1', - }, - }, - fonts: { - heading: 'Inter, sans-serif', - body: 'Inter, sans-serif', - }, - components: { - Button: { - defaultProps: { - colorScheme: 'brand', - }, - }, - Link: { - baseStyle: { - _hover: { - textDecoration: 'none', - }, - }, - }, - }, -}) - -interface ChakraAppProviderProps { - children: ReactNode -} - -export function ChakraAppProvider({ children }: ChakraAppProviderProps) { - return {children} -} -``` - -### Step 3: Update Root Route - -```tsx -// src/routes/__root.tsx -import { createRootRoute, Outlet } from '@tanstack/react-router' -import { TanStackRouterDevtools } from '@tanstack/router-devtools' -import { ChakraAppProvider } from '@/components/chakra-provider' - -export const Route = createRootRoute({ - component: () => ( - - - - - ), -}) -``` - ---- - -## Creating Router-Compatible Components - -### Step 1: Create Router-Compatible Chakra Components - -```tsx -// src/components/ui/chakra-router-link.tsx -import { createLink } from '@tanstack/react-router' -import { Link as ChakraLink, Button, IconButton } from '@chakra-ui/react' -import { forwardRef } from 'react' - -// Router-compatible Chakra Link -export const RouterLink = createLink( - forwardRef((props, ref) => { - return - }), -) - -// Router-compatible Chakra Button -export const RouterButton = createLink( - forwardRef((props, ref) => { - return - - - - ) -} -``` - ---- - -## Common Problems - -### Theme Provider Issues - -**Problem:** Chakra theme not applying correctly across routes. - -**Solution:** Ensure ChakraProvider wraps the entire app at the root level: - -```tsx -// ❌ Don't put provider inside individual routes -export const Route = createFileRoute('/some-route')({ - component: () => ( - - - - ), -}) - -// βœ… Put provider at root level -export const Route = createRootRoute({ - component: () => ( - - - - ), -}) -``` - -### TypeScript Errors with Router Integration - -**Problem:** TypeScript errors when using Chakra components with TanStack Router. - -**Solution:** Use proper typing with `createLink`: - -```tsx -import { createLink } from '@tanstack/react-router' -import { Button, type ButtonProps } from '@chakra-ui/react' -import { forwardRef } from 'react' - -// Properly typed router button -export const RouterButton = createLink( - forwardRef((props, ref) => { - return - - {/* Overlay */} - - {isOpen && ( - setIsOpen(false)} - /> - )} - - - {/* Menu */} - -
- - {items.map((item) => ( - - setIsOpen(false)} - > - {item.icon} - {item.label} - - - ))} - -
-
- - ) -} -``` - -### Step 3: Create Floating Action Button with Animations - -```tsx -// src/components/navigation/animated-fab.tsx -import { Link } from '@tanstack/react-router' -import { motion } from 'framer-motion' -import { Plus } from 'lucide-react' - -interface AnimatedFabProps { - to: string - label?: string - icon?: React.ReactNode - className?: string -} - -export function AnimatedFab({ - to, - label = 'Add', - icon = , - className = '', -}: AnimatedFabProps) { - return ( - - - - {icon} - - {label} - - - ) -} -``` - ---- - -## Advanced Animation Patterns - -### Step 1: Shared Element Transitions - -```tsx -// src/components/animations/shared-element.tsx -import { motion } from 'framer-motion' -import { ReactNode } from 'react' - -interface SharedElementProps { - layoutId: string - children: ReactNode - className?: string -} - -export function SharedElement({ - layoutId, - children, - className, -}: SharedElementProps) { - return ( - - {children} - - ) -} - -// Usage in post list -function PostCard({ post }: { post: Post }) { - return ( - - -
-

{post.title}

-

{post.excerpt}

-
-
- - ) -} - -// Usage in post detail -function PostDetail({ post }: { post: Post }) { - return ( - -
-

{post.title}

-
{post.content}
-
-
- ) -} -``` - -### Step 2: Route-Based Animation Variants - -```tsx -// src/components/animations/route-variants.tsx -import { motion } from 'framer-motion' -import { useRouter } from '@tanstack/react-router' -import { ReactNode } from 'react' - -interface RouteVariantsProps { - children: ReactNode -} - -export function RouteVariants({ children }: RouteVariantsProps) { - const router = useRouter() - const currentPath = router.state.location.pathname - - // Different animations based on route depth - const getVariants = (path: string) => { - const depth = path.split('/').length - 1 - - if (depth === 1) { - // Top-level routes slide from right - return { - initial: { opacity: 0, x: 100 }, - in: { opacity: 1, x: 0 }, - out: { opacity: 0, x: -100 }, - } - } else if (depth === 2) { - // Sub-routes slide up - return { - initial: { opacity: 0, y: 50 }, - in: { opacity: 1, y: 0 }, - out: { opacity: 0, y: -50 }, - } - } else { - // Deep routes fade - return { - initial: { opacity: 0 }, - in: { opacity: 1 }, - out: { opacity: 0 }, - } - } - } - - return ( - - {children} - - ) -} -``` - -### Step 3: Loading Animations - -```tsx -// src/components/animations/loading-animation.tsx -import { motion } from 'framer-motion' - -export function LoadingAnimation() { - return ( -
- - {[0, 1, 2].map((index) => ( - - ))} - -
- ) -} - -// Usage in routes with loading states -export const Route = createFileRoute('/posts/$postId')({ - component: PostPage, - pendingComponent: LoadingAnimation, -}) -``` - ---- - -## Complete Example - -### App with Full Animation Integration - -```tsx -// src/routes/posts/index.tsx -import { createFileRoute } from '@tanstack/react-router' -import { motion } from 'framer-motion' -import { AnimatedRoute } from '@/components/animated-route' -import { AnimatedTabs } from '@/components/navigation/animated-tabs' -import { AnimatedFab } from '@/components/navigation/animated-fab' -import { SharedElement } from '@/components/animations/shared-element' - -export const Route = createFileRoute('/posts/')({ - component: PostsPage, -}) - -const tabItems = [ - { to: '/posts', label: 'All Posts', exact: true }, - { to: '/posts/published', label: 'Published' }, - { to: '/posts/drafts', label: 'Drafts' }, -] - -function PostsPage() { - const posts = [ - { id: '1', title: 'First Post', excerpt: 'This is the first post' }, - { id: '2', title: 'Second Post', excerpt: 'This is the second post' }, - ] - - return ( - -
- {/* Animated header */} - -

Posts

- -
- - {/* Animated post grid */} - - {posts.map((post, index) => ( - - -
-

{post.title}

-

{post.excerpt}

-
-
-
- ))} -
- - {/* Floating action button */} - -
-
- ) -} -``` - ---- - -## Common Problems - -### Animations Not Triggering - -**Problem:** Route animations don't work or appear choppy. - -**Solutions:** - -1. **Ensure proper key for AnimatePresence:** - - ```tsx - - - - - - ``` - -2. **Use layout animations correctly:** - - ```tsx - // ❌ This might cause layout shifts - - - // βœ… Use layout for changing layouts - - ``` - -### Performance Issues - -**Problem:** Animations cause performance problems or jank. - -**Solutions:** - -1. **Prefer transform and opacity animations:** - - ```tsx - // βœ… GPU-accelerated properties - const variants = { - initial: { opacity: 0, scale: 0.95 }, - in: { opacity: 1, scale: 1 }, - } - - // ❌ Avoid animating layout properties - const badVariants = { - initial: { width: 0, height: 0 }, - in: { width: 'auto', height: 'auto' }, - } - ``` - -2. **Use will-change CSS property sparingly:** - ```tsx - - ``` - -### Layout Shift Issues - -**Problem:** Shared element transitions cause layout shifts. - -**Solution:** Use layout animations and proper positioning: - -```tsx - - {children} - -``` - ---- - -## Production Checklist - -Before deploying your animated TanStack Router app: - -### Performance - -- [ ] Animations use GPU-accelerated properties (transform, opacity) -- [ ] No unnecessary will-change CSS properties -- [ ] Complex animations are conditional on user preferences -- [ ] Frame rate stays above 60fps on target devices - -### User Experience - -- [ ] Animations respect user's motion preferences -- [ ] Loading states have appropriate animations -- [ ] Navigation feels responsive and smooth -- [ ] Animations enhance rather than distract from content - -### Accessibility - -- [ ] Respect prefers-reduced-motion media query -- [ ] Animations don't interfere with screen readers -- [ ] Focus management works during transitions -- [ ] Essential content isn't hidden behind animations - -### Technical - -- [ ] Bundle size impact acceptable -- [ ] No animation-related console errors -- [ ] Smooth transitions on slower devices -- [ ] Proper cleanup of animation effects - ---- - -## Related Resources - -- [Framer Motion Documentation](https://www.framer.com/motion/) - Complete animation library guide -- [React Transition Group Migration](https://www.framer.com/motion/migrate-from-react-transition-group/) - Migration guide from other animation libraries -- [Animation Performance](https://web.dev/animations-guide/) - Web performance guide for animations -- [TanStack Router Route Transitions](../guide/route-transitions) - Official guide for route animations diff --git a/docs/router/framework/react/how-to/integrate-material-ui.md b/docs/router/framework/react/how-to/integrate-material-ui.md deleted file mode 100644 index 106b80f9913..00000000000 --- a/docs/router/framework/react/how-to/integrate-material-ui.md +++ /dev/null @@ -1,717 +0,0 @@ ---- -title: How to Integrate TanStack Router with Material-UI (MUI) ---- - -This guide covers setting up Material-UI with TanStack Router, including proper TypeScript integration and component composition patterns. - -## Quick Start - -**Time Required:** 45-60 minutes -**Difficulty:** Intermediate -**Prerequisites:** Existing TanStack Router project - -### What You'll Accomplish - -- Install and configure Material-UI with TanStack Router -- Set up proper theme provider integration -- Create type-safe router-compatible MUI components -- Implement navigation with active state indicators -- Resolve common TypeScript and styling issues - ---- - -## Installation and Setup - -### Step 1: Install Material-UI Dependencies - -```bash -npm install @mui/material @emotion/react @emotion/styled @mui/icons-material -``` - -**Optional: Add date picker support** - -```bash -npm install @mui/x-date-pickers dayjs -``` - -### Step 2: Set Up Theme Provider - -Create a theme provider that works with TanStack Router: - -```tsx -// src/components/theme-provider.tsx -import { ThemeProvider, createTheme } from '@mui/material/styles' -import CssBaseline from '@mui/material/CssBaseline' -import { ReactNode } from 'react' - -const theme = createTheme({ - palette: { - mode: 'light', - primary: { - main: '#1976d2', - }, - secondary: { - main: '#dc004e', - }, - }, - typography: { - fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', - }, - components: { - // Customize components for router integration - MuiButton: { - styleOverrides: { - root: { - textTransform: 'none', // More modern button styling - }, - }, - }, - MuiLink: { - styleOverrides: { - root: { - textDecoration: 'none', - '&:hover': { - textDecoration: 'underline', - }, - }, - }, - }, - }, -}) - -interface MuiThemeProviderProps { - children: ReactNode -} - -export function MuiThemeProvider({ children }: MuiThemeProviderProps) { - return ( - - - {children} - - ) -} -``` - -### Step 3: Update Root Route - -Wrap your application with the MUI theme provider: - -```tsx -// src/routes/__root.tsx -import { createRootRoute, Outlet } from '@tanstack/react-router' -import { TanStackRouterDevtools } from '@tanstack/router-devtools' -import { MuiThemeProvider } from '@/components/theme-provider' - -export const Route = createRootRoute({ - component: () => ( - - - - - ), -}) -``` - ---- - -## Creating Router-Compatible MUI Components - -### Step 1: Create Typed MUI Link Component - -MUI Link components require special handling for TanStack Router's type system: - -```tsx -// src/components/ui/mui-router-link.tsx -import { createLink } from '@tanstack/react-router' -import { Link as MuiLink, type LinkProps } from '@mui/material/Link' -import { forwardRef } from 'react' - -// Create a router-compatible MUI Link with full type safety -export const RouterLink = createLink( - forwardRef((props, ref) => { - return - }), -) -``` - -### Step 2: Create Typed MUI Button Component - -```tsx -// src/components/ui/mui-router-button.tsx -import { createLink } from '@tanstack/react-router' -import { Button, type ButtonProps } from '@mui/material/Button' -import { forwardRef } from 'react' - -// Create a router-compatible MUI Button -export const RouterButton = createLink( - forwardRef((props, ref) => { - return } - title="Navigation Menu" - description="Navigate through your posts" - > -
-

This sheet animates correctly with TanStack Router!

- - Create New Post - -
- - - ) -} -``` - ---- - -## Common Problems - -### Animation Components Not Working - -**Problem:** Sheet, Dialog, or other animated components don't animate properly. - -**Solutions:** - -1. **Ensure proper portal setup:** - - ```tsx - // Add to your index.html or root component -
- ``` - -2. **Check CSS imports order:** - - ```css - /* Make sure this comes before your custom styles */ - @import 'tailwindcss/base'; - @import 'tailwindcss/components'; - @import 'tailwindcss/utilities'; - ``` - -3. **Use controlled components for complex animations:** - - ```tsx - const [open, setOpen] = useState(false) - - // Controlled instead of uncontrolled - - ``` - -### TypeScript Errors with Router Integration - -**Problem:** TypeScript errors when using Shadcn/ui components with TanStack Router. - -**Solution:** Use `createLink` for proper typing: - -```tsx -import { createLink } from '@tanstack/react-router' -import { Button } from '@/components/ui/button' - -// This provides full type safety -export const RouterButton = createLink(Button) -``` - -### Styling Conflicts - -**Problem:** Shadcn/ui styles conflict with router or custom styles. - -**Solutions:** - -1. **Use CSS layers:** - - ```css - @layer base, components, utilities; - - @layer base { - /* Shadcn/ui base styles */ - } - - @layer components { - /* Your component styles */ - } - ``` - -2. **Increase specificity for router-specific styles:** - ```tsx - - ``` - -### Dark Mode Integration - -**Problem:** Dark mode doesn't work properly with route changes. - -**Solution:** Set up theme provider correctly: - -```tsx -// src/components/theme-provider.tsx -import { createContext, useContext, useEffect, useState } from 'react' - -type Theme = 'dark' | 'light' | 'system' - -interface ThemeProviderProps { - children: React.ReactNode - defaultTheme?: Theme - storageKey?: string -} - -const ThemeProviderContext = createContext<{ - theme: Theme - setTheme: (theme: Theme) => void -}>({ - theme: 'system', - setTheme: () => null, -}) - -export function ThemeProvider({ - children, - defaultTheme = 'system', - storageKey = 'ui-theme', -}: ThemeProviderProps) { - const [theme, setTheme] = useState( - () => (localStorage.getItem(storageKey) as Theme) || defaultTheme, - ) - - useEffect(() => { - const root = window.document.documentElement - root.classList.remove('light', 'dark') - - if (theme === 'system') { - const systemTheme = window.matchMedia('(prefers-color-scheme: dark)') - .matches - ? 'dark' - : 'light' - root.classList.add(systemTheme) - return - } - - root.classList.add(theme) - }, [theme]) - - const value = { - theme, - setTheme: (theme: Theme) => { - localStorage.setItem(storageKey, theme) - setTheme(theme) - }, - } - - return ( - - {children} - - ) -} - -export const useTheme = () => { - const context = useContext(ThemeProviderContext) - if (context === undefined) - throw new Error('useTheme must be used within a ThemeProvider') - return context -} -``` - ---- - -## Production Checklist - -Before deploying your Shadcn/ui + TanStack Router app: - -### Styling - -- [ ] All Shadcn/ui components render correctly -- [ ] Animations work properly on route changes -- [ ] Dark mode integration working (if applicable) -- [ ] CSS conflicts resolved -- [ ] Responsive design tested - -### Functionality - -- [ ] Navigation components work with router state -- [ ] Active states properly reflected -- [ ] TypeScript compilation successful -- [ ] All sheets, dialogs, and modals animate correctly - -### Performance - -- [ ] Bundle size optimized (tree shaking working) -- [ ] CSS-in-JS not causing performance issues -- [ ] Animation performance acceptable on slower devices - ---- - -## Related Resources - -- [Shadcn/ui TanStack Router Installation](https://ui.shadcn.com/docs/installation/tanstack-router) - Official integration guide -- [TanStack Router createLink API](../api/router#createlink) - API documentation for component integration -- [Tailwind CSS with TanStack Router](./integrate-tailwind-css.md) - Styling setup guide -- [Shadcn/ui Components](https://ui.shadcn.com/docs/components) - Complete component documentation diff --git a/docs/router/framework/react/how-to/migrate-from-react-router.md b/docs/router/framework/react/how-to/migrate-from-react-router.md deleted file mode 100644 index c5ae35ba237..00000000000 --- a/docs/router/framework/react/how-to/migrate-from-react-router.md +++ /dev/null @@ -1,744 +0,0 @@ ---- -title: How to Migrate from React Router v7 ---- - -This guide provides a step-by-step process to migrate your application from React Router v7 to TanStack Router. We'll cover the complete migration process from removing React Router dependencies to implementing TanStack Router's type-safe routing patterns. - -## Quick Start - -**Time Required:** 2-4 hours depending on app complexity -**Difficulty:** Intermediate -**Prerequisites:** Basic React knowledge, existing React Router v7 app - -### What You'll Accomplish - -- Remove React Router v7 dependencies and components -- Install and configure TanStack Router -- Convert route definitions to file-based routing -- Update navigation components and hooks -- Implement type-safe routing patterns -- Handle search params and dynamic routes -- Migrate from React Router v7's new features to TanStack Router equivalents - ---- - -## Complete Migration Process - -### Step 1: Prepare for Migration - -Before making any changes, prepare your environment and codebase: - -**1.1 Create a backup branch** - -```bash -git checkout -b migrate-to-tanstack-router -git push -u origin migrate-to-tanstack-router -``` - -**1.2 Install TanStack Router (keep React Router temporarily)** - -```bash -# Install TanStack Router -npm install @tanstack/react-router - -# Install development dependencies -npm install -D @tanstack/router-plugin @tanstack/react-router-devtools -``` - -**1.3 Set up the router plugin for your bundler** - -For **Vite** users, update your `vite.config.ts`: - -```typescript -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import { tanstackRouter } from '@tanstack/router-plugin/vite' - -export default defineConfig({ - plugins: [ - tanstackRouter(), // Add this before react plugin - react(), - ], -}) -``` - -For **other bundlers**, see our [bundler configuration guides](../routing/). - -### Step 2: Create TanStack Router Configuration - -**2.1 Create router configuration file** - -Create `tsr.config.json` in your project root: - -```json -{ - "routesDirectory": "./src/routes", - "generatedRouteTree": "./src/routeTree.gen.ts", - "quoteStyle": "single" -} -``` - -**2.2 Create routes directory** - -```bash -mkdir src/routes -``` - -### Step 3: Convert Your React Router v7 Structure - -**3.1 Identify your current React Router v7 setup** - -React Router v7 introduced several new patterns. Look for: - -- `createBrowserRouter` with new data APIs -- Framework mode configurations -- Server-side rendering setup -- New `loader` and `action` functions -- `defer` usage (simplified in v7) -- Type-safe routing features - -**3.2 Create root route** - -Create `src/routes/__root.tsx`: - -```typescript -import { createRootRoute, Link, Outlet } from '@tanstack/react-router' -import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' - -export const Route = createRootRoute({ - component: () => ( - <> - {/* Your existing layout/navbar content */} -
- - Home - - - About - -
-
- - - - ), -}) -``` - -**3.3 Create index route** - -Create `src/routes/index.tsx` for your home page: - -```typescript -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/')({ - component: Index, -}) - -function Index() { - return ( -
-

Welcome Home!

-
- ) -} -``` - -**3.4 Convert React Router v7 loaders** - -React Router v7 simplified loader patterns. Here's how to migrate them: - -**React Router v7:** - -```typescript -// app/routes/posts.tsx -export async function loader() { - const posts = await fetchPosts() - return { posts } // v7 removed need for json() wrapper -} - -export default function Posts() { - const { posts } = useLoaderData() - return
{/* render posts */}
-} -``` - -**TanStack Router equivalent:** -Create `src/routes/posts.tsx`: - -```typescript -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/posts')({ - loader: async () => { - const posts = await fetchPosts() - return { posts } - }, - component: Posts, -}) - -function Posts() { - const { posts } = Route.useLoaderData() - return
{/* render posts */}
-} -``` - -**3.5 Convert dynamic routes** - -**React Router v7:** - -```typescript -// app/routes/posts.$postId.tsx -export async function loader({ params }) { - const post = await fetchPost(params.postId) - return { post } -} - -export default function Post() { - const { post } = useLoaderData() - return
{post.title}
-} -``` - -**TanStack Router equivalent:** -Create `src/routes/posts/$postId.tsx`: - -```typescript -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/posts/$postId')({ - loader: async ({ params }) => { - const post = await fetchPost(params.postId) - return { post } - }, - component: Post, -}) - -function Post() { - const { post } = Route.useLoaderData() - const { postId } = Route.useParams() - return
{post.title}
-} -``` - -**3.6 Convert React Router v7 actions** - -**React Router v7:** - -```typescript -export async function action({ request, params }) { - const formData = await request.formData() - const result = await updatePost(params.postId, formData) - return { success: true } -} -``` - -**TanStack Router equivalent:** - -```typescript -export const Route = createFileRoute('/posts/$postId/edit')({ - component: EditPost, - // Actions are typically handled differently in TanStack Router - // Use mutations or form libraries like React Hook Form -}) - -function EditPost() { - const navigate = useNavigate() - - const handleSubmit = async (formData) => { - const result = await updatePost(params.postId, formData) - navigate({ to: '/posts/$postId', params: { postId } }) - } - - return
{/* form */}
-} -``` - -### Step 4: Handle React Router v7 Framework Features - -**4.1 Server-Side Rendering Migration** - -React Router v7 introduced framework mode with SSR. If you're using this: - -**React Router v7 Framework Mode:** - -```typescript -// react-router.config.ts -export default { - ssr: true, - prerender: ['/'], -} -``` - -**TanStack Router approach:** - -TanStack Router has built-in SSR capabilities. Set up your router for SSR: - -```typescript -// src/router.tsx -import { createRouter } from '@tanstack/react-router' -import { routeTree } from './routeTree.gen' - -const router = createRouter({ - routeTree, - context: { - // Add any SSR context here - }, -}) - -declare module '@tanstack/react-router' { - interface Register { - router: typeof router - } -} - -export { router } -``` - -For server-side rendering, use TanStack Router's built-in SSR APIs: - -```typescript -// server.tsx -import { createMemoryHistory } from '@tanstack/react-router' -import { StartServer } from '@tanstack/start/server' - -export async function render(url: string) { - const router = createRouter({ - routeTree, - history: createMemoryHistory({ initialEntries: [url] }), - }) - - await router.load() - - return ( - - ) -} -``` - -**4.2 Code Splitting Migration** - -React Router v7 improved code splitting. TanStack Router handles this via lazy routes: - -**React Router v7:** - -```typescript -const LazyComponent = lazy(() => import('./LazyComponent')) -``` - -**TanStack Router:** - -```typescript -import { createLazyFileRoute } from '@tanstack/react-router' - -export const Route = createLazyFileRoute('/lazy-route')({ - component: LazyComponent, -}) - -function LazyComponent() { - return
Lazy loaded!
-} -``` - -### Step 5: Update Navigation Components - -**5.1 Update Link components** - -**React Router v7:** - -```typescript -import { Link } from 'react-router' - -View Post -Posts -``` - -**TanStack Router:** - -```typescript -import { Link } from '@tanstack/react-router' - -View Post -Posts -``` - -**5.2 Update navigation hooks** - -**React Router v7:** - -```typescript -import { useNavigate } from 'react-router' - -function Component() { - const navigate = useNavigate() - - const handleClick = () => { - navigate('/posts/123') - } -} -``` - -**TanStack Router:** - -```typescript -import { useNavigate } from '@tanstack/react-router' - -function Component() { - const navigate = useNavigate() - - const handleClick = () => { - navigate({ to: '/posts/$postId', params: { postId: '123' } }) - } -} -``` - -### Step 6: Handle React Router v7 Specific Features - -**6.1 Migrate simplified `defer` usage** - -React Router v7 simplified defer by removing the wrapper function: - -**React Router v7:** - -```typescript -export async function loader() { - return { - data: fetchData(), // Promise directly returned - } -} -``` - -**TanStack Router:** - -TanStack Router uses a different approach for deferred data. Use loading states: - -```typescript -export const Route = createFileRoute('/deferred')({ - loader: async () => { - const data = await fetchData() - return { data } - }, - pendingComponent: () =>
Loading...
, - component: DeferredComponent, -}) -``` - -**6.2 Handle React Router v7's enhanced type safety** - -React Router v7 improved type inference. TanStack Router provides even better type safety: - -```typescript -// TanStack Router automatically infers types -export const Route = createFileRoute('/posts/$postId')({ - loader: async ({ params }) => { - // params.postId is automatically typed as string - const post = await fetchPost(params.postId) - return { post } - }, - component: Post, -}) - -function Post() { - // post is automatically typed based on loader return - const { post } = Route.useLoaderData() - // postId is automatically typed as string - const { postId } = Route.useParams() -} -``` - -### Step 7: Update Your Main Router Setup - -**7.1 Replace React Router v7 router creation** - -**Before (React Router v7):** - -```typescript -import { createBrowserRouter, RouterProvider } from 'react-router' - -const router = createBrowserRouter([ - // Your route definitions -]) - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - , -) -``` - -**After (TanStack Router):** - -```typescript -import { RouterProvider } from '@tanstack/react-router' -import { router } from './router' - -ReactDOM.createRoot(document.getElementById('root')!).render( - - - , -) -``` - -### Step 8: Handle Search Parameters - -**8.1 React Router v7 to TanStack Router search params** - -**React Router v7:** - -```typescript -import { useSearchParams } from 'react-router' - -function Component() { - const [searchParams, setSearchParams] = useSearchParams() - const page = searchParams.get('page') || '1' - - const updatePage = (newPage) => { - setSearchParams({ page: newPage }) - } -} -``` - -**TanStack Router:** - -```typescript -import { createFileRoute } from '@tanstack/react-router' -import { z } from 'zod' - -const searchSchema = z.object({ - page: z.number().catch(1), - filter: z.string().optional(), -}) - -export const Route = createFileRoute('/posts')({ - validateSearch: searchSchema, - component: Posts, -}) - -function Posts() { - const navigate = useNavigate({ from: '/posts' }) - const { page, filter } = Route.useSearch() - - const updatePage = (newPage: number) => { - navigate({ search: (prev) => ({ ...prev, page: newPage }) }) - } -} -``` - -### Step 9: Remove React Router Dependencies - -Only after everything is working with TanStack Router: - -**9.1 Remove React Router v7** - -```bash -npm uninstall react-router -``` - -**9.2 Clean up unused imports** - -Search your codebase for any remaining React Router imports: - -```bash -# Find remaining React Router imports -grep -r "react-router" src/ -``` - -Remove any remaining imports and replace with TanStack Router equivalents. - -### Step 10: Add Advanced Type Safety - -**10.1 Configure strict TypeScript** - -Update your `tsconfig.json`: - -```json -{ - "compilerOptions": { - "strict": true, - "noUncheckedIndexedAccess": true - } -} -``` - -**10.2 Add search parameter validation** - -For routes with search parameters, add validation schemas: - -```typescript -import { createFileRoute } from '@tanstack/react-router' -import { z } from 'zod' - -const postsSearchSchema = z.object({ - page: z.number().min(1).catch(1), - search: z.string().optional(), - category: z.enum(['tech', 'business', 'lifestyle']).optional(), -}) - -export const Route = createFileRoute('/posts')({ - validateSearch: postsSearchSchema, - component: Posts, -}) -``` - ---- - -## Production Checklist - -Before deploying your migrated application: - -### Router Configuration - -- [ ] Router instance created and properly exported -- [ ] Route tree generated successfully -- [ ] TypeScript declarations registered -- [ ] All route files follow naming conventions - -### Route Migration - -- [ ] All React Router v7 routes converted to file-based routing -- [ ] Dynamic routes updated with proper parameter syntax -- [ ] Nested routes maintain hierarchy -- [ ] Index routes created where needed -- [ ] Layout routes preserve component structure - -### Feature Migration - -- [ ] All React Router v7 loaders converted -- [ ] Actions migrated to appropriate patterns -- [ ] Server-side rendering configured (if applicable) -- [ ] Code splitting implemented -- [ ] Type safety enhanced - -### Navigation Updates - -- [ ] All Link components updated to TanStack Router -- [ ] useNavigate hooks replaced and tested -- [ ] Navigation parameters properly typed -- [ ] Search parameter validation implemented - -### Code Cleanup - -- [ ] React Router v7 dependencies removed -- [ ] Unused imports cleaned up -- [ ] No React Router references remain -- [ ] TypeScript compilation successful -- [ ] All tests passing - -### Testing - -- [ ] All routes accessible and rendering correctly -- [ ] Navigation between routes working -- [ ] Back/forward browser buttons functional -- [ ] Search parameters persisting correctly -- [ ] Dynamic routes with parameters working -- [ ] Nested route layouts displaying properly -- [ ] Framework features (SSR, code splitting) working if applicable - ---- - -## Common Problems - -### Error: "Cannot use useNavigate outside of context" - -**Problem:** You have remaining React Router imports that conflict with TanStack Router. - -**Solution:** - -1. Search for all React Router imports: - ```bash - grep -r "react-router" src/ - ``` -2. Replace all imports with TanStack Router equivalents -3. Ensure React Router is completely uninstalled - -### TypeScript Errors: Route Parameters - -**Problem:** TypeScript showing errors about route parameters not being typed correctly. - -**Solution:** - -1. Ensure your router is registered in the TypeScript module declaration: - ```typescript - declare module '@tanstack/react-router' { - interface Register { - router: typeof router - } - } - ``` -2. Check that your route files export the Route correctly -3. Verify parameter names match between route definition and usage - -### React Router v7 Framework Features Not Working - -**Problem:** Missing SSR or code splitting functionality after migration. - -**Solution:** - -1. TanStack Router has built-in SSR capabilities - use TanStack Start for full-stack applications -2. Use TanStack Router's lazy routes for code splitting -3. Configure SSR using TanStack Router's native APIs -4. Follow the [SSR setup guide](../setup-ssr.md) for detailed instructions - -### Routes Not Matching - -**Problem:** Routes not rendering or 404 errors for valid routes. - -**Solution:** - -1. Check file naming follows TanStack Router conventions: - - Dynamic routes: `$paramName.tsx` - - Index routes: `index.tsx` - - Nested routes: proper directory structure -2. Verify route tree generation is working -3. Check that the router plugin is properly configured - -### React Router v7 Simplified APIs Not Translating - -**Problem:** v7's simplified `defer` or other features don't have direct equivalents. - -**Solution:** - -1. Use TanStack Router's pending states for loading UX -2. Implement data fetching patterns that fit TanStack Router's architecture -3. Leverage TanStack Router's superior type safety for better DX - ---- - -## React Router v7 vs TanStack Router Feature Comparison - -| Feature | React Router v7 | TanStack Router | -| ------------------ | ------------------- | ---------------------------- | -| Type Safety | Good | Excellent | -| File-based Routing | Framework mode only | Built-in | -| Search Params | Basic | Validated with schemas | -| Code Splitting | Good | Excellent with lazy routes | -| SSR | Framework mode | Built-in with TanStack Start | -| Bundle Size | Larger | Smaller | -| Learning Curve | Moderate | Moderate | -| Community | Large | Growing | - ---- - -## Common Next Steps - -After successfully migrating to TanStack Router, consider these enhancements: - -### Advanced Features to Explore - -- **Route-based code splitting** - Improve performance with lazy loading -- **Search parameter validation** - Type-safe URL state management -- **Route preloading** - Enhance perceived performance -- **Route masking** - Advanced URL management -- **Integration with TanStack Query** - Powerful data fetching - ---- - -## Related Resources - -- [TanStack Router Documentation](https://tanstack.com/router) - Complete API reference -- [File-Based Routing Guide](../../routing/file-based-routing.md) - Detailed routing concepts -- [Navigation Guide](../../guide/navigation.md) - Complete navigation patterns -- [Search Parameters Guide](../../guide/search-params.md) - Advanced search param usage -- [Type Safety Guide](../../guide/type-safety.md) - TypeScript integration details -- [React Router v7 Changelog](https://reactrouter.com/start/changelog) - What changed in v7 diff --git a/docs/router/framework/react/how-to/navigate-with-search-params.md b/docs/router/framework/react/how-to/navigate-with-search-params.md deleted file mode 100644 index 512eb152e95..00000000000 --- a/docs/router/framework/react/how-to/navigate-with-search-params.md +++ /dev/null @@ -1,345 +0,0 @@ ---- -title: How to Navigate with Search Parameters ---- - -This guide covers updating and managing search parameters during navigation using TanStack Router's Link components and programmatic navigation methods. - -**Prerequisites:** [Set Up Basic Search Parameters](../setup-basic-search-params.md) - Foundation concepts for reading and validating search params. - -## Quick Start - -Configure navigation that updates search parameters while preserving existing state: - -```tsx -import { Link, useNavigate } from '@tanstack/react-router' - -// Link with search parameter updates -; ({ ...prev, query: 'new search' })}> - Search for "new search" - - -// Programmatic navigation -const navigate = useNavigate() -navigate({ - to: '/search', - search: (prev) => ({ ...prev, page: 1 }), -}) -``` - -## Navigation Methods - -### Using Link Components - -#### Basic Search Parameter Updates - -Replace all search parameters: - -```tsx -import { Link } from '@tanstack/router' - -function SearchForm() { - return ( -
- {/* Replace all search params */} - - Electronics - - - {/* Navigate to same route with new search */} - Sort by Price -
- ) -} -``` - -#### Functional Search Parameter Updates - -Merge with existing search parameters: - -```tsx -import { Link } from '@tanstack/react-router' - -function Pagination() { - return ( -
- {/* Preserve existing search, update page */} - ({ ...prev, page: (prev.page || 1) + 1 })}> - Next Page - - - {/* Toggle filter while keeping other params */} - ({ - ...prev, - inStock: !prev.inStock, - })} - > - Toggle In Stock - - - {/* Remove a search parameter */} - { - const { category, ...rest } = prev - return rest - }} - > - Clear Category Filter - -
- ) -} -``` - -#### Preserving All Search Parameters - -Use `search={true}` to keep all current search parameters when navigating: - -```tsx -import { Link } from '@tanstack/react-router' - -function Navigation() { - return ( - - ) -} -``` - -#### Navigation with Route Changes - -Navigate to different routes with search parameters: - -```tsx -import { Link } from '@tanstack/react-router' - -function Navigation() { - return ( - - ) -} -``` - -### Programmatic Navigation - -#### Using useNavigate Hook - -Navigate programmatically with search parameter updates: - -```tsx -import { useNavigate } from '@tanstack/react-router' - -function SearchControls() { - const navigate = useNavigate() - - const handleSortChange = (sortBy: string) => { - navigate({ - search: (prev) => ({ ...prev, sort: sortBy, page: 1 }), - }) - } - - const handleClearFilters = () => { - navigate({ - search: (prev) => { - const { category, minPrice, maxPrice, ...rest } = prev - return rest - }, - }) - } - - const handleSearch = (query: string) => { - navigate({ - to: '/search', - search: { query, page: 1 }, - }) - } - - return ( -
- - - - - -
- ) -} -``` - -#### Navigation with Router Instance - -Use the router directly only in non-React contexts where `useNavigate` or `Link` aren't available: - -```tsx -import { router } from './router' // Your router instance - -// βœ… Appropriate use case: Utility function outside React components -export function navigateFromUtility(searchParams: Record) { - router.navigate({ - search: (prev) => ({ ...prev, ...searchParams }), - }) -} - -// βœ… Appropriate use case: Event handlers in non-React code -class ApiService { - onAuthError() { - // Navigate to login when auth fails - router.navigate({ - to: '/login', - search: { redirect: window.location.pathname }, - }) - } -} - -// βœ… Appropriate use case: Global error handler -window.addEventListener('unhandledrejection', (event) => { - if (event.reason.status === 401) { - router.navigate({ - to: '/login', - search: { error: 'session-expired' }, - }) - } -}) -``` - -**⚠️ In React components, prefer `useNavigate` instead:** - -```tsx -// ❌ Avoid in React components -function Component() { - const router = useRouter() - - const handleClick = () => { - router.navigate({ search: { filter: 'active' } }) - } - - return -} - -// βœ… Use useNavigate in React components -function Component() { - const navigate = useNavigate() - - const handleClick = () => { - navigate({ search: { filter: 'active' } }) - } - - return -} -``` - -## Advanced Navigation Patterns - -### Conditional Navigation - -Navigate automatically when certain conditions are met: - -```tsx -import { useEffect } from 'react' -import { useNavigate, useSearch } from '@tanstack/react-router' - -function ConditionalNavigation() { - const navigate = useNavigate() - const search = useSearch({ from: '/products' }) - - // Auto-reset page when search query changes - useEffect(() => { - if (search.query && search.page > 1) { - navigate({ - search: (prev) => ({ ...prev, page: 1 }), - }) - } - }, [search.query, search.page, navigate]) - - return
Page resets automatically when search changes
-} -``` - -## Common Patterns - -## Common Problems - -### Search Parameters Not Updating - -**Problem:** Link navigation doesn't update search parameters. - -```tsx -// ❌ Wrong - no search prop -Electronics - -// βœ… Correct - with search parameters - - Electronics - -``` - -### Losing Existing Search Parameters - -**Problem:** New navigation replaces all search parameters instead of updating specific ones. - -```tsx -// ❌ Wrong - replaces all search params -Next Page - -// βœ… Correct - preserves existing search params - ({ ...prev, page: 2 })}> - Next Page - -``` - -## Common Next Steps - -After mastering navigation with search parameters, you might want to: - -- [Validate Search Parameters with Schemas](../validate-search-params.md) - Add robust validation with Zod, Valibot, or ArkType -- [Work with Arrays, Objects, and Dates](../arrays-objects-dates-search-params.md) - Handle arrays, objects, dates, and nested data structures - - - -## Related Resources - -- [TanStack Router Search Params Guide](https://tanstack.com/router/latest/docs/framework/react/guide/search-params) - Official documentation diff --git a/docs/router/framework/react/how-to/setup-auth-providers.md b/docs/router/framework/react/how-to/setup-auth-providers.md deleted file mode 100644 index 7d81f67b678..00000000000 --- a/docs/router/framework/react/how-to/setup-auth-providers.md +++ /dev/null @@ -1,634 +0,0 @@ ---- -title: How to Set Up Authentication Providers ---- - -This guide covers integrating popular authentication services (Auth0, Clerk, Supabase) with TanStack Router. - -## Quick Start - -Choose an authentication provider, install their SDK, wrap your router with their context, and adapt their auth state to work with TanStack Router's context system. - ---- - -## Auth0 Integration - -### 1. Install Auth0 - -```bash -npm install @auth0/auth0-react -``` - -### 2. Set Up Environment Variables - -Add to your `.env` file: - -```env -VITE_AUTH0_DOMAIN=your-auth0-domain.auth0.com -VITE_AUTH0_CLIENT_ID=your_auth0_client_id -``` - -### 3. Create Auth0 Wrapper - -Create `src/auth/auth0.tsx`: - -```tsx -import { Auth0Provider, useAuth0 } from '@auth0/auth0-react' -import { createContext, useContext } from 'react' - -interface Auth0ContextType { - isAuthenticated: boolean - user: any - login: () => void - logout: () => void - isLoading: boolean -} - -const Auth0Context = createContext(undefined) - -export function Auth0Wrapper({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ) -} - -function Auth0ContextProvider({ children }: { children: React.ReactNode }) { - const { isAuthenticated, user, loginWithRedirect, logout, isLoading } = - useAuth0() - - const contextValue = { - isAuthenticated, - user, - login: loginWithRedirect, - logout: () => - logout({ logoutParams: { returnTo: window.location.origin } }), - isLoading, - } - - return ( - - {children} - - ) -} - -export function useAuth0Context() { - const context = useContext(Auth0Context) - if (context === undefined) { - throw new Error('useAuth0Context must be used within Auth0Wrapper') - } - return context -} -``` - -### 4. Update App Configuration - -Update `src/App.tsx`: - -```tsx -import { RouterProvider } from '@tanstack/react-router' -import { Auth0Wrapper, useAuth0Context } from './auth/auth0' -import { router } from './router' - -function InnerApp() { - const auth = useAuth0Context() - - if (auth.isLoading) { - return ( -
- Loading... -
- ) - } - - return -} - -function App() { - return ( - - - - ) -} - -export default App -``` - -### 5. Create Protected Routes - -Create `src/routes/_authenticated.tsx`: - -```tsx -import { createFileRoute, redirect, Outlet } from '@tanstack/react-router' - -export const Route = createFileRoute('/_authenticated')({ - beforeLoad: ({ context, location }) => { - if (!context.auth.isAuthenticated) { - // Auth0 handles login redirects, so just trigger login - context.auth.login() - return - } - }, - component: () => , -}) -``` - ---- - -## Clerk Integration - -### 1. Install Clerk - -```bash -npm install @clerk/clerk-react -``` - -### 2. Set Up Environment Variables - -Add to your `.env` file: - -```env -VITE_CLERK_PUBLISHABLE_KEY=pk_test_your_clerk_key -``` - -### 3. Create Clerk Wrapper - -Create `src/auth/clerk.tsx`: - -```tsx -import { ClerkProvider, useUser, useAuth } from '@clerk/clerk-react' - -export function ClerkWrapper({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ) -} - -export function useClerkAuth() { - const { isSignedIn, isLoaded } = useAuth() - const { user } = useUser() - - return { - isAuthenticated: isSignedIn, - user: user - ? { - id: user.id, - username: - user.username || user.primaryEmailAddress?.emailAddress || '', - email: user.primaryEmailAddress?.emailAddress || '', - } - : null, - isLoading: !isLoaded, - login: () => { - // Clerk handles login through components - window.location.href = '/sign-in' - }, - logout: () => { - // Clerk handles logout through components - window.location.href = '/sign-out' - }, - } -} -``` - -### 4. Create Clerk Auth Routes - -Create `src/routes/sign-in.tsx`: - -```tsx -import { createFileRoute } from '@tanstack/react-router' -import { SignIn } from '@clerk/clerk-react' - -export const Route = createFileRoute('/sign-in')({ - component: () => ( -
- -
- ), -}) -``` - -Create `src/routes/sign-up.tsx`: - -```tsx -import { createFileRoute } from '@tanstack/react-router' -import { SignUp } from '@clerk/clerk-react' - -export const Route = createFileRoute('/sign-up')({ - component: () => ( -
- -
- ), -}) -``` - -### 5. Update App Configuration - -Update `src/App.tsx`: - -```tsx -import { RouterProvider } from '@tanstack/react-router' -import { ClerkWrapper, useClerkAuth } from './auth/clerk' -import { router } from './router' - -function InnerApp() { - const auth = useClerkAuth() - - if (auth.isLoading) { - return ( -
- Loading... -
- ) - } - - return -} - -function App() { - return ( - - - - ) -} - -export default App -``` - -### 6. Create Protected Routes - -Create `src/routes/_authenticated.tsx`: - -```tsx -import { createFileRoute, redirect, Outlet } from '@tanstack/react-router' - -export const Route = createFileRoute('/_authenticated')({ - beforeLoad: ({ context, location }) => { - if (!context.auth.isAuthenticated) { - throw redirect({ - to: '/sign-in', - search: { - redirect: location.href, - }, - }) - } - }, - component: () => , -}) -``` - ---- - -## Supabase Integration - -### 1. Install Supabase - -```bash -npm install @supabase/supabase-js -``` - -### 2. Set Up Environment Variables - -Add to your `.env` file: - -```env -VITE_SUPABASE_URL=https://your-project.supabase.co -VITE_SUPABASE_ANON_KEY=your_supabase_anon_key -``` - -### 3. Create Supabase Client - -Create `src/auth/supabase.tsx`: - -```tsx -import { createClient } from '@supabase/supabase-js' -import { createContext, useContext, useEffect, useState } from 'react' - -const supabase = createClient( - import.meta.env.VITE_SUPABASE_URL, - import.meta.env.VITE_SUPABASE_ANON_KEY, -) - -interface SupabaseAuthState { - isAuthenticated: boolean - user: any - login: (email: string, password: string) => Promise - logout: () => Promise - isLoading: boolean -} - -const SupabaseAuthContext = createContext( - undefined, -) - -export function SupabaseAuthProvider({ - children, -}: { - children: React.ReactNode -}) { - const [user, setUser] = useState(null) - const [isAuthenticated, setIsAuthenticated] = useState(false) - const [isLoading, setIsLoading] = useState(true) - - useEffect(() => { - // Get initial session - supabase.auth.getSession().then(({ data: { session } }) => { - setUser(session?.user ?? null) - setIsAuthenticated(!!session?.user) - setIsLoading(false) - }) - - // Listen for auth changes - const { - data: { subscription }, - } = supabase.auth.onAuthStateChange((_event, session) => { - setUser(session?.user ?? null) - setIsAuthenticated(!!session?.user) - setIsLoading(false) - }) - - return () => subscription.unsubscribe() - }, []) - - const login = async (email: string, password: string) => { - const { error } = await supabase.auth.signInWithPassword({ - email, - password, - }) - if (error) throw error - } - - const logout = async () => { - const { error } = await supabase.auth.signOut() - if (error) throw error - } - - return ( - - {children} - - ) -} - -export function useSupabaseAuth() { - const context = useContext(SupabaseAuthContext) - if (context === undefined) { - throw new Error('useSupabaseAuth must be used within SupabaseAuthProvider') - } - return context -} -``` - -### 4. Create Login Component - -Create `src/routes/login.tsx`: - -```tsx -import { createFileRoute, redirect } from '@tanstack/react-router' -import { useState } from 'react' - -export const Route = createFileRoute('/login')({ - validateSearch: (search) => ({ - redirect: (search.redirect as string) || '/dashboard', - }), - beforeLoad: ({ context, search }) => { - if (context.auth.isAuthenticated) { - throw redirect({ to: search.redirect }) - } - }, - component: LoginComponent, -}) - -function LoginComponent() { - const { auth } = Route.useRouteContext() - const { redirect } = Route.useSearch() - const [email, setEmail] = useState('') - const [password, setPassword] = useState('') - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState('') - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - setIsLoading(true) - setError('') - - try { - await auth.login(email, password) - // Supabase auth will automatically update context - window.location.href = redirect - } catch (err: any) { - setError(err.message || 'Login failed') - } finally { - setIsLoading(false) - } - } - - return ( -
-
-

Sign In

- - {error && ( -
- {error} -
- )} - -
- - setEmail(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - required - /> -
- -
- - setPassword(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - required - /> -
- - -
-
- ) -} -``` - -### 5. Update App Configuration - -Update `src/App.tsx`: - -```tsx -import { RouterProvider } from '@tanstack/react-router' -import { SupabaseAuthProvider, useSupabaseAuth } from './auth/supabase' -import { router } from './router' - -function InnerApp() { - const auth = useSupabaseAuth() - - if (auth.isLoading) { - return ( -
- Loading... -
- ) - } - - return -} - -function App() { - return ( - - - - ) -} - -export default App -``` - ---- - -## Provider Comparison - -| Feature | Auth0 | Clerk | Supabase | -| ----------------------- | -------- | --------- | -------- | -| **Setup Complexity** | Medium | Low | Medium | -| **UI Components** | Basic | Excellent | None | -| **Customization** | High | Medium | High | -| **Pricing** | Freemium | Freemium | Freemium | -| **Social Login** | βœ… | βœ… | βœ… | -| **Enterprise Features** | βœ… | βœ… | βœ… | -| **Database Included** | ❌ | ❌ | βœ… | - -### When to Choose Each: - -- **Auth0**: Complex enterprise requirements, extensive customization -- **Clerk**: Quick setup with beautiful UI components -- **Supabase**: Full-stack solution with database and real-time features - ---- - -## Common Problems - -### Provider Context Not Available - -**Problem:** Auth context is undefined in components. - -**Solution:** Ensure the provider wrapper is above `RouterProvider`: - -```tsx -// βœ… Correct order - - - - - - -// ❌ Wrong order - - - -``` - -### Infinite Loading States - -**Problem:** App stuck on loading screen. - -**Solution:** Check if auth provider properly sets `isLoading` to `false`: - -```tsx -// Add timeout fallback -useEffect(() => { - const timeout = setTimeout(() => { - if (isLoading) { - setIsLoading(false) - } - }, 5000) - return () => clearTimeout(timeout) -}, [isLoading]) -``` - -### Redirect Loops with Auth0 - -**Problem:** Continuous redirects between login and protected routes. - -**Solution:** Handle Auth0's automatic redirects properly: - -```tsx -export const Route = createFileRoute('/_authenticated')({ - beforeLoad: ({ context }) => { - if (!context.auth.isAuthenticated && !context.auth.isLoading) { - context.auth.login() - // Don't throw redirect, let Auth0 handle it - return - } - }, - component: () => , -}) -``` - ---- - -## Common Next Steps - -After integrating authentication providers, you might want to: - -- [How to Set Up Role-Based Access Control](../setup-rbac.md) - Add permission-based routing -- [How to Set Up Basic Authentication](../setup-authentication.md) - Custom auth implementation - - - -## Related Resources - -- [Auth0 React SDK](https://auth0.com/docs/libraries/auth0-react) - Official Auth0 documentation -- [Clerk React SDK](https://clerk.com/docs/references/react/overview) - Official Clerk documentation -- [Supabase Auth](https://supabase.com/docs/guides/auth) - Official Supabase auth guide diff --git a/docs/router/framework/react/how-to/setup-authentication.md b/docs/router/framework/react/how-to/setup-authentication.md deleted file mode 100644 index 14282c9617e..00000000000 --- a/docs/router/framework/react/how-to/setup-authentication.md +++ /dev/null @@ -1,485 +0,0 @@ ---- -title: How to Set Up Basic Authentication and Protected Routes ---- - -This guide covers implementing basic authentication patterns and protecting routes in TanStack Router applications. - -## Quick Start - -Set up authentication by creating a context-aware router, implementing auth state management, and using `beforeLoad` for route protection. This guide focuses on the core authentication setup using React Context. - ---- - -## Create Authentication Context - -Create `src/auth.tsx`: - -```tsx -import React, { createContext, useContext, useState, useEffect } from 'react' - -interface User { - id: string - username: string - email: string -} - -interface AuthState { - isAuthenticated: boolean - user: User | null - login: (username: string, password: string) => Promise - logout: () => void -} - -const AuthContext = createContext(undefined) - -export function AuthProvider({ children }: { children: React.ReactNode }) { - const [user, setUser] = useState(null) - const [isAuthenticated, setIsAuthenticated] = useState(false) - const [isLoading, setIsLoading] = useState(true) - - // Restore auth state on app load - useEffect(() => { - const token = localStorage.getItem('auth-token') - if (token) { - // Validate token with your API - fetch('/api/validate-token', { - headers: { Authorization: `Bearer ${token}` }, - }) - .then((response) => response.json()) - .then((userData) => { - if (userData.valid) { - setUser(userData.user) - setIsAuthenticated(true) - } else { - localStorage.removeItem('auth-token') - } - }) - .catch(() => { - localStorage.removeItem('auth-token') - }) - .finally(() => { - setIsLoading(false) - }) - } else { - setIsLoading(false) - } - }, []) - - // Show loading state while checking auth - if (isLoading) { - return ( -
- Loading... -
- ) - } - - const login = async (username: string, password: string) => { - // Replace with your authentication logic - const response = await fetch('/api/login', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password }), - }) - - if (response.ok) { - const userData = await response.json() - setUser(userData) - setIsAuthenticated(true) - // Store token for persistence - localStorage.setItem('auth-token', userData.token) - } else { - throw new Error('Authentication failed') - } - } - - const logout = () => { - setUser(null) - setIsAuthenticated(false) - localStorage.removeItem('auth-token') - } - - return ( - - {children} - - ) -} - -export function useAuth() { - const context = useContext(AuthContext) - if (context === undefined) { - throw new Error('useAuth must be used within an AuthProvider') - } - return context -} -``` - ---- - -## Configure Router Context - -### 1. Set Up Router Context - -Update `src/routes/__root.tsx`: - -```tsx -import { createRootRouteWithContext, Outlet } from '@tanstack/react-router' -import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' - -interface AuthState { - isAuthenticated: boolean - user: { id: string; username: string; email: string } | null - login: (username: string, password: string) => Promise - logout: () => void -} - -interface MyRouterContext { - auth: AuthState -} - -export const Route = createRootRouteWithContext()({ - component: () => ( -
- - -
- ), -}) -``` - -### 2. Configure Router - -Update `src/router.tsx`: - -```tsx -import { createRouter } from '@tanstack/react-router' -import { routeTree } from './routeTree.gen' - -export const router = createRouter({ - routeTree, - context: { - // auth will be passed down from App component - auth: undefined!, - }, -}) - -declare module '@tanstack/react-router' { - interface Register { - router: typeof router - } -} -``` - -### 3. Connect App with Authentication - -Update `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 ( - - - - ) -} - -export default App -``` - ---- - -## Create Protected Routes - -### 1. Create Authentication Layout Route - -Create `src/routes/_authenticated.tsx`: - -```tsx -import { createFileRoute, redirect, Outlet } from '@tanstack/react-router' - -export const Route = createFileRoute('/_authenticated')({ - beforeLoad: ({ context, location }) => { - if (!context.auth.isAuthenticated) { - throw redirect({ - to: '/login', - search: { - // Save current location for redirect after login - redirect: location.href, - }, - }) - } - }, - component: () => , -}) -``` - -### 2. Create Login Route - -Create `src/routes/login.tsx`: - -```tsx -import { createFileRoute, redirect } from '@tanstack/react-router' -import { useState } from 'react' - -export const Route = createFileRoute('/login')({ - validateSearch: (search) => ({ - redirect: (search.redirect as string) || '/', - }), - beforeLoad: ({ context, search }) => { - // Redirect if already authenticated - if (context.auth.isAuthenticated) { - throw redirect({ to: search.redirect }) - } - }, - component: LoginComponent, -}) - -function LoginComponent() { - const { auth } = Route.useRouteContext() - const { redirect } = Route.useSearch() - const navigate = Route.useNavigate() - const [username, setUsername] = useState('') - const [password, setPassword] = useState('') - const [isLoading, setIsLoading] = useState(false) - const [error, setError] = useState('') - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - setIsLoading(true) - setError('') - - try { - await auth.login(username, password) - // Navigate to the redirect URL using router navigation - navigate({ to: redirect }) - } catch (err) { - setError('Invalid username or password') - } finally { - setIsLoading(false) - } - } - - return ( -
-
-

Sign In

- - {error && ( -
- {error} -
- )} - -
- - setUsername(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - required - /> -
- -
- - setPassword(e.target.value)} - className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" - required - /> -
- - -
-
- ) -} -``` - -### 3. Create Protected Dashboard - -Create `src/routes/_authenticated/dashboard.tsx`: - -```tsx -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/_authenticated/dashboard')({ - component: DashboardComponent, -}) - -function DashboardComponent() { - const { auth } = Route.useRouteContext() - - return ( -
-
-

Dashboard

- -
- -
-

Welcome back!

-

- Hello, {auth.user?.username}! You are successfully - authenticated. -

-

Email: {auth.user?.email}

-
-
- ) -} -``` - ---- - -## Add Authentication Persistence - -Update your `AuthProvider` to restore authentication state on page refresh: - -```tsx -export function AuthProvider({ children }: { children: React.ReactNode }) { - const [user, setUser] = useState(null) - const [isAuthenticated, setIsAuthenticated] = useState(false) - const [isLoading, setIsLoading] = useState(true) - - // Restore auth state on app load - useEffect(() => { - const token = localStorage.getItem('auth-token') - if (token) { - // Validate token with your API - fetch('/api/validate-token', { - headers: { Authorization: `Bearer ${token}` }, - }) - .then((response) => response.json()) - .then((userData) => { - if (userData.valid) { - setUser(userData.user) - setIsAuthenticated(true) - } else { - localStorage.removeItem('auth-token') - } - }) - .catch(() => { - localStorage.removeItem('auth-token') - }) - .finally(() => { - setIsLoading(false) - }) - } else { - setIsLoading(false) - } - }, []) - - // Show loading state while checking auth - if (isLoading) { - return ( -
- Loading... -
- ) - } - - // ... rest of the provider logic -} -``` - ---- - -## Production Checklist - -Before deploying authentication, ensure you have: - -- [ ] Secured API endpoints with proper authentication middleware -- [ ] Set up HTTPS in production (required for secure cookies) -- [ ] Configured environment variables for API endpoints -- [ ] Implemented proper token validation and refresh -- [ ] Added CSRF protection for form-based authentication -- [ ] Tested authentication flows (login, logout, persistence) -- [ ] Added proper error handling for network failures -- [ ] Implemented loading states for auth operations - ---- - -## Common Problems - -### Authentication Context Not Available - -**Problem:** `useAuth must be used within an AuthProvider` error. - -**Solution:** Ensure `AuthProvider` wraps your entire app and `RouterProvider` is inside it. - -### User Logged Out on Page Refresh - -**Problem:** Authentication state resets when page refreshes. - -**Solution:** Add token persistence as shown in the persistence section above. - -### Protected Route Flashing Before Redirect - -**Problem:** Protected content briefly shows before redirecting to login. - -**Solution:** Use `beforeLoad` instead of component-level auth checks: - -```tsx -export const Route = createFileRoute('/_authenticated/dashboard')({ - beforeLoad: ({ context }) => { - if (!context.auth.isAuthenticated) { - throw redirect({ to: '/login' }) - } - }, - component: DashboardComponent, -}) -``` - ---- - -## Common Next Steps - -After setting up basic authentication, you might want to: - -- [How to Integrate Authentication Providers](../setup-auth-providers.md) - Use Auth0, Clerk, or Supabase -- [How to Set Up Role-Based Access Control](../setup-rbac.md) - Add permission-based routing - - - -## Related Resources - -- [Authenticated Routes Guide](../../guide/authenticated-routes.md) - Detailed conceptual guide -- [Router Context Guide](../../guide/router-context.md) - Understanding context in TanStack Router -- [Authentication Examples](https://github.com/TanStack/router/tree/main/examples/react/authenticated-routes) - Complete working examples diff --git a/docs/router/framework/react/how-to/setup-basic-search-params.md b/docs/router/framework/react/how-to/setup-basic-search-params.md deleted file mode 100644 index 45618f05109..00000000000 --- a/docs/router/framework/react/how-to/setup-basic-search-params.md +++ /dev/null @@ -1,431 +0,0 @@ ---- -title: How to Set Up Basic Search Parameters ---- - -Learn how to add type-safe, production-ready search parameters to your TanStack Router routes using schema validation. This guide covers the fundamentals of search parameter validation, reading values, and handling different data types with any standard schema-compliant validation library. - -## Quick Start - -Set up search parameters with schema validation (recommended for production): - -```tsx -import { createFileRoute } from '@tanstack/react-router' -import { zodValidator, fallback } from '@tanstack/zod-adapter' -import { z } from 'zod' - -const productSearchSchema = z.object({ - page: fallback(z.number(), 1).default(1), - category: fallback(z.string(), 'all').default('all'), - showSale: fallback(z.boolean(), false).default(false), -}) - -export const Route = createFileRoute('/products')({ - validateSearch: zodValidator(productSearchSchema), - component: ProductsPage, -}) - -function ProductsPage() { - const { page, category, showSale } = Route.useSearch() - - return ( -
-

Products

-

Page: {page}

-

Category: {category}

-

Show Sale Items: {showSale ? 'Yes' : 'No'}

-
- ) -} -``` - -## Why Use Schema Validation for Search Parameters? - -**Production Benefits:** - -- **Type Safety**: Automatic TypeScript inference -- **Runtime Validation**: Catches invalid URL parameters gracefully -- **Default Values**: Fallback handling for missing parameters -- **Error Handling**: Built-in validation error management -- **Maintainability**: Clear, declarative schema definitions - -## Validation Library Setup - -TanStack Router supports any standard schema-compliant validation library. This guide focuses on Zod for examples, but you can use any validation library: - -```bash -npm install zod @tanstack/zod-adapter -``` - -```tsx -import { zodValidator, fallback } from '@tanstack/zod-adapter' -import { z } from 'zod' - -const searchSchema = z.object({ - page: fallback(z.number(), 1).default(1), - category: fallback(z.string(), 'all').default('all'), -}) - -export const Route = createFileRoute('/products')({ - validateSearch: zodValidator(searchSchema), - component: ProductsPage, -}) -``` - -**For detailed validation library comparisons and advanced validation patterns, see:** [Validate Search Parameters with Schemas](../validate-search-params.md) - -## Step-by-Step Setup with Zod - -The rest of this guide uses Zod for examples, but the patterns apply to any validation library. - -### Step 1: Install Dependencies - -```bash -npm install zod @tanstack/zod-adapter -``` - -### Step 2: Define Your Search Schema - -Start by identifying what search parameters your route needs: - -```tsx -import { z } from 'zod' -import { fallback } from '@tanstack/zod-adapter' - -const shopSearchSchema = z.object({ - // Pagination - page: fallback(z.number(), 1).default(1), - limit: fallback(z.number(), 20).default(20), - - // Filtering - category: fallback(z.string(), 'all').default('all'), - minPrice: fallback(z.number(), 0).default(0), - maxPrice: fallback(z.number(), 1000).default(1000), - - // Settings - sort: fallback(z.enum(['name', 'price', 'date']), 'name').default('name'), - ascending: fallback(z.boolean(), true).default(true), - - // Optional parameters - searchTerm: z.string().optional(), - showOnlyInStock: fallback(z.boolean(), false).default(false), -}) - -type ShopSearch = z.infer -``` - -### Step 3: Add Schema Validation to Route - -Use the validation adapter to connect your schema to the route: - -```tsx -import { zodValidator } from '@tanstack/zod-adapter' - -export const Route = createFileRoute('/shop')({ - validateSearch: zodValidator(shopSearchSchema), - component: ShopPage, -}) -``` - -### Step 4: Read Search Parameters in Components - -Use the route's `useSearch()` hook to access validated and typed search parameters: - -```tsx -function ShopPage() { - const searchParams = Route.useSearch() - - // All properties are fully type-safe and validated - const { - page, - limit, - category, - sort, - ascending, - searchTerm, - showOnlyInStock, - } = searchParams - - return ( -
-

Shop - Page {page}

-
Category: {category}
-
- Sort: {sort} ({ascending ? 'ascending' : 'descending'}) -
-
Items per page: {limit}
-
In stock only: {showOnlyInStock ? 'Yes' : 'No'}
- {searchTerm &&
Search: "{searchTerm}"
} -
- ) -} -``` - -## Common Search Parameter Patterns - -### Pagination with Constraints - -```tsx -const paginationSchema = z.object({ - page: fallback(z.number().min(1), 1).default(1), - limit: fallback(z.number().min(10).max(100), 20).default(20), -}) - -export const Route = createFileRoute('/posts')({ - validateSearch: zodValidator(paginationSchema), - component: PostsPage, -}) - -function PostsPage() { - const { page, limit } = Route.useSearch() - - // Calculate offset for API calls - const offset = (page - 1) * limit - - return ( -
-

Posts (Page {page})

-

Showing {limit} posts per page

-

Offset: {offset}

- {/* Render posts... */} -
- ) -} -``` - -### Enum Validation with Defaults - -```tsx -const catalogSchema = z.object({ - sort: fallback(z.enum(['name', 'date', 'price']), 'name').default('name'), - category: fallback( - z.enum(['electronics', 'clothing', 'books', 'all']), - 'all', - ).default('all'), - ascending: fallback(z.boolean(), true).default(true), -}) - -export const Route = createFileRoute('/catalog')({ - validateSearch: zodValidator(catalogSchema), - component: CatalogPage, -}) -``` - -### Complex Data Types - -```tsx -const dashboardSchema = z.object({ - // Numbers with validation - userId: fallback(z.number().positive(), 1).default(1), - refreshInterval: fallback(z.number().min(1000).max(60000), 5000).default( - 5000, - ), - - // Strings with validation - theme: fallback(z.enum(['light', 'dark']), 'light').default('light'), - timezone: z.string().optional(), - - // Arrays with validation - selectedIds: fallback(z.number().array(), []).default([]), - tags: fallback(z.string().array(), []).default([]), - - // Objects with validation - filters: fallback( - z.object({ - status: z.enum(['active', 'inactive']).optional(), - type: z.string().optional(), - }), - {}, - ).default({}), -}) -``` - -### Date and Advanced Types - -```tsx -const reportSchema = z.object({ - startDate: z.string().pipe(z.coerce.date()).optional(), - endDate: z.string().pipe(z.coerce.date()).optional(), - format: fallback(z.enum(['pdf', 'csv', 'excel']), 'pdf').default('pdf'), - includeCharts: fallback(z.boolean(), true).default(true), -}) -``` - -## Reading Search Parameters Outside Components - -### Using getRouteApi - -For code-split components or separate files: - -```tsx -// components/ProductFilters.tsx -import { getRouteApi } from '@tanstack/react-router' - -const routeApi = getRouteApi('/products') - -export function ProductFilters() { - const { category, sort, showSale } = routeApi.useSearch() - - return ( -
- - {/* More filters... */} -
- ) -} -``` - -### Using useSearch with from - -```tsx -import { useSearch } from '@tanstack/react-router' - -function GenericSearchDisplay() { - const search = useSearch({ from: '/products' }) - - return
Current filters: {JSON.stringify(search, null, 2)}
-} -``` - -## Manual Validation (Understanding the Primitives) - -While schema validation is recommended for production, understanding manual validation helps you understand how search parameters work under the hood: - -```tsx -// Educational example - use schema validation for production -export const Route = createFileRoute('/example')({ - validateSearch: (search: Record) => ({ - // Numbers need coercion from URL strings - page: Number(search.page) || 1, - - // Strings can be cast with defaults - category: (search.category as string) || 'all', - - // Booleans: TanStack Router auto-converts "true"/"false" to booleans - showSale: Boolean(search.showSale), - - // Arrays need JSON parsing validation - selectedIds: Array.isArray(search.selectedIds) - ? search.selectedIds.map(Number).filter(Boolean) - : [], - }), - component: ExamplePage, -}) -``` - -## Production Checklist - -- [x] **Use schema validation** with a validation library for type safety and runtime validation -- [x] **Add fallback values** for graceful error handling -- [x] **Set default values** for optional parameters -- [x] **Validate constraints** using your validation library's built-in validators -- [x] **Handle optional parameters** appropriately -- [x] **Type inference** works automatically with proper schema setup -- [x] **Error boundaries** are configured to handle validation failures - -## Common Problems - -### Problem: Search Parameters Cause TypeScript Errors - -**Cause:** Missing or incorrect schema definition. - -**Solution:** Ensure your schema covers all search parameters and use proper types: - -```tsx -// ❌ Missing schema or incorrect types -export const Route = createFileRoute('/page')({ - component: MyPage, -}) - -// βœ… Complete schema with proper validation -const searchSchema = z.object({ - page: fallback(z.number(), 1).default(1), - category: fallback(z.string(), 'all').default('all'), -}) - -export const Route = createFileRoute('/page')({ - validateSearch: zodValidator(searchSchema), - component: MyPage, -}) -``` - -### Problem: Invalid URL Parameters Break the App - -**Cause:** Not using fallback handling for error cases. - -**Solution:** Use fallback values to provide safe defaults: - -```tsx -// ❌ No fallback handling -const schema = z.object({ - page: z.number().default(1), // Will throw on invalid input -}) - -// βœ… Graceful fallback handling -const schema = z.object({ - page: fallback(z.number(), 1).default(1), // Safe fallback to 1 -}) -``` - -### Problem: Optional Parameters Are Required by TypeScript - -**Cause:** Using `.default()` makes parameters required in navigation. - -**Solution:** Use `.optional()` for truly optional parameters: - -```tsx -const schema = z.object({ - // Required with default (navigation can omit, but always present in component) - page: fallback(z.number(), 1).default(1), - - // Truly optional (can be undefined in component) - searchTerm: z.string().optional(), -}) -``` - -### Problem: Complex Objects Not Validating - -**Cause:** Nested objects need explicit schema definition. - -**Solution:** Define complete nested schemas: - -```tsx -const schema = z.object({ - filters: fallback( - z.object({ - status: z.enum(['active', 'inactive']).optional(), - tags: z.string().array().optional(), - dateRange: z - .object({ - start: z.string().pipe(z.coerce.date()), - end: z.string().pipe(z.coerce.date()), - }) - .optional(), - }), - {}, - ).default({}), -}) -``` - -## Common Next Steps - -After setting up basic search parameters, you might want to: - -- [Validate Search Parameters with Schemas](../validate-search-params.md) - Add robust validation with Zod, Valibot, or ArkType -- [Navigate with Search Parameters](../navigate-with-search-params.md) - Learn to update search params with Links and navigation -- [Work with Arrays, Objects, and Dates](../arrays-objects-dates-search-params.md) - Handle arrays, objects, dates, and nested data structures - -## Related Resources - -- **Validation Libraries:** - - [Zod Documentation](https://zod.dev/) - Complete validation library reference - - [Valibot Documentation](https://valibot.dev/) - Lightweight validation library - - [Yup Documentation](https://github.com/jquense/yup) - Object schema validation -- **TanStack Router:** - - [TanStack Zod Adapter](https://tanstack.com/router/latest/docs/framework/react/api/router/zodValidator) - Official Zod adapter - - [TanStack Valibot Adapter](https://tanstack.com/router/latest/docs/framework/react/api/router/valibotValidator) - Official Valibot adapter - - [Search Parameters Guide](../../guide/search-params.md) - Comprehensive search parameters documentation - - [Type Safety Guide](../../guide/type-safety.md) - Understanding TanStack Router's type safety diff --git a/docs/router/framework/react/how-to/setup-rbac.md b/docs/router/framework/react/how-to/setup-rbac.md deleted file mode 100644 index 8307ebbdf04..00000000000 --- a/docs/router/framework/react/how-to/setup-rbac.md +++ /dev/null @@ -1,738 +0,0 @@ ---- -title: How to Set Up Role-Based Access Control ---- - -This guide covers implementing role-based access control (RBAC) and permission-based routing in TanStack Router applications. - -## Quick Start - -Extend your authentication context to include roles and permissions, create role-protected layout routes, and use `beforeLoad` to check user permissions before rendering routes. - ---- - -## Extend Authentication Context - -### 1. Add Roles to User Type - -Update your authentication context to include roles: - -```tsx -// src/auth.tsx -import React, { createContext, useContext, useState } from 'react' - -interface User { - id: string - username: string - email: string - roles: string[] - permissions: string[] -} - -interface AuthState { - isAuthenticated: boolean - user: User | null - hasRole: (role: string) => boolean - hasAnyRole: (roles: string[]) => boolean - hasPermission: (permission: string) => boolean - hasAnyPermission: (permissions: string[]) => boolean - login: (username: string, password: string) => Promise - logout: () => void -} - -const AuthContext = createContext(undefined) - -export function AuthProvider({ children }: { children: React.ReactNode }) { - const [user, setUser] = useState(null) - const [isAuthenticated, setIsAuthenticated] = useState(false) - - const hasRole = (role: string) => { - return user?.roles.includes(role) ?? false - } - - const hasAnyRole = (roles: string[]) => { - return roles.some((role) => user?.roles.includes(role)) ?? false - } - - const hasPermission = (permission: string) => { - return user?.permissions.includes(permission) ?? false - } - - const hasAnyPermission = (permissions: string[]) => { - return ( - permissions.some((permission) => - user?.permissions.includes(permission), - ) ?? false - ) - } - - const login = async (username: string, password: string) => { - const response = await fetch('/api/login', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password }), - }) - - if (response.ok) { - const userData = await response.json() - setUser(userData) - setIsAuthenticated(true) - } else { - throw new Error('Authentication failed') - } - } - - const logout = () => { - setUser(null) - setIsAuthenticated(false) - } - - return ( - - {children} - - ) -} - -export function useAuth() { - const context = useContext(AuthContext) - if (context === undefined) { - throw new Error('useAuth must be used within an AuthProvider') - } - return context -} -``` - -### 2. Update Router Context Types - -Update `src/routes/__root.tsx`: - -```tsx -import { createRootRouteWithContext, Outlet } from '@tanstack/react-router' - -interface AuthState { - isAuthenticated: boolean - user: { - id: string - username: string - email: string - roles: string[] - permissions: string[] - } | null - hasRole: (role: string) => boolean - hasAnyRole: (roles: string[]) => boolean - hasPermission: (permission: string) => boolean - hasAnyPermission: (permissions: string[]) => boolean - login: (username: string, password: string) => Promise - logout: () => void -} - -interface MyRouterContext { - auth: AuthState -} - -export const Route = createRootRouteWithContext()({ - component: () => ( -
- -
- ), -}) -``` - ---- - -## Create Role-Protected Routes - -### 1. Admin-Only Routes - -Create `src/routes/_authenticated/_admin.tsx`: - -```tsx -import { createFileRoute, redirect, Outlet } from '@tanstack/react-router' - -export const Route = createFileRoute('/_authenticated/_admin')({ - beforeLoad: ({ context, location }) => { - if (!context.auth.hasRole('admin')) { - throw redirect({ - to: '/unauthorized', - search: { - redirect: location.href, - }, - }) - } - }, - component: AdminLayout, -}) - -function AdminLayout() { - return ( -
-
- Admin Area: You have administrative privileges. -
- -
- ) -} -``` - -### 2. Multiple Role Access - -Create `src/routes/_authenticated/_moderator.tsx`: - -```tsx -import { createFileRoute, redirect, Outlet } from '@tanstack/react-router' - -export const Route = createFileRoute('/_authenticated/_moderator')({ - beforeLoad: ({ context, location }) => { - const allowedRoles = ['admin', 'moderator'] - if (!context.auth.hasAnyRole(allowedRoles)) { - throw redirect({ - to: '/unauthorized', - search: { - redirect: location.href, - reason: 'insufficient_role', - }, - }) - } - }, - component: ModeratorLayout, -}) - -function ModeratorLayout() { - const { auth } = Route.useRouteContext() - - return ( -
-
- Moderator Area: Role: {auth.user?.roles.join(', ')} -
- -
- ) -} -``` - -### 3. Permission-Based Routes - -Create `src/routes/_authenticated/_users.tsx`: - -```tsx -import { createFileRoute, redirect, Outlet } from '@tanstack/react-router' - -export const Route = createFileRoute('/_authenticated/_users')({ - beforeLoad: ({ context, location }) => { - const requiredPermissions = ['users:read', 'users:write'] - if (!context.auth.hasAnyPermission(requiredPermissions)) { - throw redirect({ - to: '/unauthorized', - search: { - redirect: location.href, - reason: 'insufficient_permissions', - }, - }) - } - }, - component: () => , -}) -``` - ---- - -## Create Specific Protected Pages - -### 1. Admin Dashboard - -Create `src/routes/_authenticated/_admin/dashboard.tsx`: - -```tsx -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/_authenticated/_admin/dashboard')({ - component: AdminDashboard, -}) - -function AdminDashboard() { - const { auth } = Route.useRouteContext() - - return ( -
-

Admin Dashboard

- -
-
-

User Management

-

Manage all users in the system

- -
- -
-

System Settings

-

Configure system-wide settings

- -
- -
-

Reports

-

View system reports and analytics

- -
-
- -
-

Your Info:

-

Username: {auth.user?.username}

-

Roles: {auth.user?.roles.join(', ')}

-

Permissions: {auth.user?.permissions.join(', ')}

-
-
- ) -} -``` - -### 2. User Management Page - -Create `src/routes/_authenticated/_users/manage.tsx`: - -```tsx -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/_authenticated/_users/manage')({ - beforeLoad: ({ context }) => { - // Additional permission check at the page level - if (!context.auth.hasPermission('users:write')) { - throw new Error('You need write permissions to manage users') - } - }, - component: UserManagement, -}) - -function UserManagement() { - const { auth } = Route.useRouteContext() - - const canEdit = auth.hasPermission('users:write') - const canDelete = auth.hasPermission('users:delete') - - return ( -
-

User Management

- -
- - - - - - - - - - - - - - - - - -
- Name - - Email - - Role - - Actions -
John Doejohn@example.com - - User - - - {canEdit && ( - - )} - {canDelete && ( - - )} -
-
- -
-

Your Permissions:

-
    - {auth.user?.permissions.map((permission) => ( -
  • βœ“ {permission}
  • - ))} -
-
-
- ) -} -``` - ---- - -## Create Unauthorized Page - -Create `src/routes/unauthorized.tsx`: - -```tsx -import { createFileRoute, Link } from '@tanstack/react-router' - -export const Route = createFileRoute('/unauthorized')({ - validateSearch: (search) => ({ - redirect: (search.redirect as string) || '/dashboard', - reason: (search.reason as string) || 'insufficient_permissions', - }), - component: UnauthorizedPage, -}) - -function UnauthorizedPage() { - const { redirect, reason } = Route.useSearch() - const { auth } = Route.useRouteContext() - - const reasonMessages = { - insufficient_role: 'You do not have the required role to access this page.', - insufficient_permissions: - 'You do not have the required permissions to access this page.', - default: 'You are not authorized to access this page.', - } - - const message = - reasonMessages[reason as keyof typeof reasonMessages] || - reasonMessages.default - - return ( -
-
-
-
- - - -
-
- -

Access Denied

-

{message}

- -
-

- Your roles: {auth.user?.roles.join(', ') || 'None'} -

-

- Your permissions:{' '} - {auth.user?.permissions.join(', ') || 'None'} -

-
- -
- - Go to Dashboard - - - - Try Again - -
-
-
- ) -} -``` - ---- - -## Component-Level Permission Checks - -### 1. Conditional Rendering Hook - -Create `src/hooks/usePermissions.ts`: - -```tsx -import { useRouter } from '@tanstack/react-router' - -export function usePermissions() { - const router = useRouter() - const auth = router.options.context.auth - - return { - hasRole: auth.hasRole, - hasAnyRole: auth.hasAnyRole, - hasPermission: auth.hasPermission, - hasAnyPermission: auth.hasAnyPermission, - user: auth.user, - } -} -``` - -### 2. Permission Guard Component - -Create `src/components/PermissionGuard.tsx`: - -```tsx -interface PermissionGuardProps { - children: React.ReactNode - roles?: string[] - permissions?: string[] - requireAll?: boolean - fallback?: React.ReactNode -} - -export function PermissionGuard({ - children, - roles = [], - permissions = [], - requireAll = false, - fallback = null, -}: PermissionGuardProps) { - const { hasAnyRole, hasAnyPermission, hasRole, hasPermission } = - usePermissions() - - const hasRequiredRoles = - roles.length === 0 || - (requireAll ? roles.every((role) => hasRole(role)) : hasAnyRole(roles)) - - const hasRequiredPermissions = - permissions.length === 0 || - (requireAll - ? permissions.every((permission) => hasPermission(permission)) - : hasAnyPermission(permissions)) - - if (hasRequiredRoles && hasRequiredPermissions) { - return <>{children} - } - - return <>{fallback} -} -``` - -### 3. Using Permission Guards - -```tsx -import { PermissionGuard } from '../components/PermissionGuard' - -function SomeComponent() { - return ( -
-

Dashboard

- - - - - - You cannot edit users

} - > - -
- - - - -
- ) -} -``` - ---- - -## Advanced Permission Patterns - -### 1. Resource-Based Permissions - -```tsx -// Check if user can edit a specific resource -function canEditResource(auth: AuthState, resourceId: string, ownerId: string) { - // Admin can edit anything - if (auth.hasRole('admin')) return true - - // Owner can edit their own resources - if (auth.user?.id === ownerId && auth.hasPermission('resource:edit:own')) - return true - - // Moderators can edit with permission - if (auth.hasRole('moderator') && auth.hasPermission('resource:edit:any')) - return true - - return false -} - -// Usage in component -function ResourceEditor({ resource }) { - const { auth } = Route.useRouteContext() - - if (!canEditResource(auth, resource.id, resource.ownerId)) { - return
You cannot edit this resource
- } - - return -} -``` - -### 2. Time-Based Permissions - -```tsx -function hasTimeBasedPermission(auth: AuthState, permission: string) { - const userPermissions = auth.user?.permissions || [] - const hasPermission = userPermissions.includes(permission) - - // Check if permission has time restrictions - const timeRestricted = userPermissions.find((p) => - p.startsWith(`${permission}:time:`), - ) - - if (timeRestricted) { - const [, , startHour, endHour] = timeRestricted.split(':') - const currentHour = new Date().getHours() - return ( - currentHour >= parseInt(startHour) && currentHour <= parseInt(endHour) - ) - } - - return hasPermission -} -``` - ---- - -## Common Problems - -### Role/Permission Data Not Loading - -**Problem:** User roles/permissions are undefined in routes. - -**Solution:** Ensure your authentication API returns complete user data: - -```tsx -const login = async (username: string, password: string) => { - const response = await fetch('/api/login', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ username, password }), - }) - - if (response.ok) { - const userData = await response.json() - // Ensure userData includes roles and permissions - console.log('User data:', userData) // Debug log - setUser(userData) - setIsAuthenticated(true) - } -} -``` - -### Permission Checks Too Restrictive - -**Problem:** Users locked out of areas they should access. - -**Solution:** Use hierarchical permissions and role inheritance: - -```tsx -const roleHierarchy = { - admin: ['admin', 'moderator', 'user'], - moderator: ['moderator', 'user'], - user: ['user'], -} - -const hasRole = (requiredRole: string) => { - const userRoles = user?.roles || [] - return userRoles.some((userRole) => - roleHierarchy[userRole]?.includes(requiredRole), - ) -} -``` - -### Performance Issues with Many Permission Checks - -**Problem:** Too many permission checks slowing down renders. - -**Solution:** Memoize permission computations: - -```tsx -import { useMemo } from 'react' - -function usePermissions() { - const { auth } = Route.useRouteContext() - - const permissions = useMemo( - () => ({ - canEditUsers: auth.hasPermission('users:write'), - canDeleteUsers: auth.hasPermission('users:delete'), - isAdmin: auth.hasRole('admin'), - isModerator: auth.hasAnyRole(['admin', 'moderator']), - }), - [auth.user?.roles, auth.user?.permissions], - ) - - return permissions -} -``` - ---- - -## Common Next Steps - -After setting up RBAC, you might want to: - -- [How to Set Up Basic Authentication](../setup-authentication.md) - Core auth implementation -- [How to Integrate Authentication Providers](../setup-auth-providers.md) - Use external auth services - - - -## Related Resources - -- [Authenticated Routes Guide](../../guide/authenticated-routes.md) - Core authentication concepts -- [Router Context Guide](../../guide/router-context.md) - Understanding router context -- [RBAC Best Practices](https://auth0.com/docs/manage-users/access-control/rbac) - General RBAC principles diff --git a/docs/router/framework/react/how-to/setup-ssr.md b/docs/router/framework/react/how-to/setup-ssr.md deleted file mode 100644 index 79dde24bb0d..00000000000 --- a/docs/router/framework/react/how-to/setup-ssr.md +++ /dev/null @@ -1,552 +0,0 @@ ---- -title: How to Set Up Server-Side Rendering (SSR) ---- - -> [!IMPORTANT] > **[TanStack Start](../../guide/tanstack-start.md) is the recommended way to set up SSR** - it provides SSR, streaming, and deployment with zero configuration. -> -> Use the manual setup below only if you need to integrate with an existing server. - -## Quick Start with TanStack Start - -```bash -npx create-tsrouter-app@latest my-app --template start -cd my-app -npm run dev -``` - -## Manual SSR Setup - -### 1. Install Dependencies - -```bash -npm install express compression -npm install --save-dev @types/express -``` - -### 2. Create Shared Router Configuration - -```tsx -// src/router.tsx -import { createRouter as createTanstackRouter } from '@tanstack/react-router' -import { routeTree } from './routeTree.gen' - -export function createRouter() { - return createTanstackRouter({ - routeTree, - context: { - head: '', // For server-side head injection - }, - defaultPreload: 'intent', - }) -} - -declare module '@tanstack/react-router' { - interface Register { - router: ReturnType - } -} -``` - -### 3. Set Up Server Entry Point - -```tsx -// src/entry-server.tsx -import { pipeline } from 'node:stream/promises' -import { - RouterServer, - createRequestHandler, - renderRouterToString, -} from '@tanstack/react-router/ssr/server' -import { createRouter } from './router' -import type express from 'express' - -export async function render({ - req, - res, - head = '', -}: { - head?: string - req: express.Request - res: express.Response -}) { - // Convert Express request to Web API Request - const url = new URL(req.originalUrl || req.url, 'https://localhost:3000').href - - const request = new Request(url, { - method: req.method, - headers: (() => { - const headers = new Headers() - for (const [key, value] of Object.entries(req.headers)) { - headers.set(key, value as any) - } - return headers - })(), - }) - - // Create request handler - const handler = createRequestHandler({ - request, - createRouter: () => { - const router = createRouter() - - // Inject server context (like head tags from Vite) - router.update({ - context: { - ...router.options.context, - head: head, - }, - }) - return router - }, - }) - - // Render to string (non-streaming) - const response = await handler(({ responseHeaders, router }) => - renderRouterToString({ - responseHeaders, - router, - children: , - }), - ) - - // Convert Web API Response back to Express response - res.statusMessage = response.statusText - res.status(response.status) - - response.headers.forEach((value, name) => { - res.setHeader(name, value) - }) - - // Stream response body - return pipeline(response.body as any, res) -} -``` - -### 4. Set Up Client Entry Point - -```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, ) -``` - -### 5. Configure Vite for SSR - -```ts -// vite.config.ts -import path from 'node:path' -import url from 'node:url' -import { TanStackRouterVite } from '@tanstack/router-plugin/vite' -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -const __filename = url.fileURLToPath(import.meta.url) -const __dirname = path.dirname(__filename) - -export default defineConfig(({ isSsrBuild }) => ({ - plugins: [ - TanStackRouterVite({ - autoCodeSplitting: true, - }), - react(), - ], - build: isSsrBuild - ? { - // SSR build configuration - ssr: true, - outDir: 'dist/server', - emitAssets: true, - copyPublicDir: false, - rollupOptions: { - input: path.resolve(__dirname, 'src/entry-server.tsx'), - output: { - entryFileNames: '[name].js', - chunkFileNames: 'assets/[name]-[hash].js', - assetFileNames: 'assets/[name]-[hash][extname]', - }, - }, - } - : { - // Client build configuration - outDir: 'dist/client', - emitAssets: true, - copyPublicDir: true, - rollupOptions: { - input: path.resolve(__dirname, 'src/entry-client.tsx'), - output: { - entryFileNames: '[name].js', - chunkFileNames: 'assets/[name]-[hash].js', - assetFileNames: 'assets/[name]-[hash][extname]', - }, - }, - }, -})) -``` - -### 6. Update Root Route for HTML Structure - -```tsx -// src/routes/__root.tsx -import { - HeadContent, - Outlet, - createRootRouteWithContext, -} from '@tanstack/react-router' -import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' - -interface RouterContext { - head: string -} - -export const Route = createRootRouteWithContext()({ - head: () => ({ - links: [ - { rel: 'icon', href: '/favicon.ico' }, - { rel: 'apple-touch-icon', href: '/logo192.png' }, - { rel: 'manifest', href: '/manifest.json' }, - ], - meta: [ - { - charSet: 'UTF-8', - }, - { - name: 'viewport', - content: 'width=device-width, initial-scale=1.0', - }, - { - title: 'TanStack Router SSR App', - }, - ], - scripts: [ - // Development scripts - ...(!import.meta.env.PROD - ? [ - { - type: 'module', - children: `import RefreshRuntime from "/@react-refresh" - RefreshRuntime.injectIntoGlobalHook(window) - window.$RefreshReg$ = () => {} - window.$RefreshSig$ = () => (type) => type - window.__vite_plugin_react_preamble_installed__ = true`, - }, - { - type: 'module', - src: '/@vite/client', - }, - ] - : []), - // Entry script - { - type: 'module', - src: import.meta.env.PROD - ? '/entry-client.js' - : '/src/entry-client.tsx', - }, - ], - }), - component: RootComponent, -}) - -function RootComponent() { - return ( - - - - - - - - - - ) -} -``` - -### 7. Create Express Server - -```js -// server.js -import path from 'node:path' -import express from 'express' -import compression from 'compression' - -const isTest = process.env.NODE_ENV === 'test' || !!process.env.VITE_TEST_BUILD - -export async function createServer( - root = process.cwd(), - isProd = process.env.NODE_ENV === 'production', - hmrPort = process.env.VITE_DEV_SERVER_PORT, -) { - const app = express() - - let vite - if (!isProd) { - // Development mode with Vite middleware - vite = await ( - await import('vite') - ).createServer({ - root, - logLevel: isTest ? 'error' : 'info', - server: { - middlewareMode: true, - watch: { - usePolling: true, - interval: 100, - }, - hmr: { - port: hmrPort, - }, - }, - appType: 'custom', - }) - app.use(vite.middlewares) - } else { - // Production mode - app.use(compression()) - app.use(express.static('./dist/client')) - } - - app.use('*', async (req, res) => { - try { - const url = req.originalUrl - - // Check for static assets - if (path.extname(url) !== '') { - console.warn(`${url} is not a valid router path`) - res.status(404).end(`${url} is not a valid router path`) - return - } - - // Extract head content from Vite in development - let viteHead = '' - if (!isProd) { - const transformedHtml = await vite.transformIndexHtml( - url, - ``, - ) - viteHead = transformedHtml.substring( - transformedHtml.indexOf('') + 6, - transformedHtml.indexOf(''), - ) - } - - // Load server entry - const entry = await (async () => { - if (!isProd) { - return vite.ssrLoadModule('/src/entry-server.tsx') - } else { - return import('./dist/server/entry-server.js') - } - })() - - console.info('Rendering:', url) - await entry.render({ req, res, head: viteHead }) - } catch (e) { - !isProd && vite.ssrFixStacktrace(e) - console.error(e.stack) - res.status(500).end(e.stack) - } - }) - - return { app, vite } -} - -if (!isTest) { - createServer().then(({ app }) => - app.listen(3000, () => { - console.info('Server running at http://localhost:3000') - }), - ) -} -``` - -### 8. Update Package Scripts - -```json -{ - "scripts": { - "dev": "node server.js", - "build": "npm run build:client && npm run build:server", - "build:client": "vite build", - "build:server": "vite build --ssr", - "start": "NODE_ENV=production node server.js" - } -} -``` - -## Streaming SSR - -For better performance, enable streaming SSR by replacing `renderRouterToString` with `renderRouterToStream`: - -```tsx -// src/entry-server.tsx -import { renderRouterToStream } from '@tanstack/react-router/ssr/server' - -// Replace renderRouterToString with: -const response = await handler(({ request, responseHeaders, router }) => - renderRouterToStream({ - request, - responseHeaders, - router, - children: , - }), -) -``` - -### Streaming Vite Configuration - -For streaming SSR, update your Vite config: - -```ts -// vite.config.ts -export default defineConfig(({ isSsrBuild }) => ({ - plugins: [ - TanStackRouterVite({ - autoCodeSplitting: true, - enableStreaming: true, // Enable streaming support - }), - react(), - ], - // ... rest of config - ssr: { - optimizeDeps: { - include: ['@tanstack/react-router/ssr/server'], - }, - }, -})) -``` - -## Common Problems - -> [!TIP] > **Most of these problems are automatically solved by [TanStack Start](../../guide/tanstack-start.md).** The issues below are primarily relevant for manual SSR setups. - -### React Import Errors - -**Problem:** `ReferenceError: React is not defined` during SSR - -**Solution:** Ensure React is properly imported in components: - -```tsx -// In your route components -import React from 'react' // Add explicit import -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/')({ - component: () =>
Hello World
, // React is now available -}) -``` - -### Hydration Mismatches - -**Problem:** Client HTML doesn't match server HTML - -**Solution:** Ensure consistent rendering between server and client: - -```tsx -// Use useIsomorphicLayoutEffect for browser-only effects -import { useLayoutEffect, useEffect } from 'react' - -const useIsomorphicLayoutEffect = - typeof window !== 'undefined' ? useLayoutEffect : useEffect - -function MyComponent() { - useIsomorphicLayoutEffect(() => { - // Browser-only code that won't cause hydration mismatches - }, []) -} -``` - -### Bun Runtime Issues - -**Problem:** `Cannot find module "react-dom/server"` with Bun - -**Solution:** Use Node.js compatibility or create Bun-specific builds: - -```json -{ - "scripts": { - "build:bun": "bun build --target=bun --outdir=dist/bun src/entry-server.tsx" - } -} -``` - -### Module Resolution Errors - -**Problem:** SSR modules not resolving correctly - -**Solution:** Configure Vite SSR externals: - -```ts -// vite.config.ts -export default defineConfig({ - ssr: { - noExternal: [ - // Packages that need to be bundled for SSR - '@tanstack/react-router', - ], - external: [ - // Packages that should remain external - 'express', - ], - }, -}) -``` - -### Streaming Configuration Issues - -**Problem:** Streaming SSR not working with existing Vite setup - -**Solution:** Ensure proper streaming configuration: - -```ts -// vite.config.ts - Additional streaming config -export default defineConfig({ - define: { - 'process.env.STREAMING_SSR': JSON.stringify(true), - }, - optimizeDeps: { - include: ['@tanstack/react-router/ssr/server'], - }, -}) -``` - -### Build Output Issues - -**Problem:** Server build missing assets or incorrect paths - -**Solution:** Verify build configuration: - -```ts -// vite.config.ts -const ssrConfig = { - ssr: true, - outDir: 'dist/server', - ssrEmitAssets: true, // Important for asset handling - copyPublicDir: false, - rollupOptions: { - input: path.resolve(__dirname, 'src/entry-server.tsx'), - external: ['express', 'compression'], // External deps - }, -} -``` - -## Related Resources - -- [TanStack Start](../../guide/tanstack-start.md) - **Recommended full-stack React framework with SSR** -- [SSR Guide (Detailed)](../../guide/ssr.md) - Comprehensive SSR concepts, utilities, and theory -- [Data Loading](../../guide/data-loading.md) - SSR-compatible data loading patterns - -## Common Next Steps - -- [Deploy to Production](../deploy-to-production.md) - Deploy your SSR app -- [Setup Authentication](../setup-authentication.md) - Add auth to SSR routes -- [Debug Router Issues](../debug-router-issues.md) - Troubleshoot SSR-specific routing problems - - diff --git a/docs/router/framework/react/how-to/setup-testing.md b/docs/router/framework/react/how-to/setup-testing.md deleted file mode 100644 index 39200694f79..00000000000 --- a/docs/router/framework/react/how-to/setup-testing.md +++ /dev/null @@ -1,975 +0,0 @@ -# How to Set Up Testing with Code-Based Routing - -This guide covers setting up comprehensive testing for TanStack Router applications that use code-based routing, including unit tests, integration tests, and end-to-end testing strategies. - -## Quick Start - -Set up testing by configuring your test framework (Vitest/Jest), creating router test utilities, and implementing patterns for testing navigation, route components, and data loading with manually defined routes. - -> **Using File-Based Routing?** See [How to Test File-Based Routing](../test-file-based-routing.md) for patterns specific to file-based routing applications. - ---- - -## Configure Test Framework - -### 1. Install Dependencies - -For **Vitest** (recommended): - -```bash -npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom -``` - -For **Jest**: - -```bash -npm install -D jest @testing-library/react @testing-library/jest-dom @testing-library/user-event jest-environment-jsdom -``` - -### 2. Configure Vitest - -Create `vitest.config.ts`: - -```ts -import { defineConfig } from 'vitest/config' -import react from '@vitejs/plugin-react' - -export default defineConfig({ - plugins: [react()], - test: { - environment: 'jsdom', - setupFiles: ['./src/test/setup.ts'], - typecheck: { enabled: true }, - watch: false, - }, -}) -``` - -### 3. Create Test Setup - -Create `src/test/setup.ts`: - -```ts -import '@testing-library/jest-dom/vitest' - -// @ts-expect-error -global.IS_REACT_ACT_ENVIRONMENT = true -``` - ---- - -## Code-Based Router Testing Patterns - -The following patterns are specifically designed for applications using code-based routing where you manually create routes with `createRoute()` and build route trees programmatically. - -### 1. TanStack Router Internal Pattern (Recommended) - -The TanStack Router team uses this pattern internally for testing router components: - -```tsx -import { beforeEach, afterEach, describe, expect, test, vi } from 'vitest' -import { cleanup, render, screen } from '@testing-library/react' -import { - RouterProvider, - createBrowserHistory, - createRootRoute, - createRoute, - createRouter, -} from '@tanstack/react-router' -import type { RouterHistory } from '@tanstack/react-router' - -let history: RouterHistory - -beforeEach(() => { - history = createBrowserHistory() - expect(window.location.pathname).toBe('/') -}) - -afterEach(() => { - history.destroy() - window.history.replaceState(null, 'root', '/') - vi.clearAllMocks() - vi.resetAllMocks() - cleanup() -}) - -describe('Router Component Testing', () => { - test('should render route component', async () => { - const rootRoute = createRootRoute() - const indexRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/', - component: () =>

IndexTitle

, - }) - - const routeTree = rootRoute.addChildren([indexRoute]) - const router = createRouter({ routeTree, history }) - - render() - - expect(await screen.findByText('IndexTitle')).toBeInTheDocument() - }) -}) -``` - -### 2. Alternative: Router Test Utilities (For Simpler Cases) - -Create `src/test/router-utils.tsx`: - -```tsx -import React from 'react' -import { render, RenderOptions } from '@testing-library/react' -import { - createRouter, - createRootRoute, - createRoute, - RouterProvider, - Outlet, -} from '@tanstack/react-router' -import { createMemoryHistory } from '@tanstack/react-router' - -// Create a root route for testing -const rootRoute = createRootRoute({ - component: () => , -}) - -// Test router factory -export function createTestRouter(routes: any[], initialLocation = '/') { - const routeTree = rootRoute.addChildren(routes) - - const router = createRouter({ - routeTree, - history: createMemoryHistory({ - initialEntries: [initialLocation], - }), - }) - - return router -} - -// Wrapper component for testing -interface RouterWrapperProps { - children: React.ReactNode - router: any -} - -function RouterWrapper({ children, router }: RouterWrapperProps) { - return {children} -} - -// Custom render function with router -interface RenderWithRouterOptions extends Omit { - router?: any - initialLocation?: string - routes?: any[] -} - -export function renderWithRouter( - ui: React.ReactElement, - { - router, - initialLocation = '/', - routes = [], - ...renderOptions - }: RenderWithRouterOptions = {}, -) { - if (!router && routes.length > 0) { - router = createTestRouter(routes, initialLocation) - } - - if (!router) { - throw new Error( - 'Router is required. Provide either a router or routes array.', - ) - } - - function Wrapper({ children }: { children: React.ReactNode }) { - return {children} - } - - return { - ...render(ui, { wrapper: Wrapper, ...renderOptions }), - router, - } -} -``` - -### 2. Mock Route Factory - -Create `src/test/mock-routes.tsx`: - -```tsx -import { createRoute } from '@tanstack/react-router' -import { rootRoute } from './router-utils' - -export const createMockRoute = ( - path: string, - component: React.ComponentType, - options: any = {}, -) => { - return createRoute({ - getParentRoute: () => rootRoute, - path, - component, - ...options, - }) -} - -// Common test components -export function TestComponent({ title = 'Test' }: { title?: string }) { - return
{title}
-} - -export function LoadingComponent() { - return
Loading...
-} - -export function ErrorComponent({ error }: { error: Error }) { - return
Error: {error.message}
-} -``` - ---- - -## Test Code-Based Route Components - -### 1. Basic Component Testing - -```tsx -import { describe, it, expect } from 'vitest' -import { screen } from '@testing-library/react' -import { createRoute } from '@tanstack/react-router' -import { - renderWithRouter, - rootRoute, - TestComponent, -} from '../test/router-utils' - -describe('Code-Based Route Component Testing', () => { - it('should render route component', () => { - const testRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/', - component: TestComponent, - }) - - renderWithRouter(
, { - routes: [testRoute], - initialLocation: '/', - }) - - expect(screen.getByTestId('test-component')).toBeInTheDocument() - }) - - it('should render component with props from route context', () => { - function ComponentWithContext() { - const { title } = Route.useLoaderData() - return
{title}
- } - - const contextRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/context', - component: ComponentWithContext, - loader: () => ({ title: 'From Context' }), - }) - - renderWithRouter(
, { - routes: [contextRoute], - initialLocation: '/context', - }) - - expect(screen.getByText('From Context')).toBeInTheDocument() - }) -}) -``` - -### 2. Testing Route Parameters - -```tsx -import { describe, it, expect } from 'vitest' -import { screen } from '@testing-library/react' -import { createRoute } from '@tanstack/react-router' -import { renderWithRouter, rootRoute } from '../test/router-utils' - -describe('Route Parameters', () => { - it('should handle route params correctly', () => { - function UserProfile() { - const { userId } = Route.useParams() - return
User: {userId}
- } - - const userRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/users/$userId', - component: UserProfile, - }) - - renderWithRouter(
, { - routes: [userRoute], - initialLocation: '/users/123', - }) - - expect(screen.getByText('User: 123')).toBeInTheDocument() - }) - - it('should handle search params correctly', () => { - function SearchPage() { - const { q, page } = Route.useSearch() - return ( -
- Query: {q}, Page: {page} -
- ) - } - - const searchRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/search', - component: SearchPage, - validateSearch: (search) => ({ - q: (search.q as string) || '', - page: Number(search.page) || 1, - }), - }) - - renderWithRouter(
, { - routes: [searchRoute], - initialLocation: '/search?q=react&page=2', - }) - - expect(screen.getByText('Query: react, Page: 2')).toBeInTheDocument() - }) -}) -``` - ---- - -## Test Navigation - -### 1. Testing Link Components - -```tsx -import { describe, it, expect } from 'vitest' -import { screen, fireEvent } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import { Link, createRoute } from '@tanstack/react-router' -import { - renderWithRouter, - rootRoute, - TestComponent, -} from '../test/router-utils' - -describe('Code-Based Route Navigation', () => { - it('should navigate when link is clicked', async () => { - const user = userEvent.setup() - - function HomePage() { - return ( -
-

Home

- - About - -
- ) - } - - function AboutPage() { - return

About Page

- } - - const homeRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/', - component: HomePage, - }) - - const aboutRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/about', - component: AboutPage, - }) - - const { router } = renderWithRouter(
, { - routes: [homeRoute, aboutRoute], - initialLocation: '/', - }) - - // Initial state - expect(screen.getByText('Home')).toBeInTheDocument() - expect(router.state.location.pathname).toBe('/') - - // Click link - await user.click(screen.getByTestId('about-link')) - - // Check navigation - expect(screen.getByText('About Page')).toBeInTheDocument() - expect(router.state.location.pathname).toBe('/about') - }) - - it('should navigate programmatically', async () => { - function NavigationTest() { - const navigate = Route.useNavigate() - - const handleNavigate = () => { - navigate({ to: '/dashboard', search: { tab: 'settings' } }) - } - - return ( -
-

Navigation Test

- -
- ) - } - - const testRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/', - component: NavigationTest, - }) - - const dashboardRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/dashboard', - component: () =>

Dashboard

, - validateSearch: (search) => ({ - tab: (search.tab as string) || 'general', - }), - }) - - const { router } = renderWithRouter(
, { - routes: [testRoute, dashboardRoute], - initialLocation: '/', - }) - - await userEvent.click(screen.getByTestId('navigate-btn')) - - expect(router.state.location.pathname).toBe('/dashboard') - expect(router.state.location.search).toEqual({ tab: 'settings' }) - }) -}) -``` - -### 2. Testing Route Guards - -```tsx -import { describe, it, expect } from 'vitest' -import { screen } from '@testing-library/react' -import { createRoute, redirect } from '@tanstack/react-router' -import { renderWithRouter, rootRoute } from '../test/router-utils' - -describe('Code-Based Route Guards', () => { - it('should redirect unauthenticated users', () => { - const mockAuth = { isAuthenticated: false } - - function ProtectedPage() { - return

Protected Content

- } - - function LoginPage() { - return

Login Required

- } - - const protectedRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/protected', - component: ProtectedPage, - beforeLoad: ({ context }) => { - if (!mockAuth.isAuthenticated) { - throw redirect({ to: '/login' }) - } - }, - }) - - const loginRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/login', - component: LoginPage, - }) - - renderWithRouter(
, { - routes: [protectedRoute, loginRoute], - initialLocation: '/protected', - }) - - // Should redirect to login - expect(screen.getByText('Login Required')).toBeInTheDocument() - }) - - it('should allow authenticated users', () => { - const mockAuth = { isAuthenticated: true } - - function ProtectedPage() { - return

Protected Content

- } - - const protectedRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/protected', - component: ProtectedPage, - beforeLoad: ({ context }) => { - if (!mockAuth.isAuthenticated) { - throw redirect({ to: '/login' }) - } - }, - }) - - renderWithRouter(
, { - routes: [protectedRoute], - initialLocation: '/protected', - }) - - expect(screen.getByText('Protected Content')).toBeInTheDocument() - }) -}) -``` - ---- - -## Test Data Loading - -### 1. Testing Loaders - -```tsx -import { describe, it, expect, vi } from 'vitest' -import { screen, waitFor } from '@testing-library/react' -import { createRoute } from '@tanstack/react-router' -import { renderWithRouter, rootRoute } from '../test/router-utils' - -describe('Code-Based Route Data Loading', () => { - it('should load and display data from loader', async () => { - const mockFetchUser = vi.fn().mockResolvedValue({ - id: 1, - name: 'John Doe', - email: 'john@example.com', - }) - - function UserProfile() { - const user = Route.useLoaderData() - return ( -
-

{user.name}

-

{user.email}

-
- ) - } - - const userRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/users/$userId', - component: UserProfile, - loader: ({ params }) => mockFetchUser(params.userId), - }) - - renderWithRouter(
, { - routes: [userRoute], - initialLocation: '/users/1', - }) - - await waitFor(() => { - expect(screen.getByText('John Doe')).toBeInTheDocument() - expect(screen.getByText('john@example.com')).toBeInTheDocument() - }) - - expect(mockFetchUser).toHaveBeenCalledWith('1') - }) - - it('should handle loader errors', async () => { - const mockFetchUser = vi.fn().mockRejectedValue(new Error('User not found')) - - function UserProfile() { - const user = Route.useLoaderData() - return
{user.name}
- } - - function ErrorComponent({ error }: { error: Error }) { - return
Error: {error.message}
- } - - const userRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/users/$userId', - component: UserProfile, - loader: ({ params }) => mockFetchUser(params.userId), - errorComponent: ErrorComponent, - }) - - renderWithRouter(
, { - routes: [userRoute], - initialLocation: '/users/1', - }) - - await waitFor(() => { - expect(screen.getByTestId('error')).toBeInTheDocument() - expect(screen.getByText('Error: User not found')).toBeInTheDocument() - }) - }) -}) -``` - -### 2. Testing with React Query - -```tsx -import { describe, it, expect, vi } from 'vitest' -import { screen, waitFor } from '@testing-library/react' -import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { createRoute } from '@tanstack/react-router' -import { renderWithRouter, rootRoute } from '../test/router-utils' - -describe('React Query Integration', () => { - it('should work with React Query', async () => { - const queryClient = new QueryClient({ - defaultOptions: { - queries: { - retry: false, - }, - }, - }) - - const mockFetchPosts = vi.fn().mockResolvedValue([ - { id: 1, title: 'Post 1' }, - { id: 2, title: 'Post 2' }, - ]) - - function PostsList() { - const posts = Route.useLoaderData() - return ( -
- {posts.map((post: any) => ( -
{post.title}
- ))} -
- ) - } - - const postsRoute = createRoute({ - getParentRoute: () => rootRoute, - path: '/posts', - component: PostsList, - loader: ({ context: { queryClient } }) => - queryClient.ensureQueryData({ - queryKey: ['posts'], - queryFn: mockFetchPosts, - }), - }) - - function TestWrapper({ children }: { children: React.ReactNode }) { - return ( - - {children} - - ) - } - - renderWithRouter(
, { - routes: [postsRoute], - initialLocation: '/posts', - wrapper: TestWrapper, - }) - - await waitFor(() => { - expect(screen.getByText('Post 1')).toBeInTheDocument() - expect(screen.getByText('Post 2')).toBeInTheDocument() - }) - }) -}) -``` - ---- - -## Test with Context - -### 1. Testing Router Context - -```tsx -import { describe, it, expect } from 'vitest' -import { screen } from '@testing-library/react' -import { - createRootRouteWithContext, - createRoute, - Outlet, -} from '@tanstack/react-router' - -interface RouterContext { - auth: { - user: { id: string; name: string } | null - isAuthenticated: boolean - } -} - -describe('Code-Based Router Context', () => { - it('should provide context to routes', () => { - const rootRouteWithContext = createRootRouteWithContext()({ - component: () => , - }) - - function UserDashboard() { - const { auth } = Route.useRouteContext() - return ( -
- Welcome, {auth.user?.name || 'Guest'}! -
- ) - } - - const dashboardRoute = createRoute({ - getParentRoute: () => rootRouteWithContext, - path: '/dashboard', - component: UserDashboard, - }) - - const mockContext = { - auth: { - user: { id: '1', name: 'John Doe' }, - isAuthenticated: true, - }, - } - - const router = createRouter({ - routeTree: rootRouteWithContext.addChildren([dashboardRoute]), - context: mockContext, - history: createMemoryHistory({ - initialEntries: ['/dashboard'], - }), - }) - - render() - - expect(screen.getByText('Welcome, John Doe!')).toBeInTheDocument() - }) -}) -``` - ---- - -## E2E Testing with Playwright - -### 1. Playwright Configuration - -Create `playwright.config.ts`: - -```ts -import { defineConfig, devices } from '@playwright/test' - -export default defineConfig({ - testDir: './e2e', - fullyParallel: true, - forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, - reporter: 'html', - use: { - baseURL: 'http://localhost:3000', - trace: 'on-first-retry', - }, - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, - ], - webServer: { - command: 'npm run dev', - url: 'http://localhost:3000', - reuseExistingServer: !process.env.CI, - }, -}) -``` - -### 2. E2E Test Example - -Create `e2e/navigation.spec.ts`: - -```ts -import { test, expect } from '@playwright/test' - -test.describe('Code-Based Router Navigation', () => { - test('should navigate between pages', async ({ page }) => { - await page.goto('/') - - // Check home page - await expect(page.locator('h1')).toContainText('Home') - - // Navigate to about page - await page.click('text=About') - await expect(page).toHaveURL('/about') - await expect(page.locator('h1')).toContainText('About') - - // Use browser back button - await page.goBack() - await expect(page).toHaveURL('/') - await expect(page.locator('h1')).toContainText('Home') - }) - - test('should handle search parameters', async ({ page }) => { - await page.goto('/search?q=react') - - await expect(page.locator('[data-testid="search-input"]')).toHaveValue( - 'react', - ) - await expect(page).toHaveURL('/search?q=react') - - // Update search - await page.fill('[data-testid="search-input"]', 'vue') - await page.press('[data-testid="search-input"]', 'Enter') - - await expect(page).toHaveURL('/search?q=vue') - }) - - test('should handle authentication flow', async ({ page }) => { - // Try to access protected route - await page.goto('/dashboard') - - // Should redirect to login - await expect(page).toHaveURL('/login') - - // Login - await page.fill('[data-testid="username"]', 'testuser') - await page.fill('[data-testid="password"]', 'password') - await page.click('[data-testid="login-btn"]') - - // Should redirect back to dashboard - await expect(page).toHaveURL('/dashboard') - await expect(page.locator('h1')).toContainText('Dashboard') - }) -}) -``` - ---- - -## Code-Based Routing Testing Best Practices - -### 1. Test Organization - -``` -src/ -β”œβ”€β”€ components/ -β”‚ β”œβ”€β”€ Header.tsx -β”‚ └── Header.test.tsx -β”œβ”€β”€ routes/ -β”‚ β”œβ”€β”€ posts.tsx # Code-based route definitions -β”‚ β”œβ”€β”€ posts.test.tsx -β”‚ └── index.tsx -β”œβ”€β”€ test/ -β”‚ β”œβ”€β”€ setup.ts -β”‚ β”œβ”€β”€ router-utils.tsx # Code-based router utilities -β”‚ └── mock-routes.tsx # Manual route factories -└── __tests__/ - β”œβ”€β”€ integration/ - └── e2e/ -``` - -### 2. Common Patterns - -```tsx -// Mock external dependencies for code-based routes -vi.mock('../api/users', () => ({ - fetchUser: vi.fn(), - updateUser: vi.fn(), -})) - -// Test utility for common code-based route setups -export function createAuthenticatedRouter(user = mockUser) { - // Manually create routes for testing - const protectedRoutes = [ - createRoute({ - getParentRoute: () => rootRoute, - path: '/dashboard', - component: DashboardComponent, - }), - ] - - return createTestRouter(protectedRoutes, { - context: { - auth: { user, isAuthenticated: true }, - }, - }) -} - -// Group related tests -describe('User Management', () => { - describe('when authenticated', () => { - it('should show user dashboard', () => { - // Test implementation - }) - }) - - describe('when not authenticated', () => { - it('should redirect to login', () => { - // Test implementation - }) - }) -}) -``` - ---- - -## Common Problems - -### Test Environment Issues - -**Problem:** Tests fail with "window is not defined" errors. - -**Solution:** Ensure jsdom environment is configured: - -```ts -// vitest.config.ts -export default defineConfig({ - test: { - environment: 'jsdom', - }, -}) -``` - -### Router Context Missing - -**Problem:** Components can't access router context in tests. - -**Solution:** Use the custom render function with router: - -```tsx -// βœ… Correct -renderWithRouter(, { routes, initialLocation }) - -// ❌ Wrong -render() -``` - -### Async Data Loading - -**Problem:** Tests fail because they don't wait for data loading. - -**Solution:** Use proper async testing patterns: - -```tsx -await waitFor(() => { - expect(screen.getByText('Loaded Data')).toBeInTheDocument() -}) -``` - ---- - -## Common Next Steps - -After setting up code-based routing testing, you might want to: - -- [How to Test File-Based Routing](../test-file-based-routing.md) - Specific patterns for file-based routing apps -- [How to Set Up Basic Authentication](../setup-authentication.md) - Test authentication flows -- [How to Debug Common Router Issues](../debug-router-issues.md) - Debug test failures - - - -## Related Resources - -- [Code-Based Routing Guide](../../routing/code-based-routing.md) - Understanding code-based routing -- [Vitest Documentation](https://vitest.dev/) - Testing framework -- [Testing Library React](https://testing-library.com/docs/react-testing-library/intro/) - Component testing utilities -- [Playwright Documentation](https://playwright.dev/) - E2E testing framework -- [TanStack Router Examples](https://github.com/TanStack/router/tree/main/examples) - Example test setups diff --git a/docs/router/framework/react/how-to/share-search-params-across-routes.md b/docs/router/framework/react/how-to/share-search-params-across-routes.md deleted file mode 100644 index 35eea9a6cbf..00000000000 --- a/docs/router/framework/react/how-to/share-search-params-across-routes.md +++ /dev/null @@ -1,245 +0,0 @@ ---- -title: Share Search Parameters Across Routes ---- - -# How to Share Search Parameters Across Routes - -Search parameters automatically inherit from parent routes in TanStack Router. When a parent route validates search parameters, child routes can access them via `Route.useSearch()` alongside their own parameters. - -## How Parameter Inheritance Works - -TanStack Router automatically merges search parameters from parent routes with child route parameters. This happens through the route hierarchy: - -1. **Parent route** validates shared parameters with `validateSearch` -2. **Child routes** automatically inherit those validated parameters -3. **`Route.useSearch()`** returns both local and inherited parameters - -## Global Parameters via Root Route - -Share parameters across your entire application by validating them in the root route: - -```tsx -// routes/__root.tsx -import { createRootRoute, Outlet } from '@tanstack/react-router' -import { zodValidator } from '@tanstack/zod-adapter' -import { z } from 'zod' - -const globalSearchSchema = z.object({ - theme: z.enum(['light', 'dark']).default('light'), - lang: z.enum(['en', 'es', 'fr']).default('en'), - debug: z.boolean().default(false), -}) - -export const Route = createRootRoute({ - validateSearch: zodValidator(globalSearchSchema), - component: RootComponent, -}) - -function RootComponent() { - const { theme, lang, debug } = Route.useSearch() - - return ( -
- {debug && } - -
- ) -} -``` - -```tsx -// routes/products/index.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), - category: z.string().default('all'), -}) - -export const Route = createFileRoute('/products/')({ - validateSearch: zodValidator(productSearchSchema), - component: ProductsPage, -}) - -function ProductsPage() { - // Contains both local (page, category) AND inherited (theme, lang, debug) parameters - const search = Route.useSearch() - - return ( -
-

Products (Theme: {search.theme})

-

Page: {search.page}

-

Category: {search.category}

-
- ) -} -``` - -## Section-Specific Parameters via Layout Routes - -Share parameters within a section of your app using layout routes: - -```tsx -// routes/_authenticated.tsx -import { createFileRoute, Outlet } from '@tanstack/react-router' -import { zodValidator } from '@tanstack/zod-adapter' -import { z } from 'zod' - -const authSearchSchema = z.object({ - impersonate: z.string().optional(), - sidebar: z.boolean().default(true), - notifications: z.boolean().default(true), -}) - -export const Route = createFileRoute('/_authenticated')({ - validateSearch: zodValidator(authSearchSchema), - component: AuthenticatedLayout, -}) - -function AuthenticatedLayout() { - const search = Route.useSearch() - - return ( -
- {search.sidebar && } -
- {search.notifications && } - -
- {search.impersonate && } -
- ) -} -``` - -```tsx -// routes/_authenticated/dashboard.tsx -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/_authenticated/dashboard')({ - component: DashboardPage, -}) - -function DashboardPage() { - // Contains inherited auth parameters (impersonate, sidebar, notifications) - const search = Route.useSearch() - - return ( -
-

Dashboard

- {search.impersonate && ( - Currently impersonating: {search.impersonate} - )} - -
- ) -} -``` - -## Common Use Cases - -**Global Application Settings:** - -- Theme, language, timezone -- Debug flags, feature toggles -- Analytics tracking (UTM parameters) - -**Section-Specific State:** - -- Authentication context (user role, impersonation) -- Layout preferences (sidebar, density) -- Workspace or organization context - -**Persistent UI State:** - -- Modal visibility, drawer state -- Filter presets, view modes -- Accessibility preferences - -## Common Problems - -### Problem: Parameters Not Inheriting - -**Cause**: Parent route not validating the shared parameters. - -```tsx -// ❌ Root route missing validateSearch -export const Route = createRootRoute({ - component: RootComponent, // No validateSearch -}) - -// Child route can't access theme parameter -function ProductsPage() { - const search = Route.useSearch() // No theme available -} -``` - -**Solution**: Add `validateSearch` to the parent route: - -```tsx -// βœ… Root route validates shared parameters -export const Route = createRootRoute({ - validateSearch: zodValidator(globalSearchSchema), - component: RootComponent, -}) -``` - -### Problem: Navigation Loses Shared Parameters - -**Cause**: Not preserving inherited parameters during navigation. - -```tsx -// ❌ Navigation overwrites all search parameters -router.navigate({ - to: '/products', - search: { page: 1 }, // Loses theme, lang, etc. -}) -``` - -**Solution**: Preserve existing parameters with function syntax: - -```tsx -// βœ… Preserve existing parameters -router.navigate({ - to: '/products', - search: (prev) => ({ ...prev, page: 1 }), -}) -``` - -### Problem: Type Errors with Inherited Parameters - -**Cause**: Child route schema doesn't account for inherited parameters. - -```tsx -// ❌ TypeScript error: Property 'theme' doesn't exist -const search = Route.useSearch() -console.log(search.theme) // Type error -``` - -**Solution**: TypeScript automatically infers inherited types when using `validateSearch`. No additional typing needed - the inheritance works automatically. - -## Production Checklist - -- [ ] **Clear ownership**: Document which route validates which shared parameters -- [ ] **Avoid conflicts**: Use distinct parameter names across route levels -- [ ] **Preserve on navigation**: Use function syntax to maintain inherited parameters -- [ ] **Minimal URLs**: Only include essential shared parameters -- [ ] **Graceful defaults**: Provide fallback values for all shared parameters - - - -## Related Resources - -- [Set Up Basic Search Parameters](../setup-basic-search-params.md) - Learn search parameter fundamentals -- [Navigate with Search Parameters](../navigate-with-search-params.md) - Navigate while preserving search state -- [Validate Search Parameters with Schemas](../validate-search-params.md) - Add type safety to shared parameters diff --git a/docs/router/framework/react/how-to/test-file-based-routing.md b/docs/router/framework/react/how-to/test-file-based-routing.md deleted file mode 100644 index 82ad57f7ee5..00000000000 --- a/docs/router/framework/react/how-to/test-file-based-routing.md +++ /dev/null @@ -1,952 +0,0 @@ ---- -title: How to Test Router with File-Based Routing ---- - -This guide covers testing TanStack Router applications that use file-based routing, including testing route generation, file-based route components, and file-based routing patterns. - -## Quick Start - -Test file-based routing by setting up route mocking utilities, testing generated route trees, and implementing patterns specific to file-based route structures and conventions. - ---- - -## Understanding File-Based Routing Testing - -File-based routing testing differs from code-based routing testing in several key ways: - -- **Generated Route Trees**: Routes are automatically generated from filesystem structure -- **File Conventions**: Routes follow specific file naming conventions (`index.tsx`, `route.tsx`, `$param.tsx`) -- **Route Discovery**: Routes are discovered through filesystem scanning rather than explicit imports -- **Type Generation**: Route types are automatically generated and need special testing considerations - ---- - -## Setting Up File-Based Route Testing - -### 1. Install Test Dependencies - -For file-based routing testing, you'll need the same base dependencies as regular router testing: - -```bash -npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom -``` - -### 2. Configure Test Environment - -Create `vitest.config.ts` with file-based routing support: - -```ts -import { defineConfig } from 'vitest/config' -import react from '@vitejs/plugin-react' -import { TanStackRouterVite } from '@tanstack/router-plugin/vite' - -export default defineConfig({ - plugins: [ - TanStackRouterVite({ - // Configure for test environment - routesDirectory: './src/routes', - generatedRouteTree: './src/routeTree.gen.ts', - disableLogging: true, - }), - react(), - ], - test: { - environment: 'jsdom', - setupFiles: ['./src/test/setup.ts'], - typecheck: { enabled: true }, - watch: false, - // Ensure route tree is generated before tests - globals: true, - }, -}) -``` - -### 3. Create Route Testing Utilities - -Create `src/test/file-route-utils.tsx`: - -```tsx -import React from 'react' -import { render, RenderOptions } from '@testing-library/react' -import { - createRouter, - RouterProvider, - createMemoryHistory, -} from '@tanstack/react-router' - -// Import the generated route tree -import { routeTree } from '../routeTree.gen' - -// Create test router with generated route tree -export function createTestRouterFromFiles(initialLocation = '/') { - const router = createRouter({ - routeTree, - history: createMemoryHistory({ - initialEntries: [initialLocation], - }), - context: { - // Add any required context for your routes - }, - }) - - return router -} - -// Custom render function for file-based routes -interface RenderWithFileRoutesOptions extends Omit { - initialLocation?: string - routerContext?: any -} - -export function renderWithFileRoutes( - ui: React.ReactElement, - { - initialLocation = '/', - routerContext = {}, - ...renderOptions - }: RenderWithFileRoutesOptions = {}, -) { - const router = createRouter({ - routeTree, - history: createMemoryHistory({ - initialEntries: [initialLocation], - }), - context: routerContext, - }) - - function Wrapper({ children }: { children: React.ReactNode }) { - return {children} - } - - return { - ...render(ui, { wrapper: Wrapper, ...renderOptions }), - router, - } -} - -// Helper to test specific file routes -export function createMockFileRoute( - path: string, - component: React.ComponentType, -) { - // This is useful for isolated testing when you don't want to use the full route tree - return { - path, - component, - // Add other common route properties as needed - } -} -``` - ---- - -## Testing File-Based Route Structure - -### 1. Test Route Tree Generation - -```tsx -import { describe, it, expect } from 'vitest' -import { routeTree } from '../routeTree.gen' - -describe('Generated Route Tree', () => { - it('should generate route tree from file structure', () => { - // Test that route tree exists and has expected structure - expect(routeTree).toBeDefined() - expect(routeTree.children).toBeDefined() - }) - - it('should include all expected routes', () => { - // Get all route paths from the generated tree - const getAllRoutePaths = (tree: any, paths: string[] = []): string[] => { - if (tree.path) { - paths.push(tree.path) - } - if (tree.children) { - tree.children.forEach((child: any) => { - getAllRoutePaths(child, paths) - }) - } - return paths - } - - const routePaths = getAllRoutePaths(routeTree) - - // Test that expected routes are present - expect(routePaths).toContain('/') - expect(routePaths).toContain('/about') - // Add assertions for your specific routes - }) - - it('should have correct route hierarchy', () => { - // Test parent-child relationships - const homeRoute = routeTree.children?.find( - (child: any) => child.path === '/', - ) - expect(homeRoute).toBeDefined() - - // Test for specific route structure based on your file organization - // For example, if you have /posts/$postId routes: - // const postsRoute = routeTree.children?.find((child: any) => child.path === '/posts') - // expect(postsRoute?.children).toBeDefined() - }) -}) -``` - -### 2. Test File Route Conventions - -```tsx -import { describe, it, expect } from 'vitest' -import { screen } from '@testing-library/react' -import { renderWithFileRoutes } from '../test/file-route-utils' - -describe('File Route Conventions', () => { - it('should render index route at root path', () => { - renderWithFileRoutes(
, { - initialLocation: '/', - }) - - // Test that the index route component renders - // This depends on what your src/routes/index.tsx exports - expect(screen.getByText('Welcome Home!')).toBeInTheDocument() - }) - - it('should handle route parameters from filename', () => { - // If you have a route like src/routes/posts/$postId.tsx - renderWithFileRoutes(
, { - initialLocation: '/posts/123', - }) - - // Test that parameter is correctly parsed from file-based route - expect(screen.getByText(/Post.*123/)).toBeInTheDocument() - }) - - it('should handle nested routes from directory structure', () => { - // If you have src/routes/dashboard/settings.tsx - renderWithFileRoutes(
, { - initialLocation: '/dashboard/settings', - }) - - expect(screen.getByText(/Settings/)).toBeInTheDocument() - }) - - it('should handle layout routes', () => { - // If you have src/routes/_layout.tsx - renderWithFileRoutes(
, { - initialLocation: '/some-nested-route', - }) - - // Test that layout is rendered for nested routes - expect(screen.getByTestId('layout-header')).toBeInTheDocument() - }) -}) -``` - ---- - -## Testing File-Based Route Components - -### 1. Test Individual Route Files - -```tsx -import { describe, it, expect } from 'vitest' -import { screen } from '@testing-library/react' -import { createFileRoute } from '@tanstack/react-router' -import { renderWithFileRoutes } from '../test/file-route-utils' - -describe('Individual Route Components', () => { - it('should test about route component', () => { - renderWithFileRoutes(
, { - initialLocation: '/about', - }) - - expect(screen.getByText('About')).toBeInTheDocument() - }) - - it('should test route with loader data', () => { - // For a route like src/routes/posts/index.tsx with loader - renderWithFileRoutes(
, { - initialLocation: '/posts', - }) - - // Wait for loader data to load - expect(screen.getByText(/Posts List/)).toBeInTheDocument() - }) - - it('should test route with search params validation', () => { - // For a route with validateSearch in src/routes/search.tsx - renderWithFileRoutes(
, { - initialLocation: '/search?q=react&page=1', - }) - - expect(screen.getByDisplayValue('react')).toBeInTheDocument() - }) -}) -``` - -### 2. Test Route-Specific Hooks - -```tsx -import { describe, it, expect } from 'vitest' -import { screen } from '@testing-library/react' -import { renderWithFileRoutes } from '../test/file-route-utils' - -describe('Route-Specific Hooks', () => { - it('should test useParams in parameterized route', () => { - // Create a test component that uses Route.useParams() - function TestComponent() { - // This would be available in the actual route component - const params = Route.useParams() - return
{params.postId}
- } - - renderWithFileRoutes(, { - initialLocation: '/posts/abc123', - }) - - expect(screen.getByTestId('param-value')).toHaveTextContent('abc123') - }) - - it('should test useLoaderData in route with loader', () => { - renderWithFileRoutes(
, { - initialLocation: '/posts/123', - }) - - // Test that loader data is available in the component - expect(screen.getByText(/Post Title/)).toBeInTheDocument() - }) - - it('should test useSearch in route with search validation', () => { - renderWithFileRoutes(
, { - initialLocation: '/search?q=typescript&sort=date', - }) - - // Test that search params are correctly parsed - expect(screen.getByDisplayValue('typescript')).toBeInTheDocument() - expect(screen.getByText(/sorted by date/)).toBeInTheDocument() - }) -}) -``` - ---- - -## Testing Route Navigation with File-Based Routes - -### 1. Test Link Navigation - -```tsx -import { describe, it, expect } from 'vitest' -import { screen } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import { renderWithFileRoutes } from '../test/file-route-utils' - -describe('File-Based Route Navigation', () => { - it('should navigate between file-based routes', async () => { - const user = userEvent.setup() - - const { router } = renderWithFileRoutes(
, { - initialLocation: '/', - }) - - // Initial state - should be on home route - expect(screen.getByText('Welcome Home!')).toBeInTheDocument() - expect(router.state.location.pathname).toBe('/') - - // Click navigation link - await user.click(screen.getByRole('link', { name: /about/i })) - - // Should navigate to about route - expect(screen.getByText('About')).toBeInTheDocument() - expect(router.state.location.pathname).toBe('/about') - }) - - it('should handle dynamic route navigation', async () => { - const user = userEvent.setup() - - renderWithFileRoutes(
, { - initialLocation: '/posts', - }) - - // Click on a post link (assuming your posts route renders links) - await user.click(screen.getByRole('link', { name: /View Post 1/i })) - - // Should navigate to dynamic post route - expect(screen.getByText(/Post 1 Details/)).toBeInTheDocument() - }) - - it('should handle nested route navigation', async () => { - const user = userEvent.setup() - - renderWithFileRoutes(
, { - initialLocation: '/dashboard', - }) - - // Navigate to nested route - await user.click(screen.getByRole('link', { name: /settings/i })) - - expect(screen.getByText(/Dashboard Settings/)).toBeInTheDocument() - }) -}) -``` - -### 2. Test Programmatic Navigation - -```tsx -import { describe, it, expect } from 'vitest' -import { screen } from '@testing-library/react' -import userEvent from '@testing-library/user-event' -import { renderWithFileRoutes } from '../test/file-route-utils' - -describe('Programmatic Navigation', () => { - it('should programmatically navigate between file routes', async () => { - const user = userEvent.setup() - - const { router } = renderWithFileRoutes(
, { - initialLocation: '/', - }) - - // Trigger programmatic navigation (button in your component) - await user.click(screen.getByRole('button', { name: /Go to Posts/i })) - - expect(router.state.location.pathname).toBe('/posts') - }) - - it('should navigate with search params', async () => { - const user = userEvent.setup() - - const { router } = renderWithFileRoutes(
, { - initialLocation: '/search', - }) - - // Trigger search with params - await user.type(screen.getByRole('textbox'), 'test query') - await user.click(screen.getByRole('button', { name: /search/i })) - - expect(router.state.location.search).toMatchObject({ - q: 'test query', - }) - }) -}) -``` - ---- - -## Testing File-Based Route Guards and Loaders - -### 1. Test Route Guards - -```tsx -import { describe, it, expect, vi } from 'vitest' -import { screen } from '@testing-library/react' -import { renderWithFileRoutes } from '../test/file-route-utils' - -describe('File-Based Route Guards', () => { - it('should redirect unauthenticated users from protected routes', () => { - // Mock unauthenticated state - const mockAuth = { isAuthenticated: false, user: null } - - renderWithFileRoutes(
, { - initialLocation: '/dashboard', - routerContext: { auth: mockAuth }, - }) - - // Should redirect to login (based on your beforeLoad implementation) - expect(screen.getByText(/Please log in/)).toBeInTheDocument() - }) - - it('should allow authenticated users to access protected routes', () => { - const mockAuth = { - isAuthenticated: true, - user: { id: '1', name: 'John' }, - } - - renderWithFileRoutes(
, { - initialLocation: '/dashboard', - routerContext: { auth: mockAuth }, - }) - - expect(screen.getByText(/Welcome to Dashboard/)).toBeInTheDocument() - }) -}) -``` - -### 2. Test Route Loaders - -```tsx -import { describe, it, expect, vi } from 'vitest' -import { screen, waitFor } from '@testing-library/react' -import { renderWithFileRoutes } from '../test/file-route-utils' - -describe('File-Based Route Loaders', () => { - it('should load data for route with loader', async () => { - // Mock the API function used in your route loader - const mockFetchPost = vi.fn().mockResolvedValue({ - id: '123', - title: 'Test Post', - content: 'Test content', - }) - - // If your route loader uses a global API function, mock it - vi.mock('../api/posts', () => ({ - fetchPost: mockFetchPost, - })) - - renderWithFileRoutes(
, { - initialLocation: '/posts/123', - }) - - await waitFor(() => { - expect(screen.getByText('Test Post')).toBeInTheDocument() - }) - - expect(mockFetchPost).toHaveBeenCalledWith('123') - }) - - it('should handle loader errors', async () => { - const mockFetchPost = vi.fn().mockRejectedValue(new Error('Post not found')) - - vi.mock('../api/posts', () => ({ - fetchPost: mockFetchPost, - })) - - renderWithFileRoutes(
, { - initialLocation: '/posts/invalid', - }) - - await waitFor(() => { - expect(screen.getByText(/Error.*Post not found/)).toBeInTheDocument() - }) - }) -}) -``` - ---- - -## Testing File Route Validation - -### 1. Test Search Parameter Validation - -```tsx -import { describe, it, expect } from 'vitest' -import { screen } from '@testing-library/react' -import { renderWithFileRoutes } from '../test/file-route-utils' - -describe('File Route Validation', () => { - it('should validate search parameters', () => { - // Test with valid search params - renderWithFileRoutes(
, { - initialLocation: '/search?q=react&page=1&sort=date', - }) - - expect(screen.getByDisplayValue('react')).toBeInTheDocument() - expect(screen.getByText(/Page 1/)).toBeInTheDocument() - }) - - it('should handle invalid search parameters', () => { - // Test with invalid search params (e.g., invalid page number) - renderWithFileRoutes(
, { - initialLocation: '/search?page=invalid&sort=unknown', - }) - - // Should fall back to defaults based on your validation schema - expect(screen.getByText(/Page 1/)).toBeInTheDocument() // default page - }) - - it('should validate route parameters', () => { - // Test with valid route param - renderWithFileRoutes(
, { - initialLocation: '/posts/123', - }) - - expect(screen.getByText(/Post 123/)).toBeInTheDocument() - }) -}) -``` - ---- - -## Testing File Route Error Boundaries - -### 1. Test Route-Level Error Handling - -```tsx -import { describe, it, expect, vi } from 'vitest' -import { screen } from '@testing-library/react' -import { renderWithFileRoutes } from '../test/file-route-utils' - -describe('File Route Error Handling', () => { - it('should handle component errors with error boundary', () => { - // Mock console.error to avoid noise in test output - const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}) - - // Force an error in a route component - vi.mock('../routes/error-prone.tsx', () => ({ - Route: { - component: () => { - throw new Error('Test error') - }, - }, - })) - - renderWithFileRoutes(
, { - initialLocation: '/error-prone', - }) - - expect(screen.getByText(/Something went wrong/)).toBeInTheDocument() - - consoleSpy.mockRestore() - }) - - it('should handle loader errors with error component', async () => { - const mockFailingLoader = vi - .fn() - .mockRejectedValue(new Error('Load failed')) - - vi.mock('../api/data', () => ({ - loadData: mockFailingLoader, - })) - - renderWithFileRoutes(
, { - initialLocation: '/data-route', - }) - - expect(screen.getByText(/Failed to load data/)).toBeInTheDocument() - }) -}) -``` - ---- - -## Testing with Generated Route Types - -### 1. Test Type Safety - -```tsx -import { describe, it, expect } from 'vitest' -import { useNavigate } from '@tanstack/react-router' -import { renderWithFileRoutes } from '../test/file-route-utils' - -describe('Generated Route Types', () => { - it('should provide type-safe navigation', () => { - function TestComponent() { - const navigate = useNavigate() - - const handleNavigate = () => { - // This should be type-safe based on your generated routes - navigate({ - to: '/posts/$postId', - params: { postId: '123' }, - search: { tab: 'comments' }, - }) - } - - return ( - - ) - } - - const { router } = renderWithFileRoutes(, { - initialLocation: '/', - }) - - // Test the navigation works correctly - const button = screen.getByTestId('navigate-btn') - fireEvent.click(button) - - expect(router.state.location.pathname).toBe('/posts/123') - expect(router.state.location.search).toEqual({ tab: 'comments' }) - }) -}) -``` - ---- - -## Testing Route Tree Changes - -### 1. Test Route Generation During Development - -```tsx -import { describe, it, expect } from 'vitest' -import { routeTree } from '../routeTree.gen' - -describe('Route Tree Development', () => { - it('should regenerate routes when files change', () => { - // This test ensures your route tree is properly generated - // You can add specific assertions based on your file structure - - expect(routeTree).toBeDefined() - expect(typeof routeTree.children).toBe('object') - - // Test specific routes exist - const routes = getAllRouteIds(routeTree) - expect(routes).toContain('/') - expect(routes).toContain('/about') - // Add assertions for your specific routes - }) - - // Helper function to get all route IDs from tree - function getAllRouteIds(tree: any, ids: string[] = []): string[] { - if (tree.id) { - ids.push(tree.id) - } - if (tree.children) { - Object.values(tree.children).forEach((child: any) => { - getAllRouteIds(child, ids) - }) - } - return ids - } -}) -``` - ---- - -## E2E Testing for File-Based Routes - -### 1. Playwright Configuration for File-Based Routes - -Create `e2e/file-routing.spec.ts`: - -```ts -import { test, expect } from '@playwright/test' - -test.describe('File-Based Route E2E', () => { - test('should navigate through file-based route structure', async ({ - page, - }) => { - await page.goto('/') - - // Test home route (from src/routes/index.tsx) - await expect(page.locator('h3')).toContainText('Welcome Home!') - - // Navigate to about route (from src/routes/about.tsx) - await page.click('text=About') - await expect(page).toHaveURL('/about') - await expect(page.locator('h3')).toContainText('About') - - // Test browser navigation - await page.goBack() - await expect(page).toHaveURL('/') - }) - - test('should handle dynamic routes from file structure', async ({ page }) => { - await page.goto('/posts') - - // Click on a dynamic post link (from src/routes/posts/$postId.tsx) - await page.click('[data-testid="post-link-1"]') - await expect(page).toHaveURL('/posts/1') - await expect(page.locator('h1')).toContainText('Post 1') - }) - - test('should handle nested routes', async ({ page }) => { - await page.goto('/dashboard') - - // Navigate to nested route (from src/routes/dashboard/settings.tsx) - await page.click('text=Settings') - await expect(page).toHaveURL('/dashboard/settings') - await expect(page.locator('h2')).toContainText('Settings') - }) -}) -``` - ---- - -## Common File-Based Routing Testing Patterns - -### 1. Mock Route Files for Testing - -```tsx -// src/test/mock-file-routes.tsx -import { createFileRoute } from '@tanstack/react-router' - -// Mock individual route for isolated testing -export const createMockFileRoute = ( - path: string, - component: React.ComponentType, - options: any = {}, -) => { - return createFileRoute(path)({ - component, - ...options, - }) -} - -// Common test route components -export const TestHomeRoute = createMockFileRoute('/', () => ( -
Home Page
-)) - -export const TestAboutRoute = createMockFileRoute('/about', () => ( -
About Page
-)) - -export const TestDynamicRoute = createMockFileRoute('/posts/$postId', () => { - const { postId } = Route.useParams() - return
Post {postId}
-}) -``` - -### 2. Test Route Discovery - -```tsx -import { describe, it, expect } from 'vitest' - -describe('Route Discovery', () => { - it('should discover all routes from file structure', () => { - // Test that your route tree includes all expected routes - // This helps catch when routes are accidentally not being generated - - const expectedRoutes = [ - '/', - '/about', - '/posts', - '/posts/$postId', - '/dashboard', - '/dashboard/settings', - ] - - expectedRoutes.forEach((routePath) => { - const routeExists = checkRouteExists(routeTree, routePath) - expect(routeExists).toBe(true) - }) - }) -}) - -function checkRouteExists(tree: any, path: string): boolean { - // Implementation to check if route exists in tree - // This depends on your route tree structure - return true // Simplified -} -``` - ---- - -## Best Practices for File-Based Route Testing - -### 1. Test Organization - -``` -src/ -β”œβ”€β”€ routes/ -β”‚ β”œβ”€β”€ __root.tsx -β”‚ β”œβ”€β”€ index.tsx -β”‚ β”œβ”€β”€ about.tsx -β”‚ β”œβ”€β”€ posts/ -β”‚ β”‚ β”œβ”€β”€ index.tsx -β”‚ β”‚ └── $postId.tsx -β”œβ”€β”€ test/ -β”‚ β”œβ”€β”€ setup.ts -β”‚ β”œβ”€β”€ file-route-utils.tsx -β”‚ └── routes/ -β”‚ β”œβ”€β”€ index.test.tsx -β”‚ β”œβ”€β”€ about.test.tsx -β”‚ └── posts/ -β”‚ β”œβ”€β”€ index.test.tsx -β”‚ └── $postId.test.tsx -``` - -### 2. Common Test Patterns - -```tsx -// Test file for each route file -describe('Posts Route (/posts)', () => { - it('should render posts list', () => { - renderWithFileRoutes(
, { - initialLocation: '/posts', - }) - - expect(screen.getByText(/Posts/)).toBeInTheDocument() - }) - - it('should handle loading state', () => { - // Test pending state for route with loader - }) - - it('should handle error state', () => { - // Test error handling for route - }) -}) - -// Test route groups -describe('Dashboard Routes', () => { - describe('/dashboard', () => { - // Dashboard index tests - }) - - describe('/dashboard/settings', () => { - // Settings route tests - }) -}) -``` - ---- - -## Troubleshooting File-Based Route Testing - -### Common Issues - -**Problem**: Route tree not found in tests - -```bash -Error: Cannot find module '../routeTree.gen' -``` - -**Solution**: Ensure route tree generation in test setup: - -```ts -// vitest.config.ts -export default defineConfig({ - plugins: [ - TanStackRouterVite(), // Ensure this runs before tests - react(), - ], - test: { - setupFiles: ['./src/test/setup.ts'], - }, -}) -``` - -**Problem**: Routes not updating in tests after file changes - -**Solution**: Clear module cache in test setup: - -```ts -// src/test/setup.ts -beforeEach(() => { - vi.clearAllMocks() - // Clear route tree cache if needed - delete require.cache[require.resolve('../routeTree.gen')] -}) -``` - -**Problem**: Type errors in tests with generated routes - -**Solution**: Ensure proper TypeScript configuration: - -```json -{ - "compilerOptions": { - "types": ["vitest/globals", "@testing-library/jest-dom"], - "moduleResolution": "bundler" - }, - "include": ["src/**/*", "src/routeTree.gen.ts"] -} -``` - ---- - -## Next Steps - -After setting up file-based route testing, you might want to: - -- [How to Set Up Testing with Code-Based Routing](../setup-testing.md) - Testing patterns for manually defined routes -- [How to Debug Router Issues](../debug-router-issues.md) - Debug file-based routing issues -- [File-Based Routing Guide](../../routing/file-based-routing.md) - Learn more about file-based routing - -## Related Resources - -- [TanStack Router File-Based Routing](../../routing/file-based-routing.md) - Complete file-based routing guide -- [File Naming Conventions](../../routing/file-naming-conventions.md) - Understanding file structure -- [Testing Library](https://testing-library.com/) - Component testing utilities -- [Vitest](https://vitest.dev/) - Testing framework documentation diff --git a/docs/router/framework/react/how-to/use-environment-variables.md b/docs/router/framework/react/how-to/use-environment-variables.md deleted file mode 100644 index 899d66683bf..00000000000 --- a/docs/router/framework/react/how-to/use-environment-variables.md +++ /dev/null @@ -1,689 +0,0 @@ ---- -title: How to Use Environment Variables ---- - -Learn how to configure and use environment variables in your TanStack Router application for API endpoints, feature flags, and build configuration across different bundlers. - -## Quick Start - -Environment variables in TanStack Router are primarily used for client-side configuration and must follow bundler-specific naming conventions for security. - -```bash -# .env -VITE_API_URL=https://api.example.com -VITE_ENABLE_DEVTOOLS=true -``` - -```typescript -// Route configuration -import { createFileRoute } from '@tanstack/react-router' - -export const Route = createFileRoute('/posts')({ - loader: async () => { - const apiUrl = import.meta.env.VITE_API_URL - const response = await fetch(`${apiUrl}/posts`) - return response.json() - }, - component: PostsList, -}) -``` - -## Environment Variable Access Patterns - -### Vite-Based Projects (Most Common) - -With Vite, environment variables must be prefixed with `VITE_` to be accessible in client code: - -```typescript -// Route loaders -export const Route = createFileRoute('/dashboard')({ - loader: async () => { - const apiUrl = import.meta.env.VITE_API_URL // βœ… Works - const apiKey = import.meta.env.VITE_PUBLIC_API_KEY // βœ… Works - - // This would be undefined (security feature): - // const secret = import.meta.env.SECRET_KEY // ❌ Undefined - - return fetchDashboardData(apiUrl, apiKey) - }, -}) - -// Components -export function ApiStatus() { - const isDev = import.meta.env.DEV // βœ… Built-in Vite variable - const isProd = import.meta.env.PROD // βœ… Built-in Vite variable - const mode = import.meta.env.MODE // βœ… development/production - - return ( -
- Environment: {mode} - {isDev && } -
- ) -} -``` - -### Webpack-Based Projects - -Configure webpack's DefinePlugin to inject environment variables. **Note:** Webpack doesn't support `import.meta.env` by default, so use `process.env` patterns: - -```typescript -// webpack.config.js -const webpack = require('webpack') - -module.exports = { - plugins: [ - new webpack.DefinePlugin({ - 'process.env.API_URL': JSON.stringify(process.env.API_URL), - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), - 'process.env.ENABLE_FEATURE': JSON.stringify(process.env.ENABLE_FEATURE), - }), - ], -} - -// Usage in routes -export const Route = createFileRoute('/api-data')({ - loader: async () => { - const response = await fetch(`${process.env.API_URL}/data`) - return response.json() - }, - component: () => { - const enableFeature = process.env.ENABLE_FEATURE === 'true' - return enableFeature ? : - }, -}) -``` - -### Rspack-Based Projects - -Rspack uses the `PUBLIC_` prefix convention. **Note:** `import.meta.env` support depends on your Rspack configuration and runtime - you may need to configure `builtins.define` properly: - -```bash -# .env -PUBLIC_API_URL=https://api.example.com -PUBLIC_FEATURE_FLAG=true -``` - -```typescript -// Route usage -export const Route = createFileRoute('/features')({ - loader: async () => { - const apiUrl = import.meta.env.PUBLIC_API_URL - return fetch(`${apiUrl}/features`).then(r => r.json()) - }, - component: () => { - const enableFeature = import.meta.env.PUBLIC_FEATURE_FLAG === 'true' - return enableFeature ? : - }, -}) -``` - -### ESBuild Projects - -Configure defines manually: - -```typescript -// build script -import { build } from 'esbuild' - -await build({ - entryPoints: ['src/main.tsx'], - define: { - 'process.env.NODE_ENV': '"production"', - 'process.env.API_URL': `"${process.env.API_URL}"`, - }, -}) -``` - -## Common Patterns - -### API Configuration in Route Loaders - -```typescript -// src/routes/posts/index.tsx -import { createFileRoute } from '@tanstack/react-router' - -const fetchPosts = async () => { - const baseUrl = import.meta.env.VITE_API_URL - const apiKey = import.meta.env.VITE_API_KEY - - const response = await fetch(`${baseUrl}/posts`, { - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'Content-Type': 'application/json', - }, - }) - - if (!response.ok) { - throw new Error('Failed to fetch posts') - } - - return response.json() -} - -export const Route = createFileRoute('/posts/')({ - loader: fetchPosts, - errorComponent: ({ error }) => ( -
Error loading posts: {error.message}
- ), -}) -``` - -### Environment-Based Route Configuration - -```typescript -// src/routes/__root.tsx -import { createRootRoute, Outlet } from '@tanstack/react-router' -import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' - -export const Route = createRootRoute({ - component: () => ( - <> - - {/* Only show devtools in development */} - {import.meta.env.DEV && } - - ), -}) -``` - -### Feature Flags in Routes - -```typescript -// src/lib/features.ts -export const features = { - enableNewDashboard: import.meta.env.VITE_ENABLE_NEW_DASHBOARD === 'true', - enableAnalytics: import.meta.env.VITE_ENABLE_ANALYTICS === 'true', - debugMode: import.meta.env.DEV, -} - -// src/routes/dashboard/index.tsx -import { createFileRoute, redirect } from '@tanstack/react-router' -import { features } from '../../lib/features' - -export const Route = createFileRoute('/dashboard/')({ - beforeLoad: () => { - // Redirect to old dashboard if new one is disabled - if (!features.enableNewDashboard) { - throw redirect({ to: '/dashboard/legacy' }) - } - }, - component: NewDashboard, -}) -``` - -### Authentication Configuration - -```typescript -// src/lib/auth.ts -export const authConfig = { - domain: import.meta.env.VITE_AUTH0_DOMAIN, - clientId: import.meta.env.VITE_AUTH0_CLIENT_ID, - redirectUri: `${window.location.origin}/callback`, -} - -// src/routes/_authenticated.tsx -import { createFileRoute, redirect } from '@tanstack/react-router' -import { authConfig } from '../lib/auth' - -export const Route = createFileRoute('/_authenticated')({ - beforeLoad: async ({ location }) => { - const isAuthenticated = await checkAuthStatus() - - if (!isAuthenticated) { - // Redirect to auth provider - const authUrl = `https://${authConfig.domain}/authorize?client_id=${authConfig.clientId}&redirect_uri=${authConfig.redirectUri}` - window.location.href = authUrl - return - } - }, -}) -``` - -### Search Params with Environment Config - -```typescript -// src/routes/search.tsx -import { createFileRoute } from '@tanstack/react-router' -import { z } from 'zod' - -const searchSchema = z.object({ - q: z.string().optional(), - category: z.string().optional(), -}) - -export const Route = createFileRoute('/search')({ - validateSearch: searchSchema, - loader: async ({ search }) => { - const apiUrl = import.meta.env.VITE_SEARCH_API_URL - const params = new URLSearchParams({ - q: search.q || '', - category: search.category || 'all', - api_key: import.meta.env.VITE_SEARCH_API_KEY, - }) - - const response = await fetch(`${apiUrl}/search?${params}`) - return response.json() - }, -}) -``` - -## Environment File Setup - -### File Hierarchy (Vite) - -Vite loads environment files in this order: - -``` -.env.local # Local overrides (add to .gitignore) -.env.production # Production-specific -.env.development # Development-specific -.env # Default (commit to git) -``` - -### Example Configuration - -**.env** (committed to repository): - -```bash -# API Configuration -VITE_API_URL=https://api.example.com -VITE_API_VERSION=v1 - -# Feature Flags -VITE_ENABLE_NEW_UI=false -VITE_ENABLE_ANALYTICS=true - -# Auth Configuration (public keys only) -VITE_AUTH0_DOMAIN=your-domain.auth0.com -VITE_AUTH0_CLIENT_ID=your-client-id - -# Build Configuration -VITE_APP_NAME=TanStack Router App -VITE_APP_VERSION=1.0.0 -``` - -**.env.local** (add to .gitignore): - -```bash -# Development overrides -VITE_API_URL=http://localhost:3001 -VITE_ENABLE_NEW_UI=true -VITE_DEBUG_MODE=true -``` - -**.env.production**: - -```bash -# Production-specific -VITE_API_URL=https://api.prod.example.com -VITE_ENABLE_ANALYTICS=true -VITE_ENABLE_NEW_UI=true -``` - -## Type Safety - -### Vite TypeScript Declarations - -Create `src/vite-env.d.ts`: - -```typescript -/// - -interface ImportMetaEnv { - // API Configuration - readonly VITE_API_URL: string - readonly VITE_API_VERSION: string - readonly VITE_API_KEY?: string - - // Feature Flags - readonly VITE_ENABLE_NEW_UI: string - readonly VITE_ENABLE_ANALYTICS: string - readonly VITE_DEBUG_MODE?: string - - // Authentication - readonly VITE_AUTH0_DOMAIN: string - readonly VITE_AUTH0_CLIENT_ID: string - - // App Configuration - readonly VITE_APP_NAME: string - readonly VITE_APP_VERSION: string -} - -interface ImportMeta { - readonly env: ImportMetaEnv -} -``` - -### Runtime Validation - -Use Zod to validate environment variables at startup with fallbacks and optional values: - -```typescript -// src/config/env.ts -import { z } from 'zod' - -const envSchema = z.object({ - // Required variables - VITE_API_URL: z.string().url(), - VITE_AUTH0_DOMAIN: z.string(), - VITE_AUTH0_CLIENT_ID: z.string(), - VITE_APP_NAME: z.string(), - - // Optional with defaults - VITE_API_VERSION: z.string().default('v1'), - VITE_ENABLE_NEW_UI: z.string().default('false'), - VITE_ENABLE_ANALYTICS: z.string().default('true'), - - // Optional variables - VITE_DEBUG_MODE: z.string().optional(), - VITE_SENTRY_DSN: z.string().optional(), -}) - -// Validate at app startup with fallbacks -export const env = envSchema.parse({ - ...import.meta.env, - // Provide fallbacks for missing optional values - VITE_API_VERSION: import.meta.env.VITE_API_VERSION || 'v1', - VITE_ENABLE_NEW_UI: import.meta.env.VITE_ENABLE_NEW_UI || 'false', - VITE_ENABLE_ANALYTICS: import.meta.env.VITE_ENABLE_ANALYTICS || 'true', -}) - -// Typed helper functions -export const isFeatureEnabled = (flag: keyof typeof env) => { - return env[flag] === 'true' -} - -// Type-safe boolean conversion -export const getBooleanEnv = ( - value: string | undefined, - defaultValue = false, -): boolean => { - if (value === undefined) return defaultValue - return value === 'true' -} -``` - -### Usage with Type Safety - -```typescript -// src/routes/api-data.tsx -import { createFileRoute } from '@tanstack/react-router' -import { env, isFeatureEnabled } from '../config/env' - -export const Route = createFileRoute('/api-data')({ - loader: async () => { - // TypeScript knows these are strings and exist - const response = await fetch(`${env.VITE_API_URL}/${env.VITE_API_VERSION}/data`) - return response.json() - }, - component: () => { - return ( -
-

{env.VITE_APP_NAME}

- {isFeatureEnabled('VITE_ENABLE_NEW_UI') && } -
- ) - }, -}) -``` - -## Bundler-Specific Configuration - -### Vite Configuration - -```typescript -// vite.config.ts -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' -import { TanStackRouterVite } from '@tanstack/router-vite-plugin' - -export default defineConfig({ - plugins: [ - react(), - // TanStackRouterVite generates route tree and enables file-based routing - TanStackRouterVite(), - ], - // Environment variables are handled automatically - // Custom environment variable handling: - define: { - // Global constants (these become available as global variables) - __APP_VERSION__: JSON.stringify(process.env.npm_package_version), - }, -}) -``` - -### Webpack Configuration - -```typescript -// webpack.config.js -const { TanStackRouterWebpack } = require('@tanstack/router-webpack-plugin') -const webpack = require('webpack') - -module.exports = { - plugins: [ - // TanStackRouterWebpack generates route tree and enables file-based routing - new TanStackRouterWebpack(), - new webpack.DefinePlugin({ - // Inject environment variables (use process.env for Webpack) - 'process.env.API_URL': JSON.stringify(process.env.API_URL), - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), - 'process.env.ENABLE_FEATURE': JSON.stringify(process.env.ENABLE_FEATURE), - }), - ], -} -``` - -### Rspack Configuration - -```typescript -// rspack.config.js -const { TanStackRouterRspack } = require('@tanstack/router-rspack-plugin') - -module.exports = { - plugins: [ - // TanStackRouterRspack generates route tree and enables file-based routing - new TanStackRouterRspack(), - ], - // Rspack automatically handles PUBLIC_ prefixed variables for import.meta.env - // Custom handling for additional variables: - builtins: { - define: { - // Define additional variables (these become global replacements) - 'process.env.API_URL': JSON.stringify(process.env.PUBLIC_API_URL), - __BUILD_TIME__: JSON.stringify(new Date().toISOString()), - }, - }, -} -``` - -## Production Checklist - -- [ ] All client-exposed variables use appropriate prefix (`VITE_`, `PUBLIC_`, etc.) -- [ ] No sensitive data (API secrets, private keys) in environment variables -- [ ] `.env.local` is in `.gitignore` -- [ ] Production environment variables are configured on hosting platform -- [ ] Required environment variables are validated at build time -- [ ] TypeScript declarations are up to date -- [ ] Feature flags are properly configured for production -- [ ] API URLs point to production endpoints - -## Common Problems - -### Environment Variable is Undefined - -**Problem**: `import.meta.env.MY_VARIABLE` returns `undefined` - -**Solutions**: - -1. **Add correct prefix**: Use `VITE_` for Vite, `PUBLIC_` for Rspack. - Vite's default prefix may be changed in the config: - ```ts - // vite.config.ts - export const config = { - // ...rest of your config - envPrefix: 'MYPREFIX_', // this means `MYPREFIX_MY_VARIABLE` is the new correct way - } - ``` -2. **Restart development server** after adding new variables -3. **Check file location**: `.env` file must be in project root -4. **Verify bundler configuration**: Ensure variables are properly injected -5. **Verify variable**: - -- **In dev**: is in correct `.env` file or environment -- **For prod**: is in correct `.env` file or current environment **_at bundle time_**. That's right, `VITE_`/`PUBLIC_`-prefixed variables are replaced in a macro-like fashion at bundle time, and will _never_ be read at runtime on your server. This is a common mistake, so make sure this is not your case. - -**Example**: - -```bash -# ❌ Won't work (no prefix) -API_KEY=abc123 - -# βœ… Works with Vite -VITE_API_KEY=abc123 - -# βœ… Works with Rspack -PUBLIC_API_KEY=abc123 - -# ❌ Won't bundle the variable (assuming it is not set in the environment of the build) -npm run build - -# βœ… Works with Vite and will bundle the variable for production -VITE_API_KEY=abc123 npm run build - -# βœ… Works with Rspack and will bundle the variable for production -PUBLIC_API_KEY=abc123 npm run build -``` - -### Runtime Client Environment Variables at Runtime in Production - -**Problem**: If `VITE_`/`PUBLIC_` variables are replaced at bundle time only, how to make runtime variables available on the client ? - -**Solutions**: - -Pass variables from the server down to the client: - -1. Add your variable to the correct `env.` file -2. Create an endpoint on your server to read the value from the client - -**Example**: - -You may use your prefered backend framework/libray, but here it is using Tanstack Start server functions: - -```tsx -const getRuntimeVar = createServerFn({ method: 'GET' }).handler(() => { - return process.env.MY_RUNTIME_VAR // notice `process.env` on the server, and no `VITE_`/`PUBLIC_` prefix -}) - -export const Route = createFileRoute('/')({ - loader: async () => { - const foo = await getRuntimeVar() - return { foo } - }, - component: RouteComponent, -}) - -function RouteComponent() { - const { foo } = Route.useLoaderData() - // ... use your variable however you want -} -``` - -### Variable Not Updating - -**Problem**: Environment variable changes aren't reflected in app - -**Solutions**: - -1. **Restart development server** - Required for new variables -2. **Check file hierarchy** - `.env.local` overrides `.env` -3. **Clear browser cache** - Hard refresh (Ctrl+Shift+R) -4. **Verify correct file** - Make sure you're editing the right `.env` file - -### TypeScript Errors - -**Problem**: `Property 'VITE_MY_VAR' does not exist on type 'ImportMetaEnv'` - -**Solution**: Add declaration to `src/vite-env.d.ts`: - -```typescript -interface ImportMetaEnv { - readonly VITE_MY_VAR: string -} -``` - -### Build Errors - -**Problem**: Missing environment variables during build - -**Solutions**: - -1. **Configure CI/CD**: Set variables in build environment -2. **Add validation**: Check required variables at build time -3. **Use .env files**: Ensure production `.env` files exist -4. **Check bundler config**: Verify environment variable injection - -### Security Issues - -**Problem**: Accidentally exposing sensitive data - -**Solutions**: - -1. **Never use secrets in client variables** - They're visible in browser -2. **Use server-side proxies** for sensitive API calls -3. **Audit bundle** - Check built files for leaked secrets -4. **Follow naming conventions** - Only prefixed variables are exposed - -### Runtime vs Build-time Confusion - -**Problem**: Variables not available at runtime - -**Solutions**: - -1. **Understand static replacement** - Variables are replaced at build time -2. **Use server-side for dynamic values** - Use APIs for runtime configuration -3. **Validate at startup** - Check all required variables exist - -### Environment Variables are Always Strings - -**Problem**: Unexpected behavior when comparing boolean or numeric values - -**Solutions**: - -1. **Always compare as strings**: Use `=== 'true'` not `=== true` -2. **Convert explicitly**: Use `parseInt()`, `parseFloat()`, or `Boolean()` -3. **Use helper functions**: Create typed conversion utilities - -**Example**: - -```typescript -// ❌ Won't work as expected -const isEnabled = import.meta.env.VITE_FEATURE_ENABLED // This is a string! -if (isEnabled) { - /* Always true if variable exists */ -} - -// βœ… Correct string comparison -const isEnabled = import.meta.env.VITE_FEATURE_ENABLED === 'true' - -// βœ… Safe numeric conversion -const port = parseInt(import.meta.env.VITE_PORT || '3000', 10) - -// βœ… Helper function approach -const getBooleanEnv = (value: string | undefined, defaultValue = false) => { - if (value === undefined) return defaultValue - return value.toLowerCase() === 'true' -} -``` - -## Common Next Steps - - - - - -## Related Resources - -- [TanStack Router File-Based Routing](../../routing/file-based-routing.md) - Learn about route configuration -- [Vite Environment Variables](https://vitejs.dev/guide/env-and-mode.html) - Official Vite documentation -- [Webpack DefinePlugin](https://webpack.js.org/plugins/define-plugin/) - Webpack environment configuration diff --git a/docs/router/framework/react/how-to/validate-search-params.md b/docs/router/framework/react/how-to/validate-search-params.md deleted file mode 100644 index bc78755f09e..00000000000 --- a/docs/router/framework/react/how-to/validate-search-params.md +++ /dev/null @@ -1,643 +0,0 @@ ---- -title: Validate Search Parameters with Schemas ---- - -Learn how to add robust schema validation to your search parameters using popular validation libraries like Zod, Valibot, and ArkType. This guide covers validation setup, error handling, type safety, and common validation patterns for production applications. - -**Prerequisites:** [Set Up Basic Search Parameters](../setup-basic-search-params.md) - Foundation concepts for reading and working with search params. - -## Quick Start - -Add robust validation with custom error messages, complex types, and production-ready error handling: - -```tsx -import { createFileRoute, useRouter } from '@tanstack/react-router' -import { zodValidator, fallback } from '@tanstack/zod-adapter' -import { z } from 'zod' - -const productSearchSchema = z.object({ - query: z.string().min(1, 'Search query required'), - category: z.enum(['electronics', 'clothing', 'books', 'home']).optional(), - minPrice: fallback(z.number().min(0, 'Price cannot be negative'), 0), - maxPrice: fallback(z.number().min(0, 'Price cannot be negative'), 1000), - inStock: fallback(z.boolean(), true), - tags: z.array(z.string()).optional(), - dateRange: z - .object({ - start: z.string().datetime().optional(), - end: z.string().datetime().optional(), - }) - .optional(), -}) - -export const Route = createFileRoute('/products')({ - validateSearch: zodValidator(productSearchSchema), - errorComponent: ({ error }) => { - const router = useRouter() - return ( -
-

Invalid Search Parameters

-

{error.message}

- -
- ) - }, - component: ProductsPage, -}) - -function ProductsPage() { - // All search params are validated, type-safe, and have fallback values applied - const { query, category, minPrice, maxPrice, inStock, tags, dateRange } = - Route.useSearch() - - return ( -
-

Products

-

Search: {query}

-

Category: {category || 'All'}

-

- Price Range: ${minPrice} - ${maxPrice} -

-

In Stock Only: {inStock ? 'Yes' : 'No'}

- {tags &&

Tags: {tags.join(', ')}

} - {dateRange && ( -

- Date Range: {dateRange.start} to {dateRange.end} -

- )} -
- ) -} -``` - -## Validation Library Options - -TanStack Router supports multiple validation libraries through adapters: - -### Zod (Recommended) - -Most popular with excellent TypeScript integration: - -```tsx -import { zodValidator, fallback } from '@tanstack/zod-adapter' -import { z } from 'zod' - -const searchSchema = z.object({ - query: z.string().min(1).max(100), - page: fallback(z.number().int().positive(), 1), - sortBy: z.enum(['name', 'date', 'relevance']).optional(), - filters: z.array(z.string()).optional(), -}) - -export const Route = createFileRoute('/search')({ - validateSearch: zodValidator(searchSchema), - component: SearchPage, -}) -``` - -### Valibot - -Lightweight alternative with modular design: - -```tsx -import { valibotValidator } from '@tanstack/valibot-adapter' -import * as v from 'valibot' - -const searchSchema = v.object({ - query: v.pipe(v.string(), v.minLength(1), v.maxLength(100)), - page: v.fallback(v.pipe(v.number(), v.integer(), v.minValue(1)), 1), - sortBy: v.optional(v.picklist(['name', 'date', 'relevance'])), - filters: v.optional(v.array(v.string())), -}) - -export const Route = createFileRoute('/search')({ - validateSearch: valibotValidator(searchSchema), - component: SearchPage, -}) -``` - -### ArkType - -TypeScript-first with runtime validation: - -```tsx -import { type } from 'arktype' - -const searchSchema = type({ - query: 'string>0&<=100', - page: 'number>0 = 1', - 'sortBy?': "'name'|'date'|'relevance'", - 'filters?': 'string[]', -}) - -export const Route = createFileRoute('/search')({ - validateSearch: searchSchema, - component: SearchPage, -}) -``` - -### Custom Validation Function - -For complete control, implement your own validation logic: - -```tsx -export const Route = createFileRoute('/search')({ - validateSearch: (search: Record) => { - // Custom validation with detailed error handling - const result = { - page: 1, - query: '', - category: 'all', - } - - // Validate page number - const pageNum = Number(search.page) - if (isNaN(pageNum) || pageNum < 1) { - throw new Error('Page must be a positive number') - } - result.page = pageNum - - // Validate query string - if (typeof search.query === 'string' && search.query.length > 0) { - if (search.query.length > 100) { - throw new Error('Search query too long (max 100 characters)') - } - result.query = search.query - } - - // Validate category - const validCategories = ['electronics', 'clothing', 'books', 'all'] - if ( - typeof search.category === 'string' && - validCategories.includes(search.category) - ) { - result.category = search.category - } - - return result - }, - component: SearchPage, -}) -``` - -## Common Validation Patterns - -### Required vs Optional Parameters - -Control which search parameters are mandatory: - -```tsx -const validationSchema = z.object({ - // Required - will throw validation error if missing or invalid - userId: z.number().int().positive(), - action: z.enum(['view', 'edit', 'delete']), - - // Optional - can be undefined - sortBy: z.string().optional(), - - // Optional with fallback - provides default if missing/invalid - page: fallback(z.number().int().positive(), 1), - limit: fallback(z.number().int().min(1).max(100), 20), -}) -``` - -### Complex Data Types - -Handle arrays, objects, and custom types: - -```tsx -const advancedSchema = z.object({ - // Array of strings - tags: z.array(z.string()).optional(), - - // Array of numbers - categoryIds: z.array(z.number().int()).optional(), - - // Date validation - startDate: z.string().datetime().optional(), - endDate: z.string().datetime().optional(), - - // Custom validation - email: z.string().email().optional(), - - // Refined validation with custom logic - priceRange: z - .object({ - min: z.number().min(0), - max: z.number().min(0), - }) - .refine((data) => data.max >= data.min, { - message: 'Max price must be greater than or equal to min price', - }) - .optional(), -}) -``` - -### Input Transformation - -Transform and sanitize input values during validation: - -```tsx -const transformSchema = z.object({ - // Transform string to number - page: z - .string() - .transform((val) => parseInt(val, 10)) - .pipe(z.number().int().positive()), - - // Transform and validate email - email: z.string().toLowerCase().trim().pipe(z.string().email()).optional(), - - // Transform comma-separated string to array - tags: z - .string() - .transform((val) => (val ? val.split(',').map((tag) => tag.trim()) : [])) - .pipe(z.array(z.string().min(1))) - .optional(), -}) -``` - -## Error Handling Strategies - -### Basic Error Handling - -Handle validation errors through route error components: - -```tsx -import { createFileRoute, useRouter } from '@tanstack/react-router' -import { zodValidator } from '@tanstack/zod-adapter' -import { z } from 'zod' - -const searchSchema = z.object({ - query: z.string().min(1, 'Search query is required'), - page: z.number().int().positive('Page must be a positive number'), -}) - -export const Route = createFileRoute('/search')({ - validateSearch: zodValidator(searchSchema), - errorComponent: ({ error }) => { - const router = useRouter() - - return ( -
-

Invalid Search Parameters

-

{error.message}

- - -
- ) - }, - component: SearchPage, -}) - -function SearchPage() { - // Only called when validation succeeds - const search = Route.useSearch() - // ... rest of component -} -``` - -### Custom Error Messages - -Provide user-friendly validation messages: - -```tsx -const userFriendlySchema = z.object({ - query: z - .string() - .min(2, 'Search query must be at least 2 characters') - .max(100, 'Search query cannot exceed 100 characters'), - - page: fallback( - z - .number() - .int('Page must be a whole number') - .positive('Page must be greater than 0'), - 1, - ), - - category: z - .enum(['electronics', 'clothing', 'books'], { - errorMap: () => ({ message: 'Please select a valid category' }), - }) - .optional(), -}) -``` - -### Validation Error Recovery - -Implement fallback behavior for invalid parameters: - -```tsx -const resilientSchema = z.object({ - // Use .catch() to provide fallback values on validation failure - page: z.number().int().positive().catch(1), - - // Use .default() for missing values, .catch() for invalid values - sortBy: z - .enum(['name', 'date', 'relevance']) - .default('relevance') - .catch('relevance'), - - // Custom recovery logic - dateRange: z - .object({ - start: z.string().datetime(), - end: z.string().datetime(), - }) - .catch({ - start: new Date().toISOString(), - end: new Date().toISOString(), - }) - .optional(), -}) -``` - -## Advanced Validation Techniques - -### Conditional Validation - -Apply different validation rules based on other parameters: - -```tsx -const conditionalSchema = z - .object({ - searchType: z.enum(['basic', 'advanced']), - query: z.string().min(1), - }) - .and( - z.discriminatedUnion('searchType', [ - z.object({ - searchType: z.literal('basic'), - // Basic search requires only query - }), - z.object({ - searchType: z.literal('advanced'), - // Advanced search requires additional fields - category: z.string().min(1), - minPrice: z.number().min(0), - maxPrice: z.number().min(0), - }), - ]), - ) -``` - -### Schema Composition - -Combine and extend schemas for reusability: - -```tsx -// Base pagination schema -const paginationSchema = z.object({ - page: fallback(z.number().int().positive(), 1), - limit: fallback(z.number().int().min(1).max(100), 20), -}) - -// Base filter schema -const filterSchema = z.object({ - sortBy: z.enum(['name', 'date', 'relevance']).optional(), - sortOrder: z.enum(['asc', 'desc']).optional(), -}) - -// Compose schemas for different routes -const productSearchSchema = paginationSchema.extend({ - category: z.string().optional(), - inStock: fallback(z.boolean(), true), -}) - -const userSearchSchema = paginationSchema.merge(filterSchema).extend({ - role: z.enum(['admin', 'user', 'moderator']).optional(), - isActive: fallback(z.boolean(), true), -}) -``` - -### Performance Optimization - -Optimize validation for better performance: - -```tsx -// Pre-compile schemas for better performance -const compiledSchema = zodValidator( - z.object({ - query: z.string().min(1), - page: fallback(z.number().int().positive(), 1), - }), -) - -export const Route = createFileRoute('/search')({ - validateSearch: compiledSchema, - component: SearchPage, -}) - -// Use selective validation for expensive operations -function SearchPage() { - // Only validate specific fields when needed - const search = Route.useSearch({ - select: (search) => ({ - query: search.query, - page: search.page, - }), - }) - - return
Search Results
-} -``` - -## Testing Search Parameter Validation - -Focus on testing validation behavior specific to your schemas: - -```tsx -import { render, screen, waitFor } from '@testing-library/react' -import { - createRouter, - createMemoryHistory, - RouterProvider, -} from '@tanstack/react-router' - -describe('Search Validation Behavior', () => { - it('should show error component when validation fails', async () => { - const router = createRouter({ - routeTree, - history: createMemoryHistory({ - initialEntries: ['/search?page=invalid&query='], - }), - }) - - render() - - await waitFor(() => { - expect(screen.getByText('Invalid Search Parameters')).toBeInTheDocument() - }) - }) - - it('should apply fallback values correctly', async () => { - const router = createRouter({ - routeTree, - history: createMemoryHistory({ - initialEntries: ['/search?query=laptops'], // page missing - }), - }) - - render() - - await waitFor(() => { - expect(screen.getByText('Page: 1')).toBeInTheDocument() // Fallback applied - }) - }) -}) -``` - -**For comprehensive route testing patterns, see:** [Set Up Testing](../setup-testing.md) and [Test File-Based Routing](../test-file-based-routing.md) - -## Common Problems - -### Problem: Validation errors break the entire route - -**Symptoms:** Page fails to load when URL contains invalid search parameters. - -**Solution:** Use fallback values and error boundaries: - -```tsx -// ❌ Wrong - will throw error and break route -const strictSchema = z.object({ - page: z.number().int().positive(), // No fallback -}) - -// βœ… Correct - provides fallback for invalid values -const resilientSchema = z.object({ - page: fallback(z.number().int().positive(), 1), -}) - -// βœ… Alternative - use errorComponent on route -export const Route = createFileRoute('/search')({ - validateSearch: resilientSchema, - errorComponent: ({ error }) => , - component: SearchPage, -}) - -function SearchPage() { - // Only called when validation succeeds - const search = Route.useSearch() - return -} -``` - -### Problem: TypeScript errors with optional search parameters - -**Symptoms:** TypeScript complains about potentially undefined values. - -**Solution:** Use proper optional handling or fallback values: - -```tsx -// ❌ Wrong - category might be undefined -function FilterBar() { - const { category } = Route.useSearch() - return {category.toUpperCase()} // TypeScript error -} - -// βœ… Correct - handle optional values -function FilterBar() { - const { category } = Route.useSearch() - return {category?.toUpperCase() || 'All Categories'} -} - -// βœ… Better - use fallback in schema -const schema = z.object({ - category: fallback(z.string(), 'all'), -}) -``` - -### Problem: Search parameter arrays not parsing correctly - -**Symptoms:** Array values appear as strings instead of arrays. - -**Solution:** Ensure proper array parsing in your schema: - -```tsx -// ❌ Wrong - doesn't handle URL array format -const badSchema = z.object({ - tags: z.array(z.string()).optional(), -}) - -// βœ… Correct - parse comma-separated values or multiple params -const goodSchema = z.object({ - tags: z - .union([ - z.array(z.string()), // Multiple ?tags=a&tags=b - z.string().transform((val) => val.split(',')), // Single ?tags=a,b,c - ]) - .optional(), -}) - -// βœ… Alternative - custom preprocessing -const preprocessedSchema = z.preprocess((val) => { - if (typeof val === 'string') return val.split(',') - return val -}, z.array(z.string()).optional()) -``` - -### Problem: Schema validation is too slow - -**Symptoms:** Noticeable delay when navigating with complex search parameters. - -**Solution:** Optimize schema complexity and use selective parsing: - -```tsx -// ❌ Slow - complex validation on every navigation -const complexSchema = z.object({ - query: z.string().refine(async (val) => await validateQuery(val)), - // ... many complex validations -}) - -// βœ… Fast - simplified validation with lazy refinement -const optimizedSchema = z.object({ - query: z.string().min(1), // Basic validation only - // ... other simple validations -}) - -// Perform complex validation separately in component -function SearchPage() { - const search = Route.useSearch() - - // Complex validation only when needed - const [complexValidation, setComplexValidation] = useState(null) - - useEffect(() => { - validateComplexRules(search).then(setComplexValidation) - }, [search]) - - return -} -``` - -## Common Next Steps - -After setting up schema validation, you might want to: - -- [Work with Arrays, Objects, and Dates](../arrays-objects-dates-search-params.md) - Handle arrays, objects, dates, and nested data structures - - - -## Related Resources - -- [TanStack Zod Adapter Documentation](https://tanstack.com/router/latest/docs/framework/react/guide/search-params#zod-adapter) -- [TanStack Valibot Adapter Documentation](https://tanstack.com/router/latest/docs/framework/react/guide/search-params#valibot-adapter) -- [Zod Documentation](https://zod.dev/) - Schema validation library -- [Valibot Documentation](https://valibot.dev/) - Lightweight validation library -- [ArkType Documentation](https://arktype.io/) - TypeScript-first validation diff --git a/docs/router/framework/react/installation.md b/docs/router/framework/react/installation.md deleted file mode 100644 index 023eb807314..00000000000 --- a/docs/router/framework/react/installation.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Installation ---- - -> **Quick Installation**: For step-by-step installation instructions, see our [How to Install TanStack Router](./how-to/install.md) guide. - -You can install TanStack Router with any [NPM](https://npmjs.com) package manager. - -```sh -npm install @tanstack/react-router -# or -pnpm add @tanstack/react-router -# or -yarn add @tanstack/react-router -# or -bun add @tanstack/react-router -# or -deno add npm:@tanstack/react-router -``` - -TanStack Router is currently only compatible with React (with ReactDOM) and Solid. If you would like to contribute to the React Native, Angular, or Vue adapter, please reach out to us on [Discord](https://tlinz.com/discord). - -### Requirements - -[//]: # 'Requirements' - -- `react` either v18.x.x or v19.x.x -- `react-dom`, either v18.x.x or v19.x.x - - Note that `ReactDOM.createRoot` is required. - - The legacy `.render()` function is not supported. - -[//]: # 'Requirements' - -TypeScript is _optional_, but **HIGHLY** recommended! If you are using it, please ensure you are using `typescript>=v5.3.x`. - -> [!IMPORTANT] -> We aim to support the last five minor versions of TypeScript. If you are using an older version, you may run into issues. Please upgrade to the latest version of TypeScript to ensure compatibility. We may drop support for older versions of TypeScript, outside of the range mentioned above, without warning in a minor or patch release. - -### LLM Assistance Support - -All of our documentation for TanStack React Router is integrated into the NPM module and can be easily installed as LLM rules. You can integrate LLM rules into the editor of your choice using [vibe-rules](https://www.npmjs.com/package/vibe-rules). - -```bash -pnpm add -g vibe-rules -``` - -Then run `vibe-rules` with the editor of your choice. Here is an example for Cursor: - -```bash -vibe-rules install cursor -``` - -But you can also use `windsurf`, `claude-code`, etc. Check the [vibe-rules](https://www.npmjs.com/package/vibe-rules) documentation for more information. - -### Usage with yarn workspaces - -When using yarn workspaces, you will need to add the following config to the `.yarnrc.yml` of the application using TanStack Router - -```yml -pnpFallbackMode: all -pnpMode: loose -``` diff --git a/docs/router/framework/react/installation/manual.md b/docs/router/framework/react/installation/manual.md new file mode 100644 index 00000000000..36b02ce3c67 --- /dev/null +++ b/docs/router/framework/react/installation/manual.md @@ -0,0 +1,231 @@ +--- +title: Manual Setup +--- + +To set up TanStack Router manually in a React project, follow the steps below. This gives you a bare minimum setup to get going with TanStack Router using both file-based route generation and code-based route configuration: + +## Using File-Based Route Generation + +#### 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' + +const RootLayout = () => ( + <> +
+ + Home + {' '} + + About + +
+
+ + + +) + +export const Route = createRootRoute({ component: RootLayout }) +``` + +#### `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. diff --git a/docs/router/framework/react/migrate-from-react-location.md b/docs/router/framework/react/installation/migrate-from-react-location.md similarity index 100% rename from docs/router/framework/react/migrate-from-react-location.md rename to docs/router/framework/react/installation/migrate-from-react-location.md diff --git a/docs/router/framework/react/migrate-from-react-router.md b/docs/router/framework/react/installation/migrate-from-react-router.md similarity index 100% rename from docs/router/framework/react/migrate-from-react-router.md rename to docs/router/framework/react/installation/migrate-from-react-router.md diff --git a/docs/router/framework/react/routing/installation-with-esbuild.md b/docs/router/framework/react/installation/with-esbuild.md similarity index 98% rename from docs/router/framework/react/routing/installation-with-esbuild.md rename to docs/router/framework/react/installation/with-esbuild.md index 1932d47edb6..91052d9d2d4 100644 --- a/docs/router/framework/react/routing/installation-with-esbuild.md +++ b/docs/router/framework/react/installation/with-esbuild.md @@ -1,5 +1,5 @@ --- -title: Installation with Esbuild +title: Installation with ESbuild --- [//]: # 'BundlerConfiguration' diff --git a/docs/router/framework/react/routing/installation-with-router-cli.md b/docs/router/framework/react/installation/with-router-cli.md similarity index 100% rename from docs/router/framework/react/routing/installation-with-router-cli.md rename to docs/router/framework/react/installation/with-router-cli.md diff --git a/docs/router/framework/react/routing/installation-with-rspack.md b/docs/router/framework/react/installation/with-rspack.md similarity index 100% rename from docs/router/framework/react/routing/installation-with-rspack.md rename to docs/router/framework/react/installation/with-rspack.md diff --git a/docs/router/framework/react/routing/installation-with-vite.md b/docs/router/framework/react/installation/with-vite.md similarity index 100% rename from docs/router/framework/react/routing/installation-with-vite.md rename to docs/router/framework/react/installation/with-vite.md diff --git a/docs/router/framework/react/routing/installation-with-webpack.md b/docs/router/framework/react/installation/with-webpack.md similarity index 100% rename from docs/router/framework/react/routing/installation-with-webpack.md rename to docs/router/framework/react/installation/with-webpack.md diff --git a/docs/router/framework/react/overview.md b/docs/router/framework/react/overview.md index 05c70fd4f16..faa6d9b02eb 100644 --- a/docs/router/framework/react/overview.md +++ b/docs/router/framework/react/overview.md @@ -2,161 +2,122 @@ title: 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 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. +TanStack Router is a **type‑safe router for React and Solid applications**. It’s designed to make routing, data loading, and navigation smart, predictable, and fully integrated with TypeScript β€” so you can build faster, safer, and more scalable apps. + +## Key Features + +On top of the core features you'd expect from a modern router, TanStack Router delivers new features aimed at improving developer experience and productivity: + +- **100% inferred TypeScript support** β€” type safety across navigation, params, and context +- **Nested & layout routes** β€” including pathless layouts for flexible route trees +- **File‑based or code‑based routing** β€” choose the style that fits your workflow +- **Built‑in data loading & caching** β€” with SWR‑inspired defaults and integration with TanStack Query +- **Smart navigation** β€” prefetching, async route elements, and error boundaries out of the box +- **First‑class search params** β€” JSON‑safe, validated, and easy to use like state + +Unlike framework‑bound routers, TanStack Router is **framework‑agnostic at its core**, giving you the freedom to use it standalone or as part of a larger stack. It even powers [TanStack Start](https://tanstack.com/start), our full‑stack React framework built on Router. + +[//]: # 'Comparison' + +## Comparison + +Before selecting a router or framework, it’s useful to compare the available options to understand how they differ. The table below compares **TanStack Router / Start**, **React Router**, and **Next.js** across a range of features and capabilities. + +> This comparison table strives to be as accurate and as unbiased as possible. If you use any of these libraries and feel the information could be improved, feel free to suggest changes (with notes or evidence of claims) using the "Edit this page on GitHub" link at the bottom of this page. + +### Legend + +- βœ… First-class: built-in and ready to use with no extra setup +- 🟑 Partial support (scale of 5) +- 🟠 Supported via addon or community package +- πŸ”Ά Possible with custom code or workarounds +- πŸ›‘ Not officially supported + +| | TanStack Router / Start | React Router DOM [_(Website)_][router] | Next.JS [_(Website)_][nextjs] | +| ---------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------- | ----------------------------------------------------- | +| Github Repo / Stars | [![][stars-tanstack-router]][gh-tanstack-router] | [![][stars-router]][gh-router] | [![][stars-nextjs]][gh-nextjs] | +| Bundle Size | [![][bp-tanstack-router]][bpl-tanstack-router] | [![][bp-router]][bpl-router] | ❓ | +| History, Memory & Hash Routers | βœ… | βœ… | πŸ›‘ | +| Nested / Layout Routes | βœ… | βœ… | 🟑 | +| Suspense-like Route Transitions | βœ… | βœ… | βœ… | +| Typesafe Routes | βœ… | 🟑 (1/5) | 🟑 | +| Code-based Routes | βœ… | βœ… | πŸ›‘ | +| File-based Routes | βœ… | βœ… | βœ… | +| Virtual/Programmatic File-based Routes | βœ… | βœ… | πŸ›‘ | +| Router Loaders | βœ… | βœ… | βœ… | +| SWR Loader Caching | βœ… | πŸ›‘ | βœ… | +| Route Prefetching | βœ… | βœ… | βœ… | +| Auto Route Prefetching | βœ… | βœ… | βœ… | +| Route Prefetching Delay | βœ… | πŸ”Ά | πŸ›‘ | +| Path Params | βœ… | βœ… | βœ… | +| Typesafe Path Params | βœ… | βœ… | πŸ›‘ | +| Typesafe Route Context | βœ… | πŸ›‘ | πŸ›‘ | +| Path Param Validation | βœ… | πŸ›‘ | πŸ›‘ | +| Custom Path Param Parsing/Serialization | βœ… | πŸ›‘ | πŸ›‘ | +| Ranked Routes | βœ… | βœ… | βœ… | +| Active Link Customization | βœ… | βœ… | βœ… | +| Optimistic UI | βœ… | βœ… | πŸ”Ά | +| Typesafe Absolute + Relative Navigation | βœ… | 🟑 (1/5 via `buildHref` util) | 🟠 (IDE plugin) | +| Route Mount/Transition/Unmount Events | βœ… | πŸ›‘ | πŸ›‘ | +| Devtools | βœ… | 🟠 | πŸ›‘ | +| Basic Search Params | βœ… | βœ… | βœ… | +| Search Param Hooks | βœ… | βœ… | βœ… | +| ``/`useNavigate` Search Param API | βœ… | 🟑 (search-string only via the `to`/`search` options) | 🟑 (search-string only via the `to`/`search` options) | +| JSON Search Params | βœ… | πŸ”Ά | πŸ”Ά | +| TypeSafe Search Params | βœ… | πŸ›‘ | πŸ›‘ | +| Search Param Schema Validation | βœ… | πŸ›‘ | πŸ›‘ | +| Search Param Immutability + Structural Sharing | βœ… | πŸ”Ά | πŸ›‘ | +| Custom Search Param parsing/serialization | βœ… | πŸ”Ά | πŸ›‘ | +| Search Param Middleware | βœ… | πŸ›‘ | πŸ›‘ | +| Suspense Route Elements | βœ… | βœ… | βœ… | +| Route Error Elements | βœ… | βœ… | βœ… | +| Route Pending Elements | βœ… | βœ… | βœ… | +| ``/`useBlocker` | βœ… | πŸ”Ά (no hard reloads or cross-origin navigation) | πŸ›‘ | +| Deferred Primitives | βœ… | βœ… | βœ… | +| Navigation Scroll Restoration | βœ… | βœ… | ❓ | +| ElementScroll Restoration | βœ… | πŸ›‘ | πŸ›‘ | +| Async Scroll Restoration | βœ… | πŸ›‘ | πŸ›‘ | +| Router Invalidation | βœ… | βœ… | βœ… | +| Runtime Route Manipulation (Fog of War) | πŸ›‘ | βœ… | βœ… | +| Parallel Routes | πŸ›‘ | πŸ›‘ | βœ… | +| **Full Stack** | -- | -- | -- | +| SSR | βœ… | βœ… | βœ… | +| Streaming SSR | βœ… | βœ… | βœ… | +| Generic RPCs | βœ… | πŸ›‘ | πŸ›‘ | +| Generic RPC Middleware | βœ… | πŸ›‘ | πŸ›‘ | +| React Server Functions | βœ… | πŸ›‘ | βœ… | +| React Server Function Middleware | βœ… | πŸ›‘ | πŸ›‘ | +| API Routes | βœ… | βœ… | βœ… | +| API Middleware | βœ… | πŸ›‘ | βœ… | +| React Server Components | πŸ›‘ | 🟑 (Experimental) | βœ… | +| `
` API | πŸ›‘ | βœ… | βœ… | + +[bp-tanstack-router]: https://badgen.net/bundlephobia/minzip/@tanstack/react-router +[bpl-tanstack-router]: https://bundlephobia.com/result?p=@tanstack/react-router +[gh-tanstack-router]: https://github.com/tanstack/router +[stars-tanstack-router]: https://img.shields.io/github/stars/tanstack/router?label=%F0%9F%8C%9F +[_]: _ +[router]: https://github.com/remix-run/react-router +[bp-router]: https://badgen.net/bundlephobia/minzip/react-router +[gh-router]: https://github.com/remix-run/react-router +[stars-router]: https://img.shields.io/github/stars/remix-run/react-router?label=%F0%9F%8C%9F +[bpl-router]: https://bundlephobia.com/result?p=react-router +[bpl-history]: https://bundlephobia.com/result?p=history +[_]: _ +[nextjs]: https://nextjs.org/docs/routing/introduction +[bp-nextjs]: https://badgen.net/bundlephobia/minzip/next.js?label=All +[gh-nextjs]: https://github.com/vercel/next.js +[stars-nextjs]: https://img.shields.io/github/stars/vercel/next.js?label=%F0%9F%8C%9F +[bpl-nextjs]: https://bundlephobia.com/result?p=next +[//]: # 'Comparison' ## Acknowledgements -TanStack Router builds on concepts and patterns popularized by many other OSS projects, including: +TanStack Router builds on proven concepts and patterns introduced by several outstanding open‑source 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! +We greatly appreciate the innovation, effort, and experimentation these projects haev contributed to the ecosystem, and we’re excited to build on their foundations to raise the bar even higher. diff --git a/docs/router/framework/react/quick-start.md b/docs/router/framework/react/quick-start.md index 37cd2449c44..85c009cd8bf 100644 --- a/docs/router/framework/react/quick-start.md +++ b/docs/router/framework/react/quick-start.md @@ -1,245 +1,138 @@ --- +id: quick-start title: 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: +TanStack Router can be quickly added to any existing React project or used to scaffold a new one. -## Using File-Based Route Generation +## TanStack Router Installation -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. +### Requirements -### Scaffolding Your First TanStack Router Project +Before installing TanStack router, please ensure your project meets the following requirements: -```sh -npx create-tsrouter-app@latest my-app --template file-router -``` +[//]: # 'Requirements' + +- `react` v18 or later with `createRoot` support. +- `react-dom` v18 or later. + +[//]: # 'Requirements' + +> [!NOTE] Using TypeScript (`v5.3.x or higher`) is recommended for the best development experience, though not strictly required. We aim to support the last 5 minor versions of TypeScript, but using the latest version will help avoid potential issues. -See [create-tsrouter-app](https://github.com/TanStack/create-tsrouter-app/tree/main/cli/create-tsrouter-app) for more options. +TanStack Router is currently only compatible with React (with ReactDOM) and Solid. If you're interested in contributing to support other frameworks, such as React Native, Angular, or Vue, please reach out to us on [Discord](https://tlinz.com/discord). -### Manual Setup +### Download and Install -Alternatively, you can manually setup the project using the following steps: +To install TanStack Router in your project, run the following command using your preferred package manager: -#### Install TanStack Router, Vite Plugin, and the Router Devtools +[//]: # 'installCommand' ```sh -npm install @tanstack/react-router @tanstack/react-router-devtools -npm install -D @tanstack/router-plugin +npm install @tanstack/router # or -pnpm add @tanstack/react-router @tanstack/react-router-devtools -pnpm add -D @tanstack/router-plugin +pnpm add @tanstack/router +#or +yarn add @tanstack/router # or -yarn add @tanstack/react-router @tanstack/react-router-devtools -yarn add -D @tanstack/router-plugin +bun add @tanstack/router # 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 +deno add npm:@tanstack/router ``` -#### 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(), - // ..., - ], -}) -``` +[//]: # 'installCommand' + +Once installed, you can verify the installation by checking your `package.json` file for the `@tanstack/router` dependency. -> [!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' - -const RootLayout = () => ( - <> -
- - Home - {' '} - - About - -
-
- - - -) - -export const Route = createRootRoute({ component: RootLayout }) +[//]: # 'packageJson' + +```json +{ + "dependencies": { + "@tanstack/react-router": "^x.x.x" + } +} ``` -#### `src/routes/index.tsx` +[//]: # 'packageJson' -```tsx -import { createFileRoute } from '@tanstack/react-router' +## New Project Setup -export const Route = createFileRoute('/')({ - component: Index, -}) +To quickly scaffold a new project with TanStack Router, you can use the `create-tsrouter-app` command-line tool. This tool sets up a new React application with TanStack Router pre-configured, allowing you to get started quickly. -function Index() { - return ( -
-

Welcome Home!

-
- ) -} +> [!TIP] For full details on available options and templates, visit the [`create-tsrouter-app` documentation](https://github.com/TanStack/create-tsrouter-app/tree/main/cli/create-tsrouter-app). + +To create a new project, run the following command in your terminal: + +[//]: # 'createAppCommand' + +```sh +npx create-tsrouter-app@latest ``` -#### `src/routes/about.tsx` +[//]: # 'createAppCommand' -```tsx -import { createFileRoute } from '@tanstack/react-router' +The CLI will guide you through a short series of prompts to customize your setup, including options for: -export const Route = createFileRoute('/about')({ - component: About, -}) +[//]: # 'CLIPrompts' -function About() { - return
Hello from About!
-} +- File-based or code-based route configuration +- TypeScript support +- Tailwind CSS integration +- Toolchain setup +- Git initialization + +[//]: # 'CLIPrompts' + +Once complete, a new React project will be generated with TanStack Router installed and ready to use. All dependencies are automatically installed, so you can jump straight into development: + +```sh +cd your-project-name +npm run dev ``` -#### `src/main.tsx` +### Routing Options -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`. +TanStack Router supports both file-based and code-based route configurations, allowing you to choose the approach that best fits your workflow. -Import the generated route tree and create a new router instance: +#### File-Based Route Generation -```tsx -import { StrictMode } from 'react' -import ReactDOM from 'react-dom/client' -import { RouterProvider, createRouter } from '@tanstack/react-router' +The file-based approach is the recommended option for most projects. It automatically creates routes based on your file structure, giving you the best mix of performance, simplicity, and developer experience. -// Import the generated route tree -import { routeTree } from './routeTree.gen' +To create a new project using file-based route generation, run the following command: -// Create a new router instance -const router = createRouter({ routeTree }) +[//]: # 'createAppCommandFileBased' -// Register the router instance for type safety -declare module '@tanstack/react-router' { - interface Register { - router: typeof router - } -} +```sh +npx create-tsrouter-app@latest my-app --template file-router +``` -// Render the app -const rootElement = document.getElementById('root')! -if (!rootElement.innerHTML) { - const root = ReactDOM.createRoot(rootElement) - root.render( - - - , - ) -} +[//]: # 'createAppCommandFileBased' + +This command sets up a new directory called `my-app` with everything configured. Once setup completes, you can then start your development server and begin building your application: + +```sh +cd my-app +npm run dev ``` -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 - } -} +#### Code-Based Route Configuration -const rootElement = document.getElementById('app')! -if (!rootElement.innerHTML) { - const root = ReactDOM.createRoot(rootElement) - root.render( - - - , - ) -} +If you prefer to define routes programmatically, you can use the code-based route configuration. This approach gives you full control over routing logic while maintaining the same project scaffolding workflow. + +[//]: # 'createAppCommandCodeBased' + +```sh +npx create-tsrouter-app@latest my-app +``` + +[//]: # 'createAppCommandCodeBased' + +Similar to the file-based setup, this command creates a new directory called `my-app` with TanStack Router configured for code-based routing. After setup, navigate to your project directory and start the development server: + +```sh +cd my-app +npm run dev ``` -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. +With either approach, you can now start building your React application with TanStack Router! diff --git a/docs/router/framework/solid/installation.md b/docs/router/framework/solid/installation/manual.md similarity index 61% rename from docs/router/framework/solid/installation.md rename to docs/router/framework/solid/installation/manual.md index 3d2de60570e..0624eac8f3e 100644 --- a/docs/router/framework/solid/installation.md +++ b/docs/router/framework/solid/installation/manual.md @@ -1,6 +1,6 @@ --- -title: Installation -ref: docs/router/framework/react/installation.md +title: Manual Setup +ref: docs/router/framework/react/installation/manual.md replace: { 'react-router': 'solid-router' } --- diff --git a/docs/router/framework/solid/overview.md b/docs/router/framework/solid/overview.md index 8fc40d9aa05..a77b4cdb324 100644 --- a/docs/router/framework/solid/overview.md +++ b/docs/router/framework/solid/overview.md @@ -1,7 +1,8 @@ --- title: Overview ref: docs/router/framework/react/overview.md +replace: { 'React framework': 'framework' } --- -[//]: # 'WhyChooseTanStackRouter' -[//]: # 'WhyChooseTanStackRouter' +[//]: # 'Comparison' +[//]: # 'Comparison' diff --git a/docs/router/framework/solid/quick-start.md b/docs/router/framework/solid/quick-start.md index 287c31d8c57..3121ab85d41 100644 --- a/docs/router/framework/solid/quick-start.md +++ b/docs/router/framework/solid/quick-start.md @@ -1,232 +1,65 @@ --- -title: Quick Start +ref: docs/router/framework/react/quick-start.md +replace: { 'React': 'Solid' } --- -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: +[//]: # 'Requirements' -## Using File-Based Route Generation +- `solid-js`v1.x.x -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 +[//]: # 'Requirements' +[//]: # 'installCommand' ```sh -npx create-tsrouter-app@latest my-app --framework solid --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/solid-router @tanstack/solid-router-devtools -npm install -D @tanstack/router-plugin -# or -pnpm add @tanstack/solid-router @tanstack/solid-router-devtools -pnpm add -D @tanstack/router-plugin +npm install @tanstack/solid-router # or -yarn add @tanstack/solid-router @tanstack/solid-router-devtools -yarn add -D @tanstack/router-plugin +pnpm add @tanstack/solid-router +#or +yarn add @tanstack/solid-router # or -bun add @tanstack/solid-router @tanstack/solid-router-devtools -bun add -D @tanstack/router-plugin +bun add @tanstack/solid-router # or -deno add npm:@tanstack/solid-router npm:@tanstack/router-plugin npm:@tanstack/solid-router-devtools +deno add npm:@tanstack/solid-router ``` -#### Configure the Vite Plugin - -```tsx -// vite.config.ts -import { defineConfig } from 'vite' -import solid from 'vite-plugin-solid' -import { tanstackRouter } from '@tanstack/router-plugin/vite' - -// https://vitejs.dev/config/ -export default defineConfig({ - plugins: [ - tanstackRouter({ - target: 'solid', - autoCodeSplitting: true, - }), - solid(), - // ..., - ], -}) -``` +[//]: # 'installCommand' +[//]: # 'packageJson' -> [!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` -- `src/routes/index.tsx` -- `src/routes/about.tsx` -- `src/main.tsx` - -#### `src/routes/__root.tsx` - -```tsx -import { createRootRoute, Link, Outlet } from '@tanstack/solid-router' -import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools' - -export const Route = createRootRoute({ - component: () => ( - <> -
- - Home - {' '} - - About - -
-
- - - - ), -}) -``` - -#### `src/routes/index.tsx` - -```tsx -import { createFileRoute } from '@tanstack/solid-router' - -export const Route = createFileRoute('/')({ - component: Index, -}) - -function Index() { - return ( -
-

Welcome Home!

-
- ) +```json +{ + "dependencies": { + "@tanstack/solid-router": "^x.x.x" + } } ``` -#### `src/routes/about.tsx` - -```tsx -import { createFileRoute } from '@tanstack/solid-router' +[//]: # 'packageJson' +[//]: # 'createAppCommand' -export const Route = createFileRoute('/about')({ - component: About, -}) - -function About() { - return
Hello from About!
-} +```sh +npx create-tsrouter-app@latest --framework solid ``` -#### `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`. +[//]: # 'createAppCommand' +[//]: # 'CLIPrompts' -Import the generated route tree and create a new router instance: +- File-based or code-based route configuration +- TypeScript support +- Toolchain setup +- Git initialization -```tsx -import { render } from 'solid-js/web' -import { RouterProvider, createRouter } from '@tanstack/solid-router' +[//]: # 'CLIPrompts' +[//]: # 'createAppCommandFileBased' -// 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/solid-router' { - interface Register { - router: typeof router - } -} - -// Render the app -const rootElement = document.getElementById('root')! -if (!rootElement.innerHTML) { - render(() => , rootElement) -} +```sh +npx create-tsrouter-app@latest my-app --framework solid --template file-router ``` -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 +[//]: # 'createAppCommandFileBased' +[//]: # 'createAppCommandCodeBased' -> [!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 { render } from 'solid-js/web' -import { - Outlet, - RouterProvider, - Link, - createRouter, - createRoute, - createRootRoute, -} from '@tanstack/solid-router' -import { TanStackRouterDevtools } from '@tanstack/solid-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/solid-router' { - interface Register { - router: typeof router - } -} - -const rootElement = document.getElementById('app')! -if (!rootElement.innerHTML) { - render(() => , rootElement) -} +```sh +npx create-tsrouter-app@latest my-app --framework solid ``` -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. +[//]: # 'createAppCommandCodeBased' diff --git a/docs/start/config.json b/docs/start/config.json index b08aace01f6..f0a87f21f40 100644 --- a/docs/start/config.json +++ b/docs/start/config.json @@ -29,198 +29,214 @@ "label": "Build from Scratch", "to": "framework/react/build-from-scratch" }, + { + "label": "Migrate from Next.js", + "to": "framework/react/migrate-from-next-js" + } + ] + }, + { + "label": "solid", + "children": [ + { + "label": "Overview", + "to": "framework/solid/overview" + }, + { + "label": "Getting Started", + "to": "framework/solid/getting-started" + }, + { + "label": "Quick Start", + "to": "framework/solid/quick-start" + }, + { + "label": "Build from Scratch", + "to": "framework/solid/build-from-scratch" + } + ] + } + ] + }, + { + "label": "Guides", + "children": [], + "frameworks": [ + { + "label": "react", + "children": [ { "label": "Routing", - "to": "framework/react/routing" + "to": "framework/react/guide/routing" }, { "label": "Execution Model", - "to": "framework/react/execution-model" + "to": "framework/react/guide/execution-model" }, { "label": "Code Execution Patterns", - "to": "framework/react/code-execution-patterns" + "to": "framework/react/guide/code-execution-patterns" }, { "label": "Environment Variables", - "to": "framework/react/environment-variables" + "to": "framework/react/guide/environment-variables" }, { "label": "Server Functions", - "to": "framework/react/server-functions" + "to": "framework/react/guide/server-functions" }, { "label": "Static Server Functions", - "to": "framework/react/static-server-functions" + "to": "framework/react/guide/static-server-functions" }, { "label": "Environment Functions", - "to": "framework/react/environment-functions" + "to": "framework/react/guide/environment-functions" }, { "label": "Middleware", - "to": "framework/react/middleware" + "to": "framework/react/guide/middleware" }, { "label": "Server Routes", - "to": "framework/react/server-routes" + "to": "framework/react/guide/server-routes" }, { "label": "Selective SSR", - "to": "framework/react/selective-ssr" + "to": "framework/react/guide/selective-ssr" }, { "label": "SPA Mode", - "to": "framework/react/spa-mode" + "to": "framework/react/guide/spa-mode" }, { "label": "Static Prerendering", - "to": "framework/react/static-prerendering" + "to": "framework/react/guide/static-prerendering" }, { "label": "Server Entry Point", - "to": "framework/react/server-entry-point" + "to": "framework/react/guide/server-entry-point" }, { "label": "Client Entry Point", - "to": "framework/react/client-entry-point" + "to": "framework/react/guide/client-entry-point" }, { "label": "Hosting", - "to": "framework/react/hosting" + "to": "framework/react/guide/hosting" }, { "label": "Authentication Overview", - "to": "framework/react/authentication-overview" + "to": "framework/react/guide/authentication-overview" }, { "label": "Authentication", - "to": "framework/react/authentication" + "to": "framework/react/guide/authentication" }, { "label": "Databases", - "to": "framework/react/databases" + "to": "framework/react/guide/databases" }, { "label": "Observability", - "to": "framework/react/observability" + "to": "framework/react/guide/observability" }, { "label": "Path Aliases", - "to": "framework/react/path-aliases" + "to": "framework/react/guide/path-aliases" }, { "label": "Tailwind CSS Integration", - "to": "framework/react/tailwind-integration" - }, - { - "label": "Migrate from Next.js", - "to": "framework/react/migrate-from-next-js" + "to": "framework/solid/guide/tailwind-integration" } ] }, { "label": "solid", "children": [ - { - "label": "Overview", - "to": "framework/solid/overview" - }, - { - "label": "Getting Started", - "to": "framework/solid/getting-started" - }, - { - "label": "Quick Start", - "to": "framework/solid/quick-start" - }, - { - "label": "Build from Scratch", - "to": "framework/solid/build-from-scratch" - }, { "label": "Routing", - "to": "framework/solid/routing" + "to": "framework/solid/guide/routing" }, { "label": "Execution Model", - "to": "framework/solid/execution-model" + "to": "framework/solid/guide/execution-model" }, { "label": "Code Execution Patterns", - "to": "framework/solid/code-execution-patterns" + "to": "framework/solid/guide/code-execution-patterns" }, { "label": "Environment Variables", - "to": "framework/solid/environment-variables" + "to": "framework/solid/guide/environment-variables" }, { "label": "Server Functions", - "to": "framework/solid/server-functions" + "to": "framework/solid/guide/server-functions" }, { "label": "Static Server Functions", - "to": "framework/solid/static-server-functions" + "to": "framework/solid/guide/static-server-functions" }, { "label": "Environment Functions", - "to": "framework/solid/environment-functions" + "to": "framework/solid/guide/environment-functions" }, { "label": "Middleware", - "to": "framework/solid/middleware" + "to": "framework/solid/guide/middleware" }, { "label": "Server Routes", - "to": "framework/solid/server-routes" + "to": "framework/solid/guide/server-routes" }, { "label": "Selective SSR", - "to": "framework/solid/selective-ssr" + "to": "framework/solid/guide/selective-ssr" }, { "label": "SPA Mode", - "to": "framework/solid/spa-mode" + "to": "framework/solid/guide/spa-mode" }, { "label": "Static Prerendering", - "to": "framework/solid/static-prerendering" + "to": "framework/solid/guide/static-prerendering" }, { "label": "Server Entry Point", - "to": "framework/solid/server-entry-point" + "to": "framework/solid/guide/server-entry-point" }, { "label": "Client Entry Point", - "to": "framework/solid/client-entry-point" + "to": "framework/solid/guide/client-entry-point" }, { "label": "Hosting", - "to": "framework/solid/hosting" + "to": "framework/solid/guide/hosting" }, { "label": "Authentication Overview", - "to": "framework/solid/authentication-overview" + "to": "framework/solid/guide/authentication-overview" }, { "label": "Authentication", - "to": "framework/solid/authentication" + "to": "framework/solid/guide/authentication" }, { "label": "Databases", - "to": "framework/solid/databases" + "to": "framework/solid/guide/databases" }, { "label": "Observability", - "to": "framework/solid/observability" + "to": "framework/solid/guide/observability" }, { "label": "Path Aliases", - "to": "framework/solid/path-aliases" + "to": "framework/solid/guide/path-aliases" }, { "label": "Tailwind CSS Integration", - "to": "framework/solid/tailwind-integration" + "to": "framework/solid/guide/tailwind-integration" } ] } @@ -285,30 +301,6 @@ ] } ] - }, - { - "label": "Tutorials", - "children": [], - "frameworks": [ - { - "label": "react", - "children": [ - { - "label": "Reading and Writing a File", - "to": "framework/react/reading-writing-file" - } - ] - }, - { - "label": "react", - "children": [ - { - "label": "Fetching data from external API", - "to": "framework/react/fetching-external-api" - } - ] - } - ] } ] } diff --git a/docs/start/framework/react/build-from-scratch.md b/docs/start/framework/react/build-from-scratch.md index 06b39be4786..92df7eb2c10 100644 --- a/docs/start/framework/react/build-from-scratch.md +++ b/docs/start/framework/react/build-from-scratch.md @@ -4,7 +4,7 @@ title: Build a Project from Scratch --- > [!NOTE] -> If you chose to quick start with an example or cloned project, you can skip this guide and move on to the [Routing](../routing) guide. +> If you chose to quick start with an example or cloned project, you can skip this guide and move on to the [Routing](../guide/routing) guide. _So you want to build a TanStack Start project from scratch?_ @@ -265,4 +265,4 @@ That's it! 🀯 You've now set up a TanStack Start project and written your firs You can now run `npm run dev` to start your server and navigate to `http://localhost:3000` to see your route in action. -You want to deploy your application? Check out the [hosting guide](../hosting.md). +You want to deploy your application? Check out the [hosting guide](../guide/hosting.md). diff --git a/docs/start/framework/react/getting-started.md b/docs/start/framework/react/getting-started.md index 4ae32e08032..c6a83ad98f4 100644 --- a/docs/start/framework/react/getting-started.md +++ b/docs/start/framework/react/getting-started.md @@ -8,18 +8,17 @@ title: Getting Started - [Start a new project from scratch](#start-a-new-project-from-scratch) to quickly learn how Start works (see below) - Refer to a migration guide for your specific framework: - [Next.js](../migrate-from-next-js) - - [React Router](../migrate-from-react-router) (Including React Router 7 "Data Mode") - Remix 2 / React Router 7 "Framework Mode" (coming soon!) ## Start a new project from scratch Choose one of the following options to start building a _new_ TanStack Start project: -- [TanStack Start CLI] - Just run `npm create @tanstack/start@latest`. Local, fast, and optionally customizable +- TanStack Start CLI - Just run `npm create @tanstack/start@latest`. Local, fast, and optionally customizable - [TanStack Builder](#) (coming soon!) - A visual interface to configure new TanStack projects with a few clicks - [Quick Start Examples](../quick-start) Download or clone one of our official examples - [Build a project from scratch](../build-from-scratch) - A guide to building a TanStack Start project line-by-line, file-by-file. ## Next Steps -Unless you chose to build a project from scratch, you can now move on to the [Routing](../routing) guide to learn how to use TanStack Start! +Unless you chose to build a project from scratch, you can now move on to the [Routing](../guide/routing) guide to learn how to use TanStack Start! diff --git a/docs/start/framework/react/authentication-overview.md b/docs/start/framework/react/guide/authentication-overview.md similarity index 100% rename from docs/start/framework/react/authentication-overview.md rename to docs/start/framework/react/guide/authentication-overview.md diff --git a/docs/start/framework/react/authentication.md b/docs/start/framework/react/guide/authentication.md similarity index 98% rename from docs/start/framework/react/authentication.md rename to docs/start/framework/react/guide/authentication.md index 8b7f292a154..90d065a9c81 100644 --- a/docs/start/framework/react/authentication.md +++ b/docs/start/framework/react/guide/authentication.md @@ -606,4 +606,4 @@ When implementing authentication, consider: - **Monitoring**: Add logging and monitoring for authentication events - **Compliance**: Ensure compliance with relevant regulations if storing personal data -For other authentication approaches, check the [Authentication Overview](../authentication-overview.md). For specific integration help, see the [How-to Guides](/router/latest/docs/framework/react/how-to/README.md#authentication) or explore our [working examples](https://github.com/TanStack/router/tree/main/examples/react). +For other authentication approaches, check the [Authentication Overview](../authentication-overview.md). For specific integration help, explore our [working examples](https://github.com/TanStack/router/tree/main/examples/react). diff --git a/docs/start/framework/react/client-entry-point.md b/docs/start/framework/react/guide/client-entry-point.md similarity index 100% rename from docs/start/framework/react/client-entry-point.md rename to docs/start/framework/react/guide/client-entry-point.md diff --git a/docs/start/framework/react/code-execution-patterns.md b/docs/start/framework/react/guide/code-execution-patterns.md similarity index 100% rename from docs/start/framework/react/code-execution-patterns.md rename to docs/start/framework/react/guide/code-execution-patterns.md diff --git a/docs/start/framework/react/databases.md b/docs/start/framework/react/guide/databases.md similarity index 100% rename from docs/start/framework/react/databases.md rename to docs/start/framework/react/guide/databases.md diff --git a/docs/start/framework/react/environment-functions.md b/docs/start/framework/react/guide/environment-functions.md similarity index 100% rename from docs/start/framework/react/environment-functions.md rename to docs/start/framework/react/guide/environment-functions.md diff --git a/docs/start/framework/react/environment-variables.md b/docs/start/framework/react/guide/environment-variables.md similarity index 100% rename from docs/start/framework/react/environment-variables.md rename to docs/start/framework/react/guide/environment-variables.md diff --git a/docs/start/framework/react/execution-model.md b/docs/start/framework/react/guide/execution-model.md similarity index 100% rename from docs/start/framework/react/execution-model.md rename to docs/start/framework/react/guide/execution-model.md diff --git a/docs/start/framework/react/fetching-external-api.md b/docs/start/framework/react/guide/fetching-external-api.md similarity index 100% rename from docs/start/framework/react/fetching-external-api.md rename to docs/start/framework/react/guide/fetching-external-api.md diff --git a/docs/start/framework/react/hosting.md b/docs/start/framework/react/guide/hosting.md similarity index 100% rename from docs/start/framework/react/hosting.md rename to docs/start/framework/react/guide/hosting.md diff --git a/docs/start/framework/react/middleware.md b/docs/start/framework/react/guide/middleware.md similarity index 99% rename from docs/start/framework/react/middleware.md rename to docs/start/framework/react/guide/middleware.md index a188184f9c4..d18ab1b4352 100644 --- a/docs/start/framework/react/middleware.md +++ b/docs/start/framework/react/guide/middleware.md @@ -515,7 +515,7 @@ const fn = createServerFn() ### Reading/Modifying the Server Response -Middleware that uses the `server` method executes in the same context as server functions, so you can follow the exact same [Server Function Context Utilities](../server-functions.md#server-function-context) to read and modify anything about the request headers, status codes, etc. +Middleware that uses the `server` method executes in the same context as server functions, so you can follow the exact same [Server Function Context Utilities](../server-functions#server-context--request-handling) to read and modify anything about the request headers, status codes, etc. ### Modifying the Client Request diff --git a/docs/start/framework/react/observability.md b/docs/start/framework/react/guide/observability.md similarity index 100% rename from docs/start/framework/react/observability.md rename to docs/start/framework/react/guide/observability.md diff --git a/docs/start/framework/react/path-aliases.md b/docs/start/framework/react/guide/path-aliases.md similarity index 100% rename from docs/start/framework/react/path-aliases.md rename to docs/start/framework/react/guide/path-aliases.md diff --git a/docs/start/framework/react/reading-writing-file.md b/docs/start/framework/react/guide/reading-writing-file.md similarity index 100% rename from docs/start/framework/react/reading-writing-file.md rename to docs/start/framework/react/guide/reading-writing-file.md diff --git a/docs/start/framework/react/routing.md b/docs/start/framework/react/guide/routing.md similarity index 99% rename from docs/start/framework/react/routing.md rename to docs/start/framework/react/guide/routing.md index 21ec311f2c1..ab8da82d711 100644 --- a/docs/start/framework/react/routing.md +++ b/docs/start/framework/react/guide/routing.md @@ -232,4 +232,4 @@ export const Route = createFileRoute('/posts/$postId')({ ## This is just the "start" -This has been just a high-level overview of how to configure routes using TanStack Router. For more detailed information, please refer to the [TanStack Router documentation](/router/latest/docs/framework/react/routing/file-based-routing). +This has been just a high-level overview of how to configure routes using TanStack Router. For more detailed information, please refer to the [TanStack Router documentation](/router/latest/docs/framework/react/routing/routing-concepts). diff --git a/docs/start/framework/react/selective-ssr.md b/docs/start/framework/react/guide/selective-ssr.md similarity index 100% rename from docs/start/framework/react/selective-ssr.md rename to docs/start/framework/react/guide/selective-ssr.md diff --git a/docs/start/framework/react/server-entry-point.md b/docs/start/framework/react/guide/server-entry-point.md similarity index 100% rename from docs/start/framework/react/server-entry-point.md rename to docs/start/framework/react/guide/server-entry-point.md diff --git a/docs/start/framework/react/server-functions.md b/docs/start/framework/react/guide/server-functions.md similarity index 98% rename from docs/start/framework/react/server-functions.md rename to docs/start/framework/react/guide/server-functions.md index ff15ca12898..7d09baf53a9 100644 --- a/docs/start/framework/react/server-functions.md +++ b/docs/start/framework/react/guide/server-functions.md @@ -206,7 +206,7 @@ Access request headers, cookies, and response customization: ### Streaming -Stream typed data from server functions to the client. See the [Streaming Data from Server Functions guide](../guide/streaming-data-from-server-functions). +Stream typed data from server functions to the client. ### Raw Responses diff --git a/docs/start/framework/react/server-routes.md b/docs/start/framework/react/guide/server-routes.md similarity index 100% rename from docs/start/framework/react/server-routes.md rename to docs/start/framework/react/guide/server-routes.md diff --git a/docs/start/framework/react/spa-mode.md b/docs/start/framework/react/guide/spa-mode.md similarity index 99% rename from docs/start/framework/react/spa-mode.md rename to docs/start/framework/react/guide/spa-mode.md index 6053f62ecfa..89214655539 100644 --- a/docs/start/framework/react/spa-mode.md +++ b/docs/start/framework/react/guide/spa-mode.md @@ -42,7 +42,7 @@ To configure SPA mode, there are a few options you can add to your Start plugin' // vite.config.ts export default defineConfig({ plugins: [ - TanStackStart({ + tanstackStart({ spa: { enabled: true, }, @@ -121,7 +121,7 @@ You can always override these options by providing your own prerender options: // vite.config.ts export default defineConfig({ plugins: [ - TanStackStart({ + tanstackStart({ spa: { prerender: { outputPath: '/custom-shell', diff --git a/docs/start/framework/react/static-prerendering.md b/docs/start/framework/react/guide/static-prerendering.md similarity index 100% rename from docs/start/framework/react/static-prerendering.md rename to docs/start/framework/react/guide/static-prerendering.md diff --git a/docs/start/framework/react/static-server-functions.md b/docs/start/framework/react/guide/static-server-functions.md similarity index 100% rename from docs/start/framework/react/static-server-functions.md rename to docs/start/framework/react/guide/static-server-functions.md diff --git a/docs/start/framework/react/tailwind-integration.md b/docs/start/framework/react/guide/tailwind-integration.md similarity index 100% rename from docs/start/framework/react/tailwind-integration.md rename to docs/start/framework/react/guide/tailwind-integration.md diff --git a/docs/start/framework/react/overview.md b/docs/start/framework/react/overview.md index 7fe29f64f9d..df976ab520b 100644 --- a/docs/start/framework/react/overview.md +++ b/docs/start/framework/react/overview.md @@ -8,18 +8,18 @@ title: TanStack Start Overview > **This does not mean it is bug-free or without issues, which is why we invite you to try it out and provide feedback!** > The road to v1 will likely be a quick one, so don't wait too long to try it out! -TanStack Start is a full-stack React framework powered by TanStack Router. It provides a full-document SSR, streaming, server functions, bundling, and more. Thanks to [Vite](https://vite.dev/), it's ready to develop and deploy to any hosting provider or runtime you want! +TanStack Start is a full‑stack framework that combines the type-safe routing of [TanStack Router](https://tanstack.com/router/latest) with the fast build and dev experience of Vite. It delivers an integrated workflow for building modern web apps features all designed for fast local development and flexible deployment to Vite‑compatible hosts and runtimes. ## Dependencies -TanStack Start is built on two key technologies: +TanStack Start is built on top of two main dependencies: -- **[TanStack Router](https://tanstack.com/router)**: A type-safe router for building web applications with advanced features like nested routing, search params, and data loading -- **[Vite](https://vite.dev/)**: A modern build tool that provides fast development with hot module replacement and optimized production builds +- **TanStack Router** β€” Provides type-safe navigation, nested routes, search params, and data-loading primitives that form the foundation of Start's routing and server‑client type contract. +- **Vite** β€” Supplies a fast dev server with HMR, a modern build pipeline, and a rich plugin ecosystem for optimized production builds and broad deployment targets. -## Should I use TanStack Start or just TanStack Router? +## Key features -90% of any framework usually comes down to the router, and TanStack Start is no different. **TanStack Start relies 100% on TanStack Router for its routing system.** In addition to TanStack Router's amazing features, Start enables even more powerful features: +In addition to the [features provided by TanStack Router](https://tanstack.com/router/latest/docs/framework/react/overview#key-features), you'll find that TanStack Start extends its capabilities to provide a comprehensive full-stack solution. Some of these key features include: - **Full-document SSR** - Server-side rendering for better performance and SEO - **Streaming** - Progressive page loading for improved user experience @@ -30,20 +30,11 @@ TanStack Start is built on two key technologies: - **Universal Deployment** - Deploy to any Vite-compatible hosting provider - **End-to-End Type Safety** - Full TypeScript support across the entire stack -That said, if you **know with certainty** that you will not need any of the above features, then you may want to consider using TanStack Router alone, which is still a powerful and type-safe SPA routing upgrade over other routers and frameworks. +Together, these features provide a powerful foundation for building modern, type-safe applications that run seamlessly across client and server environments. -## Are there limitations? +## Next Steps -The only relevant limitation is that TanStack Start does not currently support React Server Components, **but we are actively working on integration and expect to support them in the near future.** +Now that you have an overview of TanStack Start, you can dive deeper into its features and capabilities. Here are some recommended next steps: -Otherwise, TanStack Start provides the same capability as other full-stack frameworks like Next.js, Remix, etc, with even more features and a more powerful developer experience. - -## How is TanStack Start funded? - -TanStack is 100% open source, free to use, and always will be. It is built and maintained by an extremely talented and dedicated community of developers and software engineers. TanStack.com (also open source) is owned by TanStack LLC, a privately held company, 100% bootstrapped and self-funded. We are not venture-backed and have never sought investors. To support the development of TanStack Start and other TanStack libraries, TanStack.com partners with [these amazing companies](https://tanstack.com/partners?status=active&libraries=%5B%22start%22%5D) who offer both financial support and resources to help us continue to build the best possible developer experience for the web community: - - - -## Ready to get started? - -Go to the next page to learn how to install TanStack Start and create your first app! +- [Getting Started Guide](../getting-started) +- [Build an application from scratch](../build-from-scratch) diff --git a/docs/start/framework/react/quick-start.md b/docs/start/framework/react/quick-start.md index 3c25e361db6..1bf5b561336 100644 --- a/docs/start/framework/react/quick-start.md +++ b/docs/start/framework/react/quick-start.md @@ -1,25 +1,39 @@ --- id: quick-start title: Quick Start +next_steps: {} --- -## Impatient? +To quickly scaffold a new React project with TanStack Start, run the following command using your package manager of choice: -The fastest way to get a Start project up and running is with the cli. Just run - -``` +```bash +npm create @tanstack/start@latest +#or pnpm create @tanstack/start@latest ``` -or +On installation, you'll be prompted with a series of questions to customize your project, from the name of your project to the add-ons you'd like to include. +The options you'll be prompted for include: + +- **Project name**: The name of your project. This will also be the name of the directory created for your project. +- **Tailwind CSS**: Whether you'd like to include Tailwind CSS in your project. +- **Toolchain options**: You can choose betwee Biome, ESLint, or none for linting and formatting. +- **Hosting providers**: Options include Cloudflare, Netlify, and Nitro. You can also choose none if you don't want any hosting configuration. +- **Various add-ons**: Integrate existing Tanstack libraries like [DB](https://tanstack.com/db/latest), [Query](https://tanstack.com/query/latest), and [Form](https://tanstack.com/form/latest), as well as range of other third-party tools and libraries. + +Once you've answered the prompts, your project will be created. Since the cli scaffolds and installs dependencies, this can take a few minutes. Once it's done, navigate to your new project directory and start the development server with the following commands: + +```bash +cd your-project-name +npm dev ``` -npm create @tanstack/start@latest -``` -depending on your package manage of choice. You'll be prompted to add things like Tailwind, eslint, and a ton of other options. +## Using Examples + +While the cli offers a quick way to scaffold a new project with the option to include various add-ons, you can also start with one of the many examples we've created to help you get started with common use-cases. -You can also clone and run the [Basic](https://github.com/TanStack/router/tree/main/examples/react/start-basic) example right away with the following commands: +For the [Basic](https://github.com/TanStack/router/tree/main/examples/react/start-basic) example, you can run the following commands to clone it and get started: ```bash npx gitpick TanStack/router/tree/main/examples/react/start-basic start-basic @@ -28,15 +42,8 @@ npm install npm run dev ``` -If you'd like to use a different example, you can replace `start-basic` above with the slug of the example you'd like to use from the list below. - -Once you've cloned the example you want, head back to the [Routing](../routing) guide to learn how to use TanStack Start! - -## Examples - -TanStack Start has load of examples to get you started. Pick one of the examples below to get started! +In addition to the Basic example, we've created a variety of other examples to get you started with different add-ons and use-cases. Simply replace `start-basic` in the commands above with the slug of the example you'd like to use from the list below to clone and get started: -- [Basic](https://github.com/TanStack/router/tree/main/examples/react/start-basic) (start-basic) - [Basic + Auth](https://github.com/TanStack/router/tree/main/examples/react/start-basic-auth) (start-basic-auth) - [Counter](https://github.com/TanStack/router/tree/main/examples/react/start-counter) (start-counter) - [Basic + React Query](https://github.com/TanStack/router/tree/main/examples/react/start-basic-react-query) (start-basic-react-query) @@ -47,40 +54,7 @@ TanStack Start has load of examples to get you started. Pick one of the examples - [WorkOS](https://github.com/TanStack/router/tree/main/examples/react/start-workos) (start-workos) - [Material UI](https://github.com/TanStack/router/tree/main/examples/react/start-material-ui) (start-material-ui) -### Stackblitz - -Each example above has an embedded stackblitz preview to find the one that feels like a good starting point - -### Quick Deploy - -To quickly deploy an example, click the **Deploy to Netlify** button on an example's page to both clone and deploy the example to Netlify. - -### Manual Deploy - -To manually clone and deploy the example to anywhere else you'd like, use the following commands replacing `EXAMPLE_SLUG` with the slug of the example you'd like to use from above: - -```bash -npx gitpick TanStack/router/tree/main/examples/react/EXAMPLE_SLUG my-new-project -cd my-new-project -npm install -npm run dev -``` - -Once you've clone or deployed an example, head back to the [Routing](../routing) guide to learn how to use TanStack Start! - -## Other Router Examples - -While not Start-specific examples, these may help you understand more about how TanStack Router works: +> [!NOTE] +> While not specific to Start, TanStack Router offers a variety of other examples that may help you understand more about how TanStack Router works. See these examples in [Router's documentation](https://tanstack.com/router/latest/docs). -- [Quickstart (file-based)](https://github.com/TanStack/router/tree/main/examples/react/quickstart-file-based) -- [Basic (file-based)](https://github.com/TanStack/router/tree/main/examples/react/basic-file-based) -- [Kitchen Sink (file-based)](https://github.com/TanStack/router/tree/main/examples/react/kitchen-sink-file-based) -- [Kitchen Sink + React Query (file-based)](https://github.com/TanStack/router/tree/main/examples/react/kitchen-sink-react-query-file-based) -- [Location Masking](https://github.com/TanStack/router/tree/main/examples/react/location-masking) -- [Authenticated Routes](https://github.com/TanStack/router/tree/main/examples/react/authenticated-routes) -- [Scroll Restoration](https://github.com/TanStack/router/tree/main/examples/react/scroll-restoration) -- [Deferred Data](https://github.com/TanStack/router/tree/main/examples/react/deferred-data) -- [Navigation Blocking](https://github.com/TanStack/router/tree/main/examples/react/navigation-blocking) -- [View Transitions](https://github.com/TanStack/router/tree/main/examples/react/view-transitions) -- [With tRPC](https://github.com/TanStack/router/tree/main/examples/react/with-trpc) -- [With tRPC + React Query](https://github.com/TanStack/router/tree/main/examples/react/with-trpc-react-query) +Each of the above examples includes an embedded Stackblitz preview to help you find the one that works best for your use-case. To see these, simply select the example you wish to preview from the "Examples" section in the sidebar. diff --git a/docs/router/framework/react/guide/streaming-data-from-server-functions.md b/docs/start/framework/react/streaming-data-from-server-functions.md similarity index 100% rename from docs/router/framework/react/guide/streaming-data-from-server-functions.md rename to docs/start/framework/react/streaming-data-from-server-functions.md diff --git a/docs/start/framework/solid/environment-functions.md b/docs/start/framework/solid/environment-functions.md deleted file mode 100644 index 63a0d9ad22a..00000000000 --- a/docs/start/framework/solid/environment-functions.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/start/framework/react/environment-functions.md -replace: { 'react': 'solid' } ---- diff --git a/docs/start/framework/solid/authentication-overview.md b/docs/start/framework/solid/guide/authentication-overview.md similarity index 68% rename from docs/start/framework/solid/authentication-overview.md rename to docs/start/framework/solid/guide/authentication-overview.md index 623d28df7e3..74de1163af3 100644 --- a/docs/start/framework/solid/authentication-overview.md +++ b/docs/start/framework/solid/guide/authentication-overview.md @@ -1,5 +1,5 @@ --- -ref: docs/start/framework/react/authentication-overview.md +ref: docs/start/framework/react/guide/authentication-overview.md replace: { '@tanstack/react-start': '@tanstack/solid-start', diff --git a/docs/start/framework/solid/authentication.md b/docs/start/framework/solid/guide/authentication.md similarity index 100% rename from docs/start/framework/solid/authentication.md rename to docs/start/framework/solid/guide/authentication.md diff --git a/docs/start/framework/solid/client-entry-point.md b/docs/start/framework/solid/guide/client-entry-point.md similarity index 100% rename from docs/start/framework/solid/client-entry-point.md rename to docs/start/framework/solid/guide/client-entry-point.md diff --git a/docs/start/framework/solid/code-execution-patterns.md b/docs/start/framework/solid/guide/code-execution-patterns.md similarity index 100% rename from docs/start/framework/solid/code-execution-patterns.md rename to docs/start/framework/solid/guide/code-execution-patterns.md diff --git a/docs/start/framework/solid/databases.md b/docs/start/framework/solid/guide/databases.md similarity index 64% rename from docs/start/framework/solid/databases.md rename to docs/start/framework/solid/guide/databases.md index 3af89fe3127..70e212d5141 100644 --- a/docs/start/framework/solid/databases.md +++ b/docs/start/framework/solid/guide/databases.md @@ -1,5 +1,5 @@ --- -ref: docs/start/framework/react/databases.md +ref: docs/start/framework/react/guide/databases.md replace: { '@tanstack/react-start': '@tanstack/solid-start', 'React': 'SolidJS' } --- diff --git a/docs/start/framework/solid/guide/environment-functions.md b/docs/start/framework/solid/guide/environment-functions.md new file mode 100644 index 00000000000..d08e95a93c2 --- /dev/null +++ b/docs/start/framework/solid/guide/environment-functions.md @@ -0,0 +1,4 @@ +--- +ref: docs/start/framework/react/guide/environment-functions.md +replace: { 'react': 'solid' } +--- diff --git a/docs/start/framework/solid/environment-variables.md b/docs/start/framework/solid/guide/environment-variables.md similarity index 100% rename from docs/start/framework/solid/environment-variables.md rename to docs/start/framework/solid/guide/environment-variables.md diff --git a/docs/start/framework/solid/execution-model.md b/docs/start/framework/solid/guide/execution-model.md similarity index 100% rename from docs/start/framework/solid/execution-model.md rename to docs/start/framework/solid/guide/execution-model.md diff --git a/docs/start/framework/solid/getting-started.md b/docs/start/framework/solid/guide/getting-started.md similarity index 100% rename from docs/start/framework/solid/getting-started.md rename to docs/start/framework/solid/guide/getting-started.md diff --git a/docs/start/framework/solid/hosting.md b/docs/start/framework/solid/guide/hosting.md similarity index 100% rename from docs/start/framework/solid/hosting.md rename to docs/start/framework/solid/guide/hosting.md diff --git a/docs/start/framework/solid/middleware.md b/docs/start/framework/solid/guide/middleware.md similarity index 63% rename from docs/start/framework/solid/middleware.md rename to docs/start/framework/solid/guide/middleware.md index f2362385c34..8c411e9fb92 100644 --- a/docs/start/framework/solid/middleware.md +++ b/docs/start/framework/solid/guide/middleware.md @@ -1,5 +1,5 @@ --- -ref: docs/start/framework/react/middleware.md +ref: docs/start/framework/react/guide/middleware.md replace: { '@tanstack/react-start': '@tanstack/solid-start', 'React': 'SolidJS' } --- diff --git a/docs/start/framework/solid/observability.md b/docs/start/framework/solid/guide/observability.md similarity index 100% rename from docs/start/framework/solid/observability.md rename to docs/start/framework/solid/guide/observability.md diff --git a/docs/start/framework/solid/path-aliases.md b/docs/start/framework/solid/guide/path-aliases.md similarity index 63% rename from docs/start/framework/solid/path-aliases.md rename to docs/start/framework/solid/guide/path-aliases.md index 29f29aae640..3c7864d188e 100644 --- a/docs/start/framework/solid/path-aliases.md +++ b/docs/start/framework/solid/guide/path-aliases.md @@ -1,5 +1,5 @@ --- -ref: docs/start/framework/react/path-aliases.md +ref: docs/start/framework/react/guide/path-aliases.md replace: { '@tanstack/react-start': '@tanstack/solid-start', 'React': 'SolidJS' } --- diff --git a/docs/start/framework/solid/reading-writing-file.md b/docs/start/framework/solid/guide/reading-writing-file.md similarity index 59% rename from docs/start/framework/solid/reading-writing-file.md rename to docs/start/framework/solid/guide/reading-writing-file.md index 00642fef61a..5aa48d8188b 100644 --- a/docs/start/framework/solid/reading-writing-file.md +++ b/docs/start/framework/solid/guide/reading-writing-file.md @@ -1,5 +1,5 @@ --- -ref: docs/start/framework/react/reading-writing-file.md +ref: docs/start/framework/react/guide/reading-writing-file.md replace: { '@tanstack/react-start': '@tanstack/solid-start', 'React': 'SolidJS' } --- diff --git a/docs/start/framework/solid/routing.md b/docs/start/framework/solid/guide/routing.md similarity index 100% rename from docs/start/framework/solid/routing.md rename to docs/start/framework/solid/guide/routing.md diff --git a/docs/start/framework/solid/selective-ssr.md b/docs/start/framework/solid/guide/selective-ssr.md similarity index 100% rename from docs/start/framework/solid/selective-ssr.md rename to docs/start/framework/solid/guide/selective-ssr.md diff --git a/docs/start/framework/solid/server-entry-point.md b/docs/start/framework/solid/guide/server-entry-point.md similarity index 60% rename from docs/start/framework/solid/server-entry-point.md rename to docs/start/framework/solid/guide/server-entry-point.md index 986c7c7339c..ef261fe9b67 100644 --- a/docs/start/framework/solid/server-entry-point.md +++ b/docs/start/framework/solid/guide/server-entry-point.md @@ -1,5 +1,5 @@ --- -ref: docs/start/framework/react/server-entry-point.md +ref: docs/start/framework/react/guide/server-entry-point.md replace: { '@tanstack/react-start': '@tanstack/solid-start', 'React': 'SolidJS' } --- diff --git a/docs/start/framework/solid/server-functions.md b/docs/start/framework/solid/guide/server-functions.md similarity index 73% rename from docs/start/framework/solid/server-functions.md rename to docs/start/framework/solid/guide/server-functions.md index 46572c0d0a6..35d8fb58a98 100644 --- a/docs/start/framework/solid/server-functions.md +++ b/docs/start/framework/solid/guide/server-functions.md @@ -1,5 +1,5 @@ --- -ref: docs/start/framework/react/server-functions.md +ref: docs/start/framework/react/guide/server-functions.md replace: { '@tanstack/react-start': '@tanstack/solid-start', diff --git a/docs/start/framework/solid/server-routes.md b/docs/start/framework/solid/guide/server-routes.md similarity index 100% rename from docs/start/framework/solid/server-routes.md rename to docs/start/framework/solid/guide/server-routes.md diff --git a/docs/start/framework/solid/spa-mode.md b/docs/start/framework/solid/guide/spa-mode.md similarity index 64% rename from docs/start/framework/solid/spa-mode.md rename to docs/start/framework/solid/guide/spa-mode.md index 2e8f4a6cb5f..c18e9b4ebf2 100644 --- a/docs/start/framework/solid/spa-mode.md +++ b/docs/start/framework/solid/guide/spa-mode.md @@ -1,5 +1,5 @@ --- -ref: docs/start/framework/react/spa-mode.md +ref: docs/start/framework/react/guide/spa-mode.md replace: { '@tanstack/react-start': '@tanstack/solid-start', 'React': 'SolidJS' } --- diff --git a/docs/start/framework/solid/static-prerendering.md b/docs/start/framework/solid/guide/static-prerendering.md similarity index 100% rename from docs/start/framework/solid/static-prerendering.md rename to docs/start/framework/solid/guide/static-prerendering.md diff --git a/docs/start/framework/solid/static-server-functions.md b/docs/start/framework/solid/guide/static-server-functions.md similarity index 58% rename from docs/start/framework/solid/static-server-functions.md rename to docs/start/framework/solid/guide/static-server-functions.md index 9a91dc3f27a..c9207e898ef 100644 --- a/docs/start/framework/solid/static-server-functions.md +++ b/docs/start/framework/solid/guide/static-server-functions.md @@ -1,5 +1,5 @@ --- -ref: docs/start/framework/react/static-server-functions.md +ref: docs/start/framework/react/guide/static-server-functions.md replace: { '@tanstack/react-start': '@tanstack/solid-start', 'React': 'SolidJS' } --- diff --git a/docs/start/framework/solid/tailwind-integration.md b/docs/start/framework/solid/guide/tailwind-integration.md similarity index 100% rename from docs/start/framework/solid/tailwind-integration.md rename to docs/start/framework/solid/guide/tailwind-integration.md diff --git a/docs/start/framework/solid/overview.md b/docs/start/framework/solid/overview.md index 76b10d0136c..c1916c6a85a 100644 --- a/docs/start/framework/solid/overview.md +++ b/docs/start/framework/solid/overview.md @@ -1,9 +1,4 @@ --- ref: docs/start/framework/react/overview.md -replace: - { - '@tanstack/react-start': '@tanstack/solid-start', - 'React': 'SolidJS', - 'react-router': 'solid-router', - } +replace: { 'react': 'solid' } --- diff --git a/docs/start/framework/solid/quick-start.md b/docs/start/framework/solid/quick-start.md index 2948e668f7d..36f9a4b660a 100644 --- a/docs/start/framework/solid/quick-start.md +++ b/docs/start/framework/solid/quick-start.md @@ -1,25 +1,36 @@ --- -id: quick-start title: Quick Start --- -## Impatient? +To quickly scaffold a new React project with TanStack Start, run the following command using your package manager of choice: -The fastest way to get a Start project up and running is with the cli. Just run - -``` -pnpm create @tanstack/start@latest +```bash +npm create @tanstack/start@latest --framework solid +#or +pnpm create @tanstack/start@latest --framework solid ``` -or +On installation, you'll be prompted with a series of questions to customize your project, from the name of your project to the add-ons you'd like to include. +The options you'll be prompted for include: + +- **Project name**: The name of your project. This will also be the name of the directory created for your project. +- **Toolchain options**: You can choose betwee Biome, ESLint, or none for linting and formatting. +- **Hosting providers**: Whether you wish to include Nitro or none for hosting configuration. +- **Various add-ons**: Integrate existing Tanstack libraries like [Store](https://tanstack.com/store/latest), [Query](https://tanstack.com/query/latest), and [Form](https://tanstack.com/form/latest), as well as other third-party tools and libraries. + +Once you've answered the prompts, your project will be created. Since the cli scaffolds and installs dependencies, this can take a few minutes. Once it's done, navigate to your new project directory and start the development server with the following commands: + +```bash +cd your-project-name +npm dev ``` -npm create @tanstack/start@latest -``` -depending on your package manage of choice. You'll be prompted to add things like Tailwind, eslint, and a ton of other options. +## Using Examples + +While the cli offers a quick way to scaffold a new project, you can also start with one of the examples we've created to help you get started with common use-cases. -You can also clone and run the [Basic](https://github.com/TanStack/router/tree/main/examples/solid/start-basic) example right away with the following commands: +For the [Basic](https://github.com/TanStack/router/tree/main/examples/solid/start-basic) example, you can run the following commands to clone it and get started: ```bash npx gitpick TanStack/router/tree/main/examples/solid/start-basic start-basic @@ -28,36 +39,11 @@ npm install npm run dev ``` -If you'd like to use a different example, you can replace `start-basic` above with the slug of the example you'd like to use from the list below. - -Once you've cloned the example you want, head back to the [Routing](../routing) guide to learn how to use TanStack Start! - -## Examples - -TanStack Start has load of examples to get you started. Pick one of the examples below to get started! +In addition to the Basic example, there are a variety of other examples to get you started with different add-ons and use-cases. Simply replace `start-basic` in the commands above with the slug of the example you'd like to use from the list below to clone and get started: - [Bare](https://github.com/TanStack/router/tree/main/examples/solid/start-bare) (start-bare) -- [Basic](https://github.com/TanStack/router/tree/main/examples/solid/start-basic) (start-basic) -- [Basic Static](https://github.com/TanStack/router/tree/main/examples/solid/start-basic-stats) (start-basic-static) -- [Counter](https://github.com/TanStack/router/tree/main/examples/solid/start-counter) (start-counter) - -### Stackblitz - -Each example above has an embedded stackblitz preview to find the one that feels like a good starting point - -### Quick Deploy - -To quickly deploy an example, click the **Deploy to Netlify** button on an example's page to both clone and deploy the example to Netlify. - -### Manual Deploy -To manually clone and deploy the example to anywhere else you'd like, use the following commands replacing `EXAMPLE_SLUG` with the slug of the example you'd like to use from above: - -```bash -npx gitpick TanStack/router/tree/main/examples/solid/EXAMPLE_SLUG my-new-project -cd my-new-project -npm install -npm run dev -``` +> [!NOTE] +> While not specific to Start, TanStack Router offers a variety of other examples that may help you understand more about how TanStack Router works. See these examples in [Router's documentation](https://tanstack.com/router/latest/docs/examples/overview). -Once you've clone or deployed an example, head back to the [Routing](../routing) guide to learn how to use TanStack Start! +Each example will include an embedded Stackblitz preview to help you find the one that works best for your use-case. To see, simply select the example you wish to preview from the "Examples" section in the sidebar. diff --git a/examples/react/authenticated-routes-firebase/package.json b/examples/react/authenticated-routes-firebase/package.json index 1b78b3d48e5..d899793c564 100644 --- a/examples/react/authenticated-routes-firebase/package.json +++ b/examples/react/authenticated-routes-firebase/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "autoprefixer": "^10.4.20", "firebase": "^11.4.0", "postcss": "^8.5.1", diff --git a/examples/react/authenticated-routes/package.json b/examples/react/authenticated-routes/package.json index dbe056eecd9..7dd273eb6fb 100644 --- a/examples/react/authenticated-routes/package.json +++ b/examples/react/authenticated-routes/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/basic-default-search-params/package.json b/examples/react/basic-default-search-params/package.json index a5db9a618a2..d7e894675c5 100644 --- a/examples/react/basic-default-search-params/package.json +++ b/examples/react/basic-default-search-params/package.json @@ -11,7 +11,7 @@ "dependencies": { "@tanstack/react-query": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/basic-devtools-panel/package.json b/examples/react/basic-devtools-panel/package.json index 39088e37a08..a73b2c38524 100644 --- a/examples/react/basic-devtools-panel/package.json +++ b/examples/react/basic-devtools-panel/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "@tanstack/react-query-devtools": "^5.67.2", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/examples/react/basic-file-based/package.json b/examples/react/basic-file-based/package.json index f7d89fa14a8..6d1c37e92e9 100644 --- a/examples/react/basic-file-based/package.json +++ b/examples/react/basic-file-based/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/basic-non-nested-devtools/package.json b/examples/react/basic-non-nested-devtools/package.json index 65315bebfe0..1324dc5cb2a 100644 --- a/examples/react/basic-non-nested-devtools/package.json +++ b/examples/react/basic-non-nested-devtools/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/basic-react-query-file-based/package.json b/examples/react/basic-react-query-file-based/package.json index 6d6bcf09472..8ad14431243 100644 --- a/examples/react/basic-react-query-file-based/package.json +++ b/examples/react/basic-react-query-file-based/package.json @@ -12,8 +12,8 @@ "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/basic-react-query/package.json b/examples/react/basic-react-query/package.json index cc0e5c8c28f..6855cdaa37f 100644 --- a/examples/react/basic-react-query/package.json +++ b/examples/react/basic-react-query/package.json @@ -12,7 +12,7 @@ "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/basic-ssr-file-based/package.json b/examples/react/basic-ssr-file-based/package.json index ea43317652a..05e28bf2e47 100644 --- a/examples/react/basic-ssr-file-based/package.json +++ b/examples/react/basic-ssr-file-based/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "compression": "^1.8.0", "express": "^4.21.2", "get-port": "^7.1.0", @@ -21,7 +21,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "@types/express": "^4.17.23", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.1", diff --git a/examples/react/basic-ssr-streaming-file-based/package.json b/examples/react/basic-ssr-streaming-file-based/package.json index 61cdd7d241c..719278821d8 100644 --- a/examples/react/basic-ssr-streaming-file-based/package.json +++ b/examples/react/basic-ssr-streaming-file-based/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "compression": "^1.7.5", "express": "^4.21.2", "get-port": "^7.1.0", @@ -21,7 +21,7 @@ "react-dom": "^19.0.0" }, "devDependencies": { - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "@types/express": "^4.17.21", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.1", diff --git a/examples/react/basic-virtual-file-based/package.json b/examples/react/basic-virtual-file-based/package.json index e80c8fe5f92..deee41fdba6 100644 --- a/examples/react/basic-virtual-file-based/package.json +++ b/examples/react/basic-virtual-file-based/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "@tanstack/virtual-file-routes": "^1.132.31", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/examples/react/basic-virtual-inside-file-based/package.json b/examples/react/basic-virtual-inside-file-based/package.json index ec192d0511d..68d628b02ff 100644 --- a/examples/react/basic-virtual-inside-file-based/package.json +++ b/examples/react/basic-virtual-inside-file-based/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "@tanstack/virtual-file-routes": "^1.132.31", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/examples/react/basic/package.json b/examples/react/basic/package.json index 163a3e699bf..43ab51b768a 100644 --- a/examples/react/basic/package.json +++ b/examples/react/basic/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/deferred-data/package.json b/examples/react/deferred-data/package.json index 00c69ad85a6..842c09ecbc1 100644 --- a/examples/react/deferred-data/package.json +++ b/examples/react/deferred-data/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/i18n-paraglide/package.json b/examples/react/i18n-paraglide/package.json index 06e8e014d63..a487549a019 100644 --- a/examples/react/i18n-paraglide/package.json +++ b/examples/react/i18n-paraglide/package.json @@ -12,7 +12,7 @@ "dependencies": { "@tailwindcss/vite": "^4.1.13", "@tanstack/react-router": "^1.132.47", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "react": "^19.1.1", "react-dom": "^19.1.1", "tailwindcss": "^4.1.13" diff --git a/examples/react/kitchen-sink-file-based/package.json b/examples/react/kitchen-sink-file-based/package.json index 3da522ccfed..860a64becfb 100644 --- a/examples/react/kitchen-sink-file-based/package.json +++ b/examples/react/kitchen-sink-file-based/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/examples/react/kitchen-sink-react-query-file-based/package.json b/examples/react/kitchen-sink-react-query-file-based/package.json index cde910bbfe8..a2829995124 100644 --- a/examples/react/kitchen-sink-react-query-file-based/package.json +++ b/examples/react/kitchen-sink-react-query-file-based/package.json @@ -12,8 +12,8 @@ "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/examples/react/kitchen-sink-react-query/package.json b/examples/react/kitchen-sink-react-query/package.json index fcc0ba7bb8f..1064226d0ef 100644 --- a/examples/react/kitchen-sink-react-query/package.json +++ b/examples/react/kitchen-sink-react-query/package.json @@ -12,7 +12,7 @@ "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/examples/react/kitchen-sink/package.json b/examples/react/kitchen-sink/package.json index 6cd7576eed8..9b6750687d9 100644 --- a/examples/react/kitchen-sink/package.json +++ b/examples/react/kitchen-sink/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "immer": "^10.1.1", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/examples/react/large-file-based/package.json b/examples/react/large-file-based/package.json index 206d52ba884..0eb983976de 100644 --- a/examples/react/large-file-based/package.json +++ b/examples/react/large-file-based/package.json @@ -13,8 +13,8 @@ "dependencies": { "@tanstack/react-query": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/location-masking/package.json b/examples/react/location-masking/package.json index a4af44b7d51..22dcfbf730e 100644 --- a/examples/react/location-masking/package.json +++ b/examples/react/location-masking/package.json @@ -12,7 +12,7 @@ "@radix-ui/react-dialog": "^1.1.6", "@tanstack/react-query": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/navigation-blocking/package.json b/examples/react/navigation-blocking/package.json index 86a57902a1a..55b347076c4 100644 --- a/examples/react/navigation-blocking/package.json +++ b/examples/react/navigation-blocking/package.json @@ -11,7 +11,7 @@ "dependencies": { "@tanstack/react-query": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/quickstart-esbuild-file-based/package.json b/examples/react/quickstart-esbuild-file-based/package.json index 2a84e782912..2389325a6e0 100644 --- a/examples/react/quickstart-esbuild-file-based/package.json +++ b/examples/react/quickstart-esbuild-file-based/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/quickstart-file-based/package.json b/examples/react/quickstart-file-based/package.json index ed2dba51a4d..4c454344577 100644 --- a/examples/react/quickstart-file-based/package.json +++ b/examples/react/quickstart-file-based/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/quickstart-rspack-file-based/package.json b/examples/react/quickstart-rspack-file-based/package.json index df53759d458..b67284d8b08 100644 --- a/examples/react/quickstart-rspack-file-based/package.json +++ b/examples/react/quickstart-rspack-file-based/package.json @@ -9,7 +9,7 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "postcss": "^8.5.1", @@ -19,7 +19,7 @@ "devDependencies": { "@rsbuild/core": "1.2.4", "@rsbuild/plugin-react": "1.1.0", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "typescript": "^5.6.2" diff --git a/examples/react/quickstart-webpack-file-based/package.json b/examples/react/quickstart-webpack-file-based/package.json index 9b568c56aa4..b7090b202f1 100644 --- a/examples/react/quickstart-webpack-file-based/package.json +++ b/examples/react/quickstart-webpack-file-based/package.json @@ -8,13 +8,13 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@swc/core": "^1.10.15", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "@types/react": "^19.0.8", "@types/react-dom": "^19.0.3", "html-webpack-plugin": "^5.6.3", diff --git a/examples/react/quickstart/package.json b/examples/react/quickstart/package.json index b1a9f917c8f..e9f45c71740 100644 --- a/examples/react/quickstart/package.json +++ b/examples/react/quickstart/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "postcss": "^8.5.1", diff --git a/examples/react/router-monorepo-react-query/package.json b/examples/react/router-monorepo-react-query/package.json index 02897d44361..f2968c6f605 100644 --- a/examples/react/router-monorepo-react-query/package.json +++ b/examples/react/router-monorepo-react-query/package.json @@ -13,8 +13,8 @@ "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1" diff --git a/examples/react/router-monorepo-react-query/packages/app/package.json b/examples/react/router-monorepo-react-query/packages/app/package.json index e8464bbd79a..54922187eb5 100644 --- a/examples/react/router-monorepo-react-query/packages/app/package.json +++ b/examples/react/router-monorepo-react-query/packages/app/package.json @@ -20,7 +20,7 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "postcss": "^8.5.1", "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", diff --git a/examples/react/router-monorepo-react-query/packages/router/package.json b/examples/react/router-monorepo-react-query/packages/router/package.json index dc33b75e54e..2ba7477f7f8 100644 --- a/examples/react/router-monorepo-react-query/packages/router/package.json +++ b/examples/react/router-monorepo-react-query/packages/router/package.json @@ -11,7 +11,7 @@ "@tanstack/history": "^1.132.31", "@tanstack/react-query": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "@router-mono-react-query/post-query": "workspace:*", "redaxios": "^0.5.1", "zod": "^3.24.2", diff --git a/examples/react/router-monorepo-simple-lazy/package.json b/examples/react/router-monorepo-simple-lazy/package.json index e1432fffa18..b0b4e4858cf 100644 --- a/examples/react/router-monorepo-simple-lazy/package.json +++ b/examples/react/router-monorepo-simple-lazy/package.json @@ -9,8 +9,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1" diff --git a/examples/react/router-monorepo-simple-lazy/packages/app/package.json b/examples/react/router-monorepo-simple-lazy/packages/app/package.json index 7e7c136bf2b..5e1a26788c4 100644 --- a/examples/react/router-monorepo-simple-lazy/packages/app/package.json +++ b/examples/react/router-monorepo-simple-lazy/packages/app/package.json @@ -19,7 +19,7 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "postcss": "^8.5.1", "autoprefixer": "^10.4.20", "tailwindcss": "^3.4.17", diff --git a/examples/react/router-monorepo-simple-lazy/packages/router/package.json b/examples/react/router-monorepo-simple-lazy/packages/router/package.json index a6518fddb73..1a0f35f5f09 100644 --- a/examples/react/router-monorepo-simple-lazy/packages/router/package.json +++ b/examples/react/router-monorepo-simple-lazy/packages/router/package.json @@ -10,7 +10,7 @@ "dependencies": { "@tanstack/history": "^1.132.31", "@tanstack/react-router": "^1.132.47", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "redaxios": "^0.5.1", "zod": "^3.24.2", "react": "^19.0.0", diff --git a/examples/react/router-monorepo-simple/package.json b/examples/react/router-monorepo-simple/package.json index 16134e49ef8..82741e9616b 100644 --- a/examples/react/router-monorepo-simple/package.json +++ b/examples/react/router-monorepo-simple/package.json @@ -9,8 +9,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1" diff --git a/examples/react/router-monorepo-simple/packages/app/package.json b/examples/react/router-monorepo-simple/packages/app/package.json index 3062e88a1c4..6fc19d3f72c 100644 --- a/examples/react/router-monorepo-simple/packages/app/package.json +++ b/examples/react/router-monorepo-simple/packages/app/package.json @@ -19,7 +19,7 @@ "@types/react-dom": "^19.0.3", "@vitejs/plugin-react": "^4.3.4", "typescript": "^5.7.2", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "vite": "^7.1.7", "postcss": "^8.5.1", "autoprefixer": "^10.4.20", diff --git a/examples/react/router-monorepo-simple/packages/router/package.json b/examples/react/router-monorepo-simple/packages/router/package.json index d8a3012e5a8..287ed34a07c 100644 --- a/examples/react/router-monorepo-simple/packages/router/package.json +++ b/examples/react/router-monorepo-simple/packages/router/package.json @@ -10,7 +10,7 @@ "dependencies": { "@tanstack/history": "^1.132.31", "@tanstack/react-router": "^1.132.47", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "redaxios": "^0.5.1", "zod": "^3.24.2", "react": "^19.0.0", diff --git a/examples/react/scroll-restoration/package.json b/examples/react/scroll-restoration/package.json index 72517243b99..1600f720ddf 100644 --- a/examples/react/scroll-restoration/package.json +++ b/examples/react/scroll-restoration/package.json @@ -11,7 +11,7 @@ "dependencies": { "@tanstack/react-router": "^1.132.47", "@tanstack/react-virtual": "^3.13.0", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "postcss": "^8.5.1", diff --git a/examples/react/search-validator-adapters/package.json b/examples/react/search-validator-adapters/package.json index 2b2d0567bab..b18b3286de2 100644 --- a/examples/react/search-validator-adapters/package.json +++ b/examples/react/search-validator-adapters/package.json @@ -13,8 +13,8 @@ "@tanstack/arktype-adapter": "^1.132.47", "@tanstack/react-query": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "@tanstack/valibot-adapter": "^1.132.47", "@tanstack/zod-adapter": "^1.132.47", "arktype": "^2.1.7", diff --git a/examples/react/start-bare/package.json b/examples/react/start-bare/package.json index 0dee94790fd..5448bad516e 100644 --- a/examples/react/start-bare/package.json +++ b/examples/react/start-bare/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "zod": "^3.24.2" diff --git a/examples/react/start-basic-auth/package.json b/examples/react/start-basic-auth/package.json index f974570ffef..5f6d388a162 100644 --- a/examples/react/start-basic-auth/package.json +++ b/examples/react/start-basic-auth/package.json @@ -12,8 +12,8 @@ "dependencies": { "@prisma/client": "5.22.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "prisma": "^5.22.0", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/examples/react/start-basic-cloudflare/package.json b/examples/react/start-basic-cloudflare/package.json index 5fcdad97490..b7d612622a8 100644 --- a/examples/react/start-basic-cloudflare/package.json +++ b/examples/react/start-basic-cloudflare/package.json @@ -13,8 +13,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0" }, diff --git a/examples/react/start-basic-react-query/package.json b/examples/react/start-basic-react-query/package.json index b7a985d8036..d3eac0442c2 100644 --- a/examples/react/start-basic-react-query/package.json +++ b/examples/react/start-basic-react-query/package.json @@ -13,8 +13,8 @@ "@tanstack/react-query-devtools": "^5.66.0", "@tanstack/react-router": "^1.132.47", "@tanstack/react-router-ssr-query": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/start-basic-static/package.json b/examples/react/start-basic-static/package.json index 6a432e182f1..38bf9e10a38 100644 --- a/examples/react/start-basic-static/package.json +++ b/examples/react/start-basic-static/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "@tanstack/start-static-server-functions": "^1.132.48", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/examples/react/start-basic/package.json b/examples/react/start-basic/package.json index 017d0540be1..6c79bf11204 100644 --- a/examples/react/start-basic/package.json +++ b/examples/react/start-basic/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^2.6.0", diff --git a/examples/react/start-bun/package.json b/examples/react/start-bun/package.json index 3dc55e6a56b..95c553af3d2 100644 --- a/examples/react/start-bun/package.json +++ b/examples/react/start-bun/package.json @@ -16,10 +16,10 @@ "@tailwindcss/vite": "^4.1.13", "@tanstack/react-devtools": "^0.7.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "@tanstack/react-router-ssr-query": "^1.132.47", - "@tanstack/react-start": "^1.132.48", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-start": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "react": "^19.1.1", "react-dom": "^19.1.1", "tailwindcss": "^4.1.13", diff --git a/examples/react/start-convex-trellaux/package.json b/examples/react/start-convex-trellaux/package.json index f6ff42b6aac..ac2dfc30cea 100644 --- a/examples/react/start-convex-trellaux/package.json +++ b/examples/react/start-convex-trellaux/package.json @@ -16,8 +16,8 @@ "@tanstack/react-query-devtools": "^5.66.0", "@tanstack/react-router": "^1.132.47", "@tanstack/react-router-ssr-query": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "concurrently": "^8.2.2", "convex": "^1.19.0", "ky": "^1.7.4", diff --git a/examples/react/start-counter/package.json b/examples/react/start-counter/package.json index 3775d6898ea..8b8739cfabd 100644 --- a/examples/react/start-counter/package.json +++ b/examples/react/start-counter/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0" }, diff --git a/examples/react/start-i18n-paraglide/package.json b/examples/react/start-i18n-paraglide/package.json index e89ff253148..7923fae0c94 100644 --- a/examples/react/start-i18n-paraglide/package.json +++ b/examples/react/start-i18n-paraglide/package.json @@ -11,8 +11,8 @@ "dependencies": { "@tanstack/react-devtools": "^0.7.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "react": "^19.1.1", "react-dom": "^19.1.1" }, diff --git a/examples/react/start-large/package.json b/examples/react/start-large/package.json index 4992f8fe46a..a691d8f1cc3 100644 --- a/examples/react/start-large/package.json +++ b/examples/react/start-large/package.json @@ -13,8 +13,8 @@ "dependencies": { "@tanstack/react-query": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/start-material-ui/package.json b/examples/react/start-material-ui/package.json index 428e3c24738..3cdf27724e1 100644 --- a/examples/react/start-material-ui/package.json +++ b/examples/react/start-material-ui/package.json @@ -15,8 +15,8 @@ "@fontsource-variable/roboto": "5.2.5", "@mui/material": "6.4.7", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-start": "^1.132.48", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-start": "^1.132.51", + "@tanstack/react-router-devtools": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "zod": "^3.24.2" diff --git a/examples/react/start-streaming-data-from-server-functions/package.json b/examples/react/start-streaming-data-from-server-functions/package.json index f3b483b9f2d..88d01c7f000 100644 --- a/examples/react/start-streaming-data-from-server-functions/package.json +++ b/examples/react/start-streaming-data-from-server-functions/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "zod": "^3.24.2" diff --git a/examples/react/start-supabase-basic/package.json b/examples/react/start-supabase-basic/package.json index 62fda87dae1..1e69b05e2fd 100644 --- a/examples/react/start-supabase-basic/package.json +++ b/examples/react/start-supabase-basic/package.json @@ -16,8 +16,8 @@ "@supabase/ssr": "^0.5.2", "@supabase/supabase-js": "^2.48.1", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1" diff --git a/examples/react/start-tailwind-v4/package.json b/examples/react/start-tailwind-v4/package.json index 587494c9671..154ce9a8d21 100644 --- a/examples/react/start-tailwind-v4/package.json +++ b/examples/react/start-tailwind-v4/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "tailwind-merge": "^2.6.0", diff --git a/examples/react/start-trellaux/package.json b/examples/react/start-trellaux/package.json index ceb14bbca81..41c465a2195 100644 --- a/examples/react/start-trellaux/package.json +++ b/examples/react/start-trellaux/package.json @@ -13,8 +13,8 @@ "@tanstack/react-query-devtools": "^5.66.0", "@tanstack/react-router": "^1.132.47", "@tanstack/react-router-ssr-query": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "ky": "^1.7.4", "msw": "^2.7.0", "react": "^19.0.0", diff --git a/examples/react/start-workos/package.json b/examples/react/start-workos/package.json index 6f6406de1c8..0f3b216618c 100644 --- a/examples/react/start-workos/package.json +++ b/examples/react/start-workos/package.json @@ -15,8 +15,8 @@ "dependencies": { "@radix-ui/themes": "^3.2.1", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/react-start": "^1.132.48", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/react-start": "^1.132.51", "@workos-inc/node": "^7.45.0", "iron-session": "^8.0.4", "jose": "^6.0.10", diff --git a/examples/react/view-transitions/package.json b/examples/react/view-transitions/package.json index f03534b5a96..2898e8a7a76 100644 --- a/examples/react/view-transitions/package.json +++ b/examples/react/view-transitions/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "react": "^19.0.0", "react-dom": "^19.0.0", "redaxios": "^0.5.1", diff --git a/examples/react/with-framer-motion/package.json b/examples/react/with-framer-motion/package.json index fa6fb6e379d..9f4e09812ad 100644 --- a/examples/react/with-framer-motion/package.json +++ b/examples/react/with-framer-motion/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", + "@tanstack/react-router-devtools": "^1.132.51", "framer-motion": "^11.18.2", "react": "^19.0.0", "react-dom": "^19.0.0", diff --git a/examples/react/with-trpc-react-query/package.json b/examples/react/with-trpc-react-query/package.json index 0de378ae527..e931225ab86 100644 --- a/examples/react/with-trpc-react-query/package.json +++ b/examples/react/with-trpc-react-query/package.json @@ -13,8 +13,8 @@ "@tanstack/react-query": "^5.66.0", "@tanstack/react-query-devtools": "^5.66.0", "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "@trpc/client": "^11.4.3", "@trpc/server": "^11.4.3", "@trpc/tanstack-react-query": "^11.4.3", diff --git a/examples/react/with-trpc/package.json b/examples/react/with-trpc/package.json index f1d6e4aeb33..364efe0b6fb 100644 --- a/examples/react/with-trpc/package.json +++ b/examples/react/with-trpc/package.json @@ -11,8 +11,8 @@ }, "dependencies": { "@tanstack/react-router": "^1.132.47", - "@tanstack/react-router-devtools": "^1.132.50", - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/react-router-devtools": "^1.132.51", + "@tanstack/router-plugin": "^1.132.51", "@trpc/client": "^11.4.3", "@trpc/server": "^11.4.3", "autoprefixer": "^10.4.20", diff --git a/examples/solid/basic-devtools-panel/package.json b/examples/solid/basic-devtools-panel/package.json index de0c1404c17..90d7c126401 100644 --- a/examples/solid/basic-devtools-panel/package.json +++ b/examples/solid/basic-devtools-panel/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/solid-router": "^1.132.49", - "@tanstack/solid-router-devtools": "^1.132.50", + "@tanstack/solid-router-devtools": "^1.132.51", "solid-js": "^1.9.5", "redaxios": "^0.5.1", "postcss": "^8.5.1", diff --git a/examples/solid/basic-file-based/package.json b/examples/solid/basic-file-based/package.json index 9e6f18aef84..991f0ac2ff0 100644 --- a/examples/solid/basic-file-based/package.json +++ b/examples/solid/basic-file-based/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/solid-router": "^1.132.49", - "@tanstack/solid-router-devtools": "^1.132.50", + "@tanstack/solid-router-devtools": "^1.132.51", "autoprefixer": "^10.4.20", "postcss": "^8.5.1", "redaxios": "^0.5.1", @@ -19,7 +19,7 @@ "zod": "^3.24.2" }, "devDependencies": { - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "typescript": "^5.7.2", "vite": "^7.1.7", "vite-plugin-solid": "^2.11.8" diff --git a/examples/solid/basic-non-nested-devtools/package.json b/examples/solid/basic-non-nested-devtools/package.json index e604f1e8a2a..5a293973d36 100644 --- a/examples/solid/basic-non-nested-devtools/package.json +++ b/examples/solid/basic-non-nested-devtools/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/solid-router": "^1.132.49", - "@tanstack/solid-router-devtools": "^1.132.50", + "@tanstack/solid-router-devtools": "^1.132.51", "redaxios": "^0.5.1", "postcss": "^8.5.1", "solid-js": "^1.9.5", diff --git a/examples/solid/basic-solid-query-file-based/package.json b/examples/solid/basic-solid-query-file-based/package.json index fe2e1c3aacb..dddcd60af5d 100644 --- a/examples/solid/basic-solid-query-file-based/package.json +++ b/examples/solid/basic-solid-query-file-based/package.json @@ -13,7 +13,7 @@ "@tanstack/solid-query": "^5.71.9", "@tanstack/solid-query-devtools": "^5.71.9", "@tanstack/solid-router": "^1.132.49", - "@tanstack/solid-router-devtools": "^1.132.50", + "@tanstack/solid-router-devtools": "^1.132.51", "solid-js": "^1.9.5", "redaxios": "^0.5.1", "postcss": "^8.5.1", @@ -22,7 +22,7 @@ "zod": "^3.24.2" }, "devDependencies": { - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "typescript": "^5.7.2", "vite": "^7.1.7", "vite-plugin-solid": "^2.11.8" diff --git a/examples/solid/basic-solid-query/package.json b/examples/solid/basic-solid-query/package.json index 6293eb8b53b..6020a622693 100644 --- a/examples/solid/basic-solid-query/package.json +++ b/examples/solid/basic-solid-query/package.json @@ -12,7 +12,7 @@ "@tanstack/solid-query": "^5.71.9", "@tanstack/solid-query-devtools": "^5.71.9", "@tanstack/solid-router": "^1.132.49", - "@tanstack/solid-router-devtools": "^1.132.50", + "@tanstack/solid-router-devtools": "^1.132.51", "solid-js": "^1.9.5", "redaxios": "^0.5.1", "postcss": "^8.5.1", @@ -20,7 +20,7 @@ "tailwindcss": "^3.4.17" }, "devDependencies": { - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "typescript": "^5.7.2", "vite": "^7.1.7", "vite-plugin-solid": "^2.11.8" diff --git a/examples/solid/basic-ssr-streaming-file-based/package.json b/examples/solid/basic-ssr-streaming-file-based/package.json index 5b4ad7b2b41..b2645e45ce1 100644 --- a/examples/solid/basic-ssr-streaming-file-based/package.json +++ b/examples/solid/basic-ssr-streaming-file-based/package.json @@ -12,7 +12,7 @@ }, "dependencies": { "@tanstack/solid-router": "^1.132.49", - "@tanstack/solid-router-devtools": "^1.132.50", + "@tanstack/solid-router-devtools": "^1.132.51", "autoprefixer": "^10.4.20", "compression": "^1.7.5", "express": "^4.21.2", @@ -25,7 +25,7 @@ "zod": "^3.24.2" }, "devDependencies": { - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "@types/express": "^4.17.21", "typescript": "^5.7.2", "vite": "^7.1.7", diff --git a/examples/solid/basic/package.json b/examples/solid/basic/package.json index 780099e096b..1114b2db98a 100644 --- a/examples/solid/basic/package.json +++ b/examples/solid/basic/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/solid-router": "^1.132.49", - "@tanstack/solid-router-devtools": "^1.132.50", + "@tanstack/solid-router-devtools": "^1.132.51", "redaxios": "^0.5.1", "postcss": "^8.5.1", "solid-js": "^1.9.5", diff --git a/examples/solid/kitchen-sink-file-based/package.json b/examples/solid/kitchen-sink-file-based/package.json index 8d2acbc5546..11a8469d7a6 100644 --- a/examples/solid/kitchen-sink-file-based/package.json +++ b/examples/solid/kitchen-sink-file-based/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/solid-router": "^1.132.49", - "@tanstack/solid-router-devtools": "^1.132.50", + "@tanstack/solid-router-devtools": "^1.132.51", "immer": "^10.1.1", "solid-js": "^1.9.5", "redaxios": "^0.5.1", @@ -20,7 +20,7 @@ "zod": "^3.24.2" }, "devDependencies": { - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "typescript": "^5.7.2", "vite": "^7.1.7", "vite-plugin-solid": "^2.11.8" diff --git a/examples/solid/quickstart-file-based/package.json b/examples/solid/quickstart-file-based/package.json index d03829ac8dc..9a8b3c2074f 100644 --- a/examples/solid/quickstart-file-based/package.json +++ b/examples/solid/quickstart-file-based/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@tanstack/solid-router": "^1.132.49", - "@tanstack/solid-router-devtools": "^1.132.50", + "@tanstack/solid-router-devtools": "^1.132.51", "autoprefixer": "^10.4.20", "postcss": "^8.5.1", "redaxios": "^0.5.1", @@ -19,7 +19,7 @@ "zod": "^3.24.2" }, "devDependencies": { - "@tanstack/router-plugin": "^1.132.47", + "@tanstack/router-plugin": "^1.132.51", "typescript": "^5.7.2", "vite": "^7.1.7", "vite-plugin-solid": "^2.11.8" diff --git a/examples/solid/start-bare/package.json b/examples/solid/start-bare/package.json index ad1f92ac9ea..1c6e60b756f 100644 --- a/examples/solid/start-bare/package.json +++ b/examples/solid/start-bare/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/solid-router": "^1.132.49", - "@tanstack/solid-router-devtools": "^1.132.50", - "@tanstack/solid-start": "^1.132.49", + "@tanstack/solid-router-devtools": "^1.132.51", + "@tanstack/solid-start": "^1.132.51", "solid-js": "^1.9.5", "redaxios": "^0.5.1", "tailwind-merge": "^2.6.0", diff --git a/examples/solid/start-basic-static/package.json b/examples/solid/start-basic-static/package.json index 7d239c6be24..4b7abc5618a 100644 --- a/examples/solid/start-basic-static/package.json +++ b/examples/solid/start-basic-static/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/solid-router": "^1.132.49", - "@tanstack/solid-router-devtools": "^1.132.50", - "@tanstack/solid-start": "^1.132.49", + "@tanstack/solid-router-devtools": "^1.132.51", + "@tanstack/solid-start": "^1.132.51", "@tanstack/start-static-server-functions": "^1.132.48", "solid-js": "^1.9.5", "redaxios": "^0.5.1", diff --git a/examples/solid/start-basic/package.json b/examples/solid/start-basic/package.json index 567c218133f..228c1438ff0 100644 --- a/examples/solid/start-basic/package.json +++ b/examples/solid/start-basic/package.json @@ -10,8 +10,8 @@ }, "dependencies": { "@tanstack/solid-router": "^1.132.49", - "@tanstack/solid-router-devtools": "^1.132.50", - "@tanstack/solid-start": "^1.132.49", + "@tanstack/solid-router-devtools": "^1.132.51", + "@tanstack/solid-start": "^1.132.51", "solid-js": "^1.9.5", "redaxios": "^0.5.1", "tailwind-merge": "^2.6.0" diff --git a/packages/directive-functions-plugin/package.json b/packages/directive-functions-plugin/package.json index 59fa34ddecf..874434bb1c5 100644 --- a/packages/directive-functions-plugin/package.json +++ b/packages/directive-functions-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/directive-functions-plugin", - "version": "1.132.42", + "version": "1.132.51", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/react-router-devtools/package.json b/packages/react-router-devtools/package.json index 6a9a13318c4..83acbff94a9 100644 --- a/packages/react-router-devtools/package.json +++ b/packages/react-router-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-router-devtools", - "version": "1.132.50", + "version": "1.132.51", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/react-start/package.json b/packages/react-start/package.json index 2a92b15fa79..d4d22a6783d 100644 --- a/packages/react-start/package.json +++ b/packages/react-start/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/react-start", - "version": "1.132.48", + "version": "1.132.51", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/router-cli/package.json b/packages/router-cli/package.json index 853f0b5b73c..432871c8bd5 100644 --- a/packages/router-cli/package.json +++ b/packages/router-cli/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-cli", - "version": "1.132.47", + "version": "1.132.51", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/router-devtools-core/package.json b/packages/router-devtools-core/package.json index d0c373495cd..f1d67ed773a 100644 --- a/packages/router-devtools-core/package.json +++ b/packages/router-devtools-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-devtools-core", - "version": "1.132.50", + "version": "1.132.51", "description": "Modern and scalable routing for Web applications", "author": "Tanner Linsley", "license": "MIT", @@ -64,10 +64,10 @@ "dependencies": { "clsx": "^2.1.1", "goober": "^2.1.16", - "solid-js": "^1.9.5", "vite": "^7.1.7" }, "devDependencies": { + "solid-js": "^1.9.5", "vite-plugin-solid": "^2.11.8" }, "peerDependencies": { diff --git a/packages/router-devtools-core/vite.config.ts b/packages/router-devtools-core/vite.config.ts index a464717a1e5..dbfc651cfd0 100644 --- a/packages/router-devtools-core/vite.config.ts +++ b/packages/router-devtools-core/vite.config.ts @@ -7,10 +7,15 @@ const config = defineConfig({ plugins: [solid()] as UserConfig['plugins'], }) -export default mergeConfig( +const merged = mergeConfig( config, tanstackViteConfig({ entry: './src/index.tsx', srcDir: './src', }), ) + +merged.build.rollupOptions.output.manualChunks = false +merged.build.rollupOptions.output.preserveModules = false + +export default merged diff --git a/packages/router-devtools/package.json b/packages/router-devtools/package.json index 1fa0529a2f2..1e10beee91d 100644 --- a/packages/router-devtools/package.json +++ b/packages/router-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-devtools", - "version": "1.132.50", + "version": "1.132.51", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/router-generator/package.json b/packages/router-generator/package.json index 28f35114fef..2ea8a9dda86 100644 --- a/packages/router-generator/package.json +++ b/packages/router-generator/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-generator", - "version": "1.132.47", + "version": "1.132.51", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/router-plugin/package.json b/packages/router-plugin/package.json index dd327825bb1..cafeda35f81 100644 --- a/packages/router-plugin/package.json +++ b/packages/router-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-plugin", - "version": "1.132.47", + "version": "1.132.51", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/router-utils/package.json b/packages/router-utils/package.json index 8d5a1db2c46..bb9387af349 100644 --- a/packages/router-utils/package.json +++ b/packages/router-utils/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-utils", - "version": "1.132.31", + "version": "1.132.51", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -69,8 +69,8 @@ "@babel/preset-typescript": "^7.27.1", "ansis": "^4.1.0", "diff": "^8.0.2", - "fast-glob": "^3.3.3", - "pathe": "^2.0.3" + "pathe": "^2.0.3", + "tinyglobby": "^0.2.15" }, "devDependencies": { "@babel/types": "^7.27.6", diff --git a/packages/router-utils/src/copy-files-plugin.ts b/packages/router-utils/src/copy-files-plugin.ts index 13cfd31ec45..77727197589 100644 --- a/packages/router-utils/src/copy-files-plugin.ts +++ b/packages/router-utils/src/copy-files-plugin.ts @@ -1,6 +1,6 @@ import { copyFile, mkdir } from 'node:fs/promises' import { dirname, join } from 'pathe' -import fg from 'fast-glob' +import { glob } from 'tinyglobby' import type { Plugin } from 'vite' export function copyFilesPlugin({ @@ -15,7 +15,7 @@ export function copyFilesPlugin({ return { name: 'copy-files', async writeBundle() { - const entries = await fg(pattern, { cwd: fromDir }) + const entries = await glob(pattern, { cwd: fromDir }) if (entries.length === 0) { throw new Error( `No files found matching pattern "${pattern}" in directory "${fromDir}"`, diff --git a/packages/router-vite-plugin/package.json b/packages/router-vite-plugin/package.json index 859e304b32d..c0bed3ab6b1 100644 --- a/packages/router-vite-plugin/package.json +++ b/packages/router-vite-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/router-vite-plugin", - "version": "1.132.47", + "version": "1.132.51", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/server-functions-plugin/package.json b/packages/server-functions-plugin/package.json index d68e6a05687..aebaa8db81e 100644 --- a/packages/server-functions-plugin/package.json +++ b/packages/server-functions-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/server-functions-plugin", - "version": "1.132.42", + "version": "1.132.51", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/solid-router-devtools/package.json b/packages/solid-router-devtools/package.json index 32448f939e8..b73db8601c9 100644 --- a/packages/solid-router-devtools/package.json +++ b/packages/solid-router-devtools/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-router-devtools", - "version": "1.132.50", + "version": "1.132.51", "description": "Modern and scalable routing for Solid applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/solid-start/package.json b/packages/solid-start/package.json index a2750e71b41..5760cdf7930 100644 --- a/packages/solid-start/package.json +++ b/packages/solid-start/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/solid-start", - "version": "1.132.49", + "version": "1.132.51", "description": "Modern and scalable routing for Solid applications", "author": "Tanner Linsley", "license": "MIT", diff --git a/packages/start-plugin-core/package.json b/packages/start-plugin-core/package.json index a5f68197114..fb6c505d88f 100644 --- a/packages/start-plugin-core/package.json +++ b/packages/start-plugin-core/package.json @@ -1,6 +1,6 @@ { "name": "@tanstack/start-plugin-core", - "version": "1.132.48", + "version": "1.132.51", "description": "Modern and scalable routing for React applications", "author": "Tanner Linsley", "license": "MIT", @@ -70,6 +70,7 @@ "@tanstack/router-plugin": "workspace:*", "@tanstack/router-utils": "workspace:*", "@tanstack/server-functions-plugin": "workspace:*", + "@tanstack/start-client-core": "workspace:*", "@tanstack/start-server-core": "workspace:*", "babel-dead-code-elimination": "^1.0.9", "cheerio": "^1.0.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6dbb750ccf4..ad8e1808efd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5346,7 +5346,7 @@ importers: version: 7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0) vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.0)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0) + version: 3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6(@types/node@22.10.2)(playwright@1.52.0)(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0))(vitest@3.2.4))(@vitest/ui@3.0.6(vitest@3.2.4))(jiti@2.6.0)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0) web-vitals: specifier: ^5.1.0 version: 5.1.0 @@ -7056,9 +7056,6 @@ importers: goober: specifier: ^2.1.16 version: 2.1.16(csstype@3.1.3) - solid-js: - specifier: ^1.9.5 - version: 1.9.5 tiny-invariant: specifier: ^1.3.3 version: 1.3.3 @@ -7066,6 +7063,9 @@ importers: specifier: ^7.1.7 version: 7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0) devDependencies: + solid-js: + specifier: ^1.9.5 + version: 1.9.5 vite-plugin-solid: specifier: ^2.11.8 version: 2.11.8(@testing-library/jest-dom@6.6.3)(solid-js@1.9.5)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0)) @@ -7200,12 +7200,12 @@ importers: diff: specifier: ^8.0.2 version: 8.0.2 - fast-glob: - specifier: ^3.3.3 - version: 3.3.3 pathe: specifier: ^2.0.3 version: 2.0.3 + tinyglobby: + specifier: ^0.2.15 + version: 0.2.15 devDependencies: '@babel/types': specifier: ^7.27.6 @@ -7486,6 +7486,9 @@ importers: '@tanstack/server-functions-plugin': specifier: workspace:* version: link:../server-functions-plugin + '@tanstack/start-client-core': + specifier: workspace:* + version: link:../start-client-core '@tanstack/start-server-core': specifier: workspace:* version: link:../start-server-core @@ -18266,7 +18269,7 @@ snapshots: dependencies: '@eslint-react/eff': 1.26.2 '@typescript-eslint/utils': 8.23.0(eslint@9.22.0(jiti@2.6.0))(typescript@5.9.2) - picomatch: 4.0.2 + picomatch: 4.0.3 ts-pattern: 5.6.2 transitivePeerDependencies: - eslint @@ -27346,7 +27349,7 @@ snapshots: optionalDependencies: vite: 7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0) - vitest@3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.0)(jsdom@25.0.1)(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0): + vitest@3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6(@types/node@22.10.2)(playwright@1.52.0)(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0))(vitest@3.2.4))(@vitest/ui@3.0.6(vitest@3.2.4))(jiti@2.6.0)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 @@ -27375,7 +27378,7 @@ snapshots: '@types/node': 22.10.2 '@vitest/browser': 3.0.6(@types/node@22.10.2)(playwright@1.52.0)(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0))(vitest@3.2.4) '@vitest/ui': 3.0.6(vitest@3.2.4) - jsdom: 25.0.1 + jsdom: 27.0.0(postcss@8.5.6) transitivePeerDependencies: - jiti - less @@ -27390,7 +27393,7 @@ snapshots: - tsx - yaml - vitest@3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.0)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0): + vitest@3.2.4(@types/node@22.10.2)(@vitest/browser@3.0.6)(@vitest/ui@3.0.6)(jiti@2.6.0)(jsdom@25.0.1)(lightningcss@1.30.1)(msw@2.7.0(@types/node@22.10.2)(typescript@5.9.2))(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0): dependencies: '@types/chai': 5.2.2 '@vitest/expect': 3.2.4 @@ -27419,7 +27422,7 @@ snapshots: '@types/node': 22.10.2 '@vitest/browser': 3.0.6(@types/node@22.10.2)(playwright@1.52.0)(typescript@5.9.2)(vite@7.1.7(@types/node@22.10.2)(jiti@2.6.0)(lightningcss@1.30.1)(terser@5.37.0)(tsx@4.20.3)(yaml@2.7.0))(vitest@3.2.4) '@vitest/ui': 3.0.6(vitest@3.2.4) - jsdom: 27.0.0(postcss@8.5.6) + jsdom: 25.0.1 transitivePeerDependencies: - jiti - less