diff --git a/docs/router/framework/react/api/router.md b/docs/router/api/router.md similarity index 100% rename from docs/router/framework/react/api/router.md rename to docs/router/api/router.md diff --git a/docs/router/framework/react/api/router/ActiveLinkOptionsType.md b/docs/router/api/router/ActiveLinkOptionsType.md similarity index 100% rename from docs/router/framework/react/api/router/ActiveLinkOptionsType.md rename to docs/router/api/router/ActiveLinkOptionsType.md diff --git a/docs/router/framework/react/api/router/AsyncRouteComponentType.md b/docs/router/api/router/AsyncRouteComponentType.md similarity index 100% rename from docs/router/framework/react/api/router/AsyncRouteComponentType.md rename to docs/router/api/router/AsyncRouteComponentType.md diff --git a/docs/router/framework/react/api/router/FileRouteClass.md b/docs/router/api/router/FileRouteClass.md similarity index 100% rename from docs/router/framework/react/api/router/FileRouteClass.md rename to docs/router/api/router/FileRouteClass.md diff --git a/docs/router/framework/react/api/router/LinkOptionsType.md b/docs/router/api/router/LinkOptionsType.md similarity index 100% rename from docs/router/framework/react/api/router/LinkOptionsType.md rename to docs/router/api/router/LinkOptionsType.md diff --git a/docs/router/framework/react/api/router/LinkPropsType.md b/docs/router/api/router/LinkPropsType.md similarity index 100% rename from docs/router/framework/react/api/router/LinkPropsType.md rename to docs/router/api/router/LinkPropsType.md diff --git a/docs/router/framework/react/api/router/MatchRouteOptionsType.md b/docs/router/api/router/MatchRouteOptionsType.md similarity index 100% rename from docs/router/framework/react/api/router/MatchRouteOptionsType.md rename to docs/router/api/router/MatchRouteOptionsType.md diff --git a/docs/router/framework/react/api/router/NavigateOptionsType.md b/docs/router/api/router/NavigateOptionsType.md similarity index 100% rename from docs/router/framework/react/api/router/NavigateOptionsType.md rename to docs/router/api/router/NavigateOptionsType.md diff --git a/docs/router/framework/react/api/router/NotFoundErrorType.md b/docs/router/api/router/NotFoundErrorType.md similarity index 100% rename from docs/router/framework/react/api/router/NotFoundErrorType.md rename to docs/router/api/router/NotFoundErrorType.md diff --git a/docs/router/framework/react/api/router/NotFoundRouteClass.md b/docs/router/api/router/NotFoundRouteClass.md similarity index 100% rename from docs/router/framework/react/api/router/NotFoundRouteClass.md rename to docs/router/api/router/NotFoundRouteClass.md diff --git a/docs/router/framework/react/api/router/ParsedHistoryStateType.md b/docs/router/api/router/ParsedHistoryStateType.md similarity index 100% rename from docs/router/framework/react/api/router/ParsedHistoryStateType.md rename to docs/router/api/router/ParsedHistoryStateType.md diff --git a/docs/router/framework/react/api/router/ParsedLocationType.md b/docs/router/api/router/ParsedLocationType.md similarity index 100% rename from docs/router/framework/react/api/router/ParsedLocationType.md rename to docs/router/api/router/ParsedLocationType.md diff --git a/docs/router/framework/react/api/router/RedirectType.md b/docs/router/api/router/RedirectType.md similarity index 100% rename from docs/router/framework/react/api/router/RedirectType.md rename to docs/router/api/router/RedirectType.md diff --git a/docs/router/framework/react/api/router/RegisterType.md b/docs/router/api/router/RegisterType.md similarity index 100% rename from docs/router/framework/react/api/router/RegisterType.md rename to docs/router/api/router/RegisterType.md diff --git a/docs/router/framework/react/api/router/RootRouteClass.md b/docs/router/api/router/RootRouteClass.md similarity index 100% rename from docs/router/framework/react/api/router/RootRouteClass.md rename to docs/router/api/router/RootRouteClass.md diff --git a/docs/router/framework/react/api/router/RouteApiClass.md b/docs/router/api/router/RouteApiClass.md similarity index 100% rename from docs/router/framework/react/api/router/RouteApiClass.md rename to docs/router/api/router/RouteApiClass.md diff --git a/docs/router/framework/react/api/router/RouteApiType.md b/docs/router/api/router/RouteApiType.md similarity index 100% rename from docs/router/framework/react/api/router/RouteApiType.md rename to docs/router/api/router/RouteApiType.md diff --git a/docs/router/framework/react/api/router/RouteClass.md b/docs/router/api/router/RouteClass.md similarity index 100% rename from docs/router/framework/react/api/router/RouteClass.md rename to docs/router/api/router/RouteClass.md diff --git a/docs/router/framework/react/api/router/RouteMaskType.md b/docs/router/api/router/RouteMaskType.md similarity index 100% rename from docs/router/framework/react/api/router/RouteMaskType.md rename to docs/router/api/router/RouteMaskType.md diff --git a/docs/router/framework/react/api/router/RouteMatchType.md b/docs/router/api/router/RouteMatchType.md similarity index 100% rename from docs/router/framework/react/api/router/RouteMatchType.md rename to docs/router/api/router/RouteMatchType.md diff --git a/docs/router/framework/react/api/router/RouteOptionsType.md b/docs/router/api/router/RouteOptionsType.md similarity index 100% rename from docs/router/framework/react/api/router/RouteOptionsType.md rename to docs/router/api/router/RouteOptionsType.md diff --git a/docs/router/framework/react/api/router/RouteType.md b/docs/router/api/router/RouteType.md similarity index 100% rename from docs/router/framework/react/api/router/RouteType.md rename to docs/router/api/router/RouteType.md diff --git a/docs/router/framework/react/api/router/RouterClass.md b/docs/router/api/router/RouterClass.md similarity index 100% rename from docs/router/framework/react/api/router/RouterClass.md rename to docs/router/api/router/RouterClass.md diff --git a/docs/router/framework/react/api/router/RouterEventsType.md b/docs/router/api/router/RouterEventsType.md similarity index 100% rename from docs/router/framework/react/api/router/RouterEventsType.md rename to docs/router/api/router/RouterEventsType.md diff --git a/docs/router/framework/react/api/router/RouterOptionsType.md b/docs/router/api/router/RouterOptionsType.md similarity index 100% rename from docs/router/framework/react/api/router/RouterOptionsType.md rename to docs/router/api/router/RouterOptionsType.md diff --git a/docs/router/framework/react/api/router/RouterStateType.md b/docs/router/api/router/RouterStateType.md similarity index 100% rename from docs/router/framework/react/api/router/RouterStateType.md rename to docs/router/api/router/RouterStateType.md diff --git a/docs/router/framework/react/api/router/RouterType.md b/docs/router/api/router/RouterType.md similarity index 100% rename from docs/router/framework/react/api/router/RouterType.md rename to docs/router/api/router/RouterType.md diff --git a/docs/router/framework/react/api/router/ToMaskOptionsType.md b/docs/router/api/router/ToMaskOptionsType.md similarity index 100% rename from docs/router/framework/react/api/router/ToMaskOptionsType.md rename to docs/router/api/router/ToMaskOptionsType.md diff --git a/docs/router/framework/react/api/router/ToOptionsType.md b/docs/router/api/router/ToOptionsType.md similarity index 100% rename from docs/router/framework/react/api/router/ToOptionsType.md rename to docs/router/api/router/ToOptionsType.md diff --git a/docs/router/framework/react/api/router/UseMatchRouteOptionsType.md b/docs/router/api/router/UseMatchRouteOptionsType.md similarity index 100% rename from docs/router/framework/react/api/router/UseMatchRouteOptionsType.md rename to docs/router/api/router/UseMatchRouteOptionsType.md diff --git a/docs/router/framework/react/api/router/ViewTransitionOptionsType.md b/docs/router/api/router/ViewTransitionOptionsType.md similarity index 100% rename from docs/router/framework/react/api/router/ViewTransitionOptionsType.md rename to docs/router/api/router/ViewTransitionOptionsType.md diff --git a/docs/router/framework/react/api/router/awaitComponent.md b/docs/router/api/router/awaitComponent.md similarity index 100% rename from docs/router/framework/react/api/router/awaitComponent.md rename to docs/router/api/router/awaitComponent.md diff --git a/docs/router/framework/react/api/router/catchBoundaryComponent.md b/docs/router/api/router/catchBoundaryComponent.md similarity index 100% rename from docs/router/framework/react/api/router/catchBoundaryComponent.md rename to docs/router/api/router/catchBoundaryComponent.md diff --git a/docs/router/framework/react/api/router/catchNotFoundComponent.md b/docs/router/api/router/catchNotFoundComponent.md similarity index 100% rename from docs/router/framework/react/api/router/catchNotFoundComponent.md rename to docs/router/api/router/catchNotFoundComponent.md diff --git a/docs/router/framework/react/api/router/clientOnlyComponent.md b/docs/router/api/router/clientOnlyComponent.md similarity index 100% rename from docs/router/framework/react/api/router/clientOnlyComponent.md rename to docs/router/api/router/clientOnlyComponent.md diff --git a/docs/router/framework/react/api/router/createFileRouteFunction.md b/docs/router/api/router/createFileRouteFunction.md similarity index 100% rename from docs/router/framework/react/api/router/createFileRouteFunction.md rename to docs/router/api/router/createFileRouteFunction.md diff --git a/docs/router/framework/react/api/router/createLazyFileRouteFunction.md b/docs/router/api/router/createLazyFileRouteFunction.md similarity index 100% rename from docs/router/framework/react/api/router/createLazyFileRouteFunction.md rename to docs/router/api/router/createLazyFileRouteFunction.md diff --git a/docs/router/framework/react/api/router/createLazyRouteFunction.md b/docs/router/api/router/createLazyRouteFunction.md similarity index 100% rename from docs/router/framework/react/api/router/createLazyRouteFunction.md rename to docs/router/api/router/createLazyRouteFunction.md diff --git a/docs/router/framework/react/api/router/createRootRouteFunction.md b/docs/router/api/router/createRootRouteFunction.md similarity index 100% rename from docs/router/framework/react/api/router/createRootRouteFunction.md rename to docs/router/api/router/createRootRouteFunction.md diff --git a/docs/router/framework/react/api/router/createRootRouteWithContextFunction.md b/docs/router/api/router/createRootRouteWithContextFunction.md similarity index 100% rename from docs/router/framework/react/api/router/createRootRouteWithContextFunction.md rename to docs/router/api/router/createRootRouteWithContextFunction.md diff --git a/docs/router/framework/react/api/router/createRouteFunction.md b/docs/router/api/router/createRouteFunction.md similarity index 100% rename from docs/router/framework/react/api/router/createRouteFunction.md rename to docs/router/api/router/createRouteFunction.md diff --git a/docs/router/framework/react/api/router/createRouteMaskFunction.md b/docs/router/api/router/createRouteMaskFunction.md similarity index 100% rename from docs/router/framework/react/api/router/createRouteMaskFunction.md rename to docs/router/api/router/createRouteMaskFunction.md diff --git a/docs/router/framework/react/api/router/createRouterFunction.md b/docs/router/api/router/createRouterFunction.md similarity index 100% rename from docs/router/framework/react/api/router/createRouterFunction.md rename to docs/router/api/router/createRouterFunction.md diff --git a/docs/router/framework/react/api/router/defaultGlobalNotFoundComponent.md b/docs/router/api/router/defaultGlobalNotFoundComponent.md similarity index 100% rename from docs/router/framework/react/api/router/defaultGlobalNotFoundComponent.md rename to docs/router/api/router/defaultGlobalNotFoundComponent.md diff --git a/docs/router/framework/react/api/router/deferFunction.md b/docs/router/api/router/deferFunction.md similarity index 100% rename from docs/router/framework/react/api/router/deferFunction.md rename to docs/router/api/router/deferFunction.md diff --git a/docs/router/framework/react/api/router/errorComponentComponent.md b/docs/router/api/router/errorComponentComponent.md similarity index 100% rename from docs/router/framework/react/api/router/errorComponentComponent.md rename to docs/router/api/router/errorComponentComponent.md diff --git a/docs/router/framework/react/api/router/getRouteApiFunction.md b/docs/router/api/router/getRouteApiFunction.md similarity index 100% rename from docs/router/framework/react/api/router/getRouteApiFunction.md rename to docs/router/api/router/getRouteApiFunction.md diff --git a/docs/router/framework/react/api/router/historyStateInterface.md b/docs/router/api/router/historyStateInterface.md similarity index 100% rename from docs/router/framework/react/api/router/historyStateInterface.md rename to docs/router/api/router/historyStateInterface.md diff --git a/docs/router/framework/react/api/router/isNotFoundFunction.md b/docs/router/api/router/isNotFoundFunction.md similarity index 100% rename from docs/router/framework/react/api/router/isNotFoundFunction.md rename to docs/router/api/router/isNotFoundFunction.md diff --git a/docs/router/framework/react/api/router/isRedirectFunction.md b/docs/router/api/router/isRedirectFunction.md similarity index 100% rename from docs/router/framework/react/api/router/isRedirectFunction.md rename to docs/router/api/router/isRedirectFunction.md diff --git a/docs/router/framework/react/api/router/lazyRouteComponentFunction.md b/docs/router/api/router/lazyRouteComponentFunction.md similarity index 100% rename from docs/router/framework/react/api/router/lazyRouteComponentFunction.md rename to docs/router/api/router/lazyRouteComponentFunction.md diff --git a/docs/router/framework/react/api/router/linkComponent.md b/docs/router/api/router/linkComponent.md similarity index 100% rename from docs/router/framework/react/api/router/linkComponent.md rename to docs/router/api/router/linkComponent.md diff --git a/docs/router/framework/react/api/router/linkOptions.md b/docs/router/api/router/linkOptions.md similarity index 100% rename from docs/router/framework/react/api/router/linkOptions.md rename to docs/router/api/router/linkOptions.md diff --git a/docs/router/framework/react/api/router/matchRouteComponent.md b/docs/router/api/router/matchRouteComponent.md similarity index 100% rename from docs/router/framework/react/api/router/matchRouteComponent.md rename to docs/router/api/router/matchRouteComponent.md diff --git a/docs/router/framework/react/api/router/navigateComponent.md b/docs/router/api/router/navigateComponent.md similarity index 100% rename from docs/router/framework/react/api/router/navigateComponent.md rename to docs/router/api/router/navigateComponent.md diff --git a/docs/router/framework/react/api/router/notFoundComponentComponent.md b/docs/router/api/router/notFoundComponentComponent.md similarity index 100% rename from docs/router/framework/react/api/router/notFoundComponentComponent.md rename to docs/router/api/router/notFoundComponentComponent.md diff --git a/docs/router/framework/react/api/router/notFoundFunction.md b/docs/router/api/router/notFoundFunction.md similarity index 100% rename from docs/router/framework/react/api/router/notFoundFunction.md rename to docs/router/api/router/notFoundFunction.md diff --git a/docs/router/framework/react/api/router/outletComponent.md b/docs/router/api/router/outletComponent.md similarity index 100% rename from docs/router/framework/react/api/router/outletComponent.md rename to docs/router/api/router/outletComponent.md diff --git a/docs/router/framework/react/api/router/redirectFunction.md b/docs/router/api/router/redirectFunction.md similarity index 100% rename from docs/router/framework/react/api/router/redirectFunction.md rename to docs/router/api/router/redirectFunction.md diff --git a/docs/router/framework/react/api/router/retainSearchParamsFunction.md b/docs/router/api/router/retainSearchParamsFunction.md similarity index 100% rename from docs/router/framework/react/api/router/retainSearchParamsFunction.md rename to docs/router/api/router/retainSearchParamsFunction.md diff --git a/docs/router/framework/react/api/router/rootRouteWithContextFunction.md b/docs/router/api/router/rootRouteWithContextFunction.md similarity index 100% rename from docs/router/framework/react/api/router/rootRouteWithContextFunction.md rename to docs/router/api/router/rootRouteWithContextFunction.md diff --git a/docs/router/framework/react/api/router/stripSearchParamsFunction.md b/docs/router/api/router/stripSearchParamsFunction.md similarity index 100% rename from docs/router/framework/react/api/router/stripSearchParamsFunction.md rename to docs/router/api/router/stripSearchParamsFunction.md diff --git a/docs/router/framework/react/api/router/useAwaitedHook.md b/docs/router/api/router/useAwaitedHook.md similarity index 100% rename from docs/router/framework/react/api/router/useAwaitedHook.md rename to docs/router/api/router/useAwaitedHook.md diff --git a/docs/router/framework/react/api/router/useBlockerHook.md b/docs/router/api/router/useBlockerHook.md similarity index 100% rename from docs/router/framework/react/api/router/useBlockerHook.md rename to docs/router/api/router/useBlockerHook.md diff --git a/docs/router/framework/react/api/router/useCanGoBack.md b/docs/router/api/router/useCanGoBack.md similarity index 100% rename from docs/router/framework/react/api/router/useCanGoBack.md rename to docs/router/api/router/useCanGoBack.md diff --git a/docs/router/framework/react/api/router/useChildMatchesHook.md b/docs/router/api/router/useChildMatchesHook.md similarity index 100% rename from docs/router/framework/react/api/router/useChildMatchesHook.md rename to docs/router/api/router/useChildMatchesHook.md diff --git a/docs/router/framework/react/api/router/useLinkPropsHook.md b/docs/router/api/router/useLinkPropsHook.md similarity index 100% rename from docs/router/framework/react/api/router/useLinkPropsHook.md rename to docs/router/api/router/useLinkPropsHook.md diff --git a/docs/router/framework/react/api/router/useLoaderDataHook.md b/docs/router/api/router/useLoaderDataHook.md similarity index 100% rename from docs/router/framework/react/api/router/useLoaderDataHook.md rename to docs/router/api/router/useLoaderDataHook.md diff --git a/docs/router/framework/react/api/router/useLoaderDepsHook.md b/docs/router/api/router/useLoaderDepsHook.md similarity index 100% rename from docs/router/framework/react/api/router/useLoaderDepsHook.md rename to docs/router/api/router/useLoaderDepsHook.md diff --git a/docs/router/framework/react/api/router/useLocationHook.md b/docs/router/api/router/useLocationHook.md similarity index 100% rename from docs/router/framework/react/api/router/useLocationHook.md rename to docs/router/api/router/useLocationHook.md diff --git a/docs/router/framework/react/api/router/useMatchHook.md b/docs/router/api/router/useMatchHook.md similarity index 100% rename from docs/router/framework/react/api/router/useMatchHook.md rename to docs/router/api/router/useMatchHook.md diff --git a/docs/router/framework/react/api/router/useMatchRouteHook.md b/docs/router/api/router/useMatchRouteHook.md similarity index 100% rename from docs/router/framework/react/api/router/useMatchRouteHook.md rename to docs/router/api/router/useMatchRouteHook.md diff --git a/docs/router/framework/react/api/router/useMatchesHook.md b/docs/router/api/router/useMatchesHook.md similarity index 100% rename from docs/router/framework/react/api/router/useMatchesHook.md rename to docs/router/api/router/useMatchesHook.md diff --git a/docs/router/framework/react/api/router/useNavigateHook.md b/docs/router/api/router/useNavigateHook.md similarity index 100% rename from docs/router/framework/react/api/router/useNavigateHook.md rename to docs/router/api/router/useNavigateHook.md diff --git a/docs/router/framework/react/api/router/useParamsHook.md b/docs/router/api/router/useParamsHook.md similarity index 100% rename from docs/router/framework/react/api/router/useParamsHook.md rename to docs/router/api/router/useParamsHook.md diff --git a/docs/router/framework/react/api/router/useParentMatchesHook.md b/docs/router/api/router/useParentMatchesHook.md similarity index 100% rename from docs/router/framework/react/api/router/useParentMatchesHook.md rename to docs/router/api/router/useParentMatchesHook.md diff --git a/docs/router/framework/react/api/router/useRouteContextHook.md b/docs/router/api/router/useRouteContextHook.md similarity index 100% rename from docs/router/framework/react/api/router/useRouteContextHook.md rename to docs/router/api/router/useRouteContextHook.md diff --git a/docs/router/framework/react/api/router/useRouterHook.md b/docs/router/api/router/useRouterHook.md similarity index 100% rename from docs/router/framework/react/api/router/useRouterHook.md rename to docs/router/api/router/useRouterHook.md diff --git a/docs/router/framework/react/api/router/useRouterStateHook.md b/docs/router/api/router/useRouterStateHook.md similarity index 100% rename from docs/router/framework/react/api/router/useRouterStateHook.md rename to docs/router/api/router/useRouterStateHook.md diff --git a/docs/router/framework/react/api/router/useSearchHook.md b/docs/router/api/router/useSearchHook.md similarity index 100% rename from docs/router/framework/react/api/router/useSearchHook.md rename to docs/router/api/router/useSearchHook.md diff --git a/docs/router/framework/react/comparison.md b/docs/router/comparison.md similarity index 97% rename from docs/router/framework/react/comparison.md rename to docs/router/comparison.md index a6b61e59f0c..84a21191626 100644 --- a/docs/router/framework/react/comparison.md +++ b/docs/router/comparison.md @@ -1,5 +1,5 @@ --- -title: Comparison | TanStack Router & TanStack Start vs Next.js vs React Router / Remix +title: Comparison toc: false --- @@ -17,6 +17,12 @@ Feature/Capability Key: - πŸ”Ά Possible, but requires custom code/implementation/casting - πŸ›‘ Not officially supported + + +# React + +## Comparison | TanStack Router & TanStack Start vs Next.js vs React Router / Remix + | | 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] | @@ -94,3 +100,11 @@ Feature/Capability Key: [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 + +# Solid + +--- + +We don't have a comparison table for Solid just yet. If you're interested in helping us create one, please reach out in the [TanStack Discord](https://tanstack.com/discord) or open a PR with your proposed comparison! + + diff --git a/docs/router/config.json b/docs/router/config.json index 808c3fe38f9..2957e040356 100644 --- a/docs/router/config.json +++ b/docs/router/config.json @@ -8,103 +8,79 @@ "sections": [ { "label": "Getting Started", - "children": [], + "children": [ + { + "label": "Overview", + "to": "overview" + }, + { + "label": "Quick Start", + "to": "quick-start" + }, + { + "label": "Devtools", + "to": "devtools" + }, + { + "label": "Decisions on DX", + "to": "decisions-on-dx" + }, + { + "label": "LLM Assistance Support", + "to": "llm-support" + }, + { + "label": "Comparison", + "to": "comparison" + }, + { + "label": "Frequently Asked Questions", + "to": "faq" + } + ], "frameworks": [ { "label": "react", - "children": [ - { - "label": "Overview", - "to": "framework/react/overview" - }, - { - "label": "Quick Start", - "to": "framework/react/quick-start" - }, - { - "label": "Devtools", - "to": "framework/react/devtools" - }, - { - "label": "Decisions on DX", - "to": "framework/react/decisions-on-dx" - }, - { - "label": "LLM Assistance Support", - "to": "framework/react/llm-support" - }, - { - "label": "Comparison", - "to": "framework/react/comparison" - }, - { - "label": "Frequently Asked Questions", - "to": "framework/react/faq" - } - ] + "children": [] }, { "label": "solid", - "children": [ - { - "label": "Overview", - "to": "framework/solid/overview" - }, - { - "label": "Quick Start", - "to": "framework/solid/quick-start" - }, - { - "label": "Devtools", - "to": "framework/solid/devtools" - }, - { - "label": "Decisions on DX", - "to": "framework/solid/decisions-on-dx" - }, - { - "label": "LLM Assistance Support", - "to": "framework/solid/llm-support" - }, - { - "label": "Frequently Asked Questions", - "to": "framework/solid/faq" - } - ] + "children": [] } ] }, { "label": "Installation Guides", - "children": [], + "children": [ + { + "label": "Manual Setup", + "to": "installation/manual" + }, + { + "label": "Vite", + "to": "installation/with-vite" + }, + { + "label": "Rspack/Rsbuild", + "to": "installation/with-rspack" + }, + { + "label": "Webpack", + "to": "installation/with-webpack" + }, + { + "label": "Esbuild", + "to": "installation/with-esbuild" + }, + { + "label": "Router CLI", + "to": "installation/with-router-cli" + } + ], "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" @@ -117,334 +93,187 @@ }, { "label": "solid", - "children": [ - { - "label": "Manual Setup", - "to": "framework/solid/installation/manual" - }, - { - "label": "Vite", - "to": "framework/solid/installation/with-vite" - }, - { - "label": "Router CLI", - "to": "framework/solid/installation/with-router-cli" - } - ] + "children": [] } ] }, { "label": "Routing", - "children": [], + "children": [ + { + "label": "Routing Concepts", + "to": "routing/routing-concepts" + }, + { + "label": "Route Trees", + "to": "routing/route-trees" + }, + { + "label": "Route Matching", + "to": "routing/route-matching" + }, + { + "label": "File-Based Routing", + "to": "routing/file-based-routing" + }, + { + "label": "Virtual File Routes", + "to": "routing/virtual-file-routes" + }, + { + "label": "Code-Based Routing", + "to": "routing/code-based-routing" + }, + { + "label": "File Naming Conventions", + "to": "routing/file-naming-conventions" + } + ], "frameworks": [ { "label": "react", - "children": [ - { - "label": "Routing Concepts", - "to": "framework/react/routing/routing-concepts" - }, - { - "label": "Route Trees", - "to": "framework/react/routing/route-trees" - }, - { - "label": "Route Matching", - "to": "framework/react/routing/route-matching" - }, - { - "label": "File-Based Routing", - "to": "framework/react/routing/file-based-routing" - }, - { - "label": "Virtual File Routes", - "to": "framework/react/routing/virtual-file-routes" - }, - { - "label": "Code-Based Routing", - "to": "framework/react/routing/code-based-routing" - }, - { - "label": "File Naming Conventions", - "to": "framework/react/routing/file-naming-conventions" - } - ] + "children": [] }, { "label": "solid", - "children": [ - { - "label": "Routing Concepts", - "to": "framework/solid/routing/routing-concepts" - }, - { - "label": "Route Trees", - "to": "framework/solid/routing/route-trees" - }, - { - "label": "Route Matching", - "to": "framework/solid/routing/route-matching" - }, - { - "label": "File-Based Routing", - "to": "framework/solid/routing/file-based-routing" - }, - { - "label": "Virtual File Routes", - "to": "framework/solid/routing/virtual-file-routes" - }, - { - "label": "Code-Based Routing", - "to": "framework/solid/routing/code-based-routing" - }, - { - "label": "File Naming Conventions", - "to": "framework/solid/routing/file-naming-conventions" - } - ] + "children": [] } ] }, { "label": "Guides", - "children": [], + "children": [ + { + "label": "Code Splitting", + "to": "guide/code-splitting" + }, + { + "label": "Automatic Code Splitting", + "to": "guide/automatic-code-splitting" + }, + { + "label": "Creating a Router", + "to": "guide/creating-a-router" + }, + { + "label": "Outlets", + "to": "guide/outlets" + }, + { + "label": "Navigation", + "to": "guide/navigation" + }, + { + "label": "Path Params", + "to": "guide/path-params" + }, + { + "label": "Search Params", + "to": "guide/search-params" + }, + { + "label": "Link Options", + "to": "guide/link-options" + }, + { + "label": "Custom Links", + "to": "guide/custom-link" + }, + { + "label": "Data Loading", + "to": "guide/data-loading" + }, + { + "label": "Deferred Data Loading", + "to": "guide/deferred-data-loading" + }, + { + "label": "External Data Loading", + "to": "guide/external-data-loading" + }, + { + "label": "Data Mutations", + "to": "guide/data-mutations" + }, + { + "label": "Type Safety", + "to": "guide/type-safety" + }, + { + "label": "Type Utilities", + "to": "guide/type-utilities" + }, + { + "label": "Preloading", + "to": "guide/preloading" + }, + { + "label": "Document Head Management", + "to": "guide/document-head-management" + }, + { + "label": "Route Masking", + "to": "guide/route-masking" + }, + { + "label": "Navigation Blocking", + "to": "guide/navigation-blocking" + }, + { + "label": "Custom Search Param Serialization", + "to": "guide/custom-search-param-serialization" + }, + { + "label": "History Types", + "to": "guide/history-types" + }, + { + "label": "Router Context", + "to": "guide/router-context" + }, + { + "label": "Not Found Errors", + "to": "guide/not-found-errors" + }, + { + "label": "Authenticated Routes", + "to": "guide/authenticated-routes" + }, + { + "label": "Scroll Restoration", + "to": "guide/scroll-restoration" + }, + { + "label": "Static Route Data", + "to": "guide/static-route-data" + }, + { + "label": "SSR", + "to": "guide/ssr" + }, + { + "label": "Render Optimizations", + "to": "guide/render-optimizations" + } + ], "frameworks": [ { "label": "react", - "children": [ - { - "label": "Code Splitting", - "to": "framework/react/guide/code-splitting" - }, - { - "label": "Automatic Code Splitting", - "to": "framework/react/guide/automatic-code-splitting" - }, - { - "label": "Creating a Router", - "to": "framework/react/guide/creating-a-router" - }, - { - "label": "Outlets", - "to": "framework/react/guide/outlets" - }, - { - "label": "Navigation", - "to": "framework/react/guide/navigation" - }, - { - "label": "Path Params", - "to": "framework/react/guide/path-params" - }, - { - "label": "Search Params", - "to": "framework/react/guide/search-params" - }, - { - "label": "Link Options", - "to": "framework/react/guide/link-options" - }, - { - "label": "Custom Links", - "to": "framework/react/guide/custom-link" - }, - { - "label": "Data Loading", - "to": "framework/react/guide/data-loading" - }, - { - "label": "Deferred Data Loading", - "to": "framework/react/guide/deferred-data-loading" - }, - { - "label": "External Data Loading", - "to": "framework/react/guide/external-data-loading" - }, - { - "label": "Data Mutations", - "to": "framework/react/guide/data-mutations" - }, - { - "label": "Type Safety", - "to": "framework/react/guide/type-safety" - }, - { - "label": "Type Utilities", - "to": "framework/react/guide/type-utilities" - }, - { - "label": "Preloading", - "to": "framework/react/guide/preloading" - }, - { - "label": "Document Head Management", - "to": "framework/react/guide/document-head-management" - }, - { - "label": "Route Masking", - "to": "framework/react/guide/route-masking" - }, - { - "label": "Navigation Blocking", - "to": "framework/react/guide/navigation-blocking" - }, - { - "label": "Custom Search Param Serialization", - "to": "framework/react/guide/custom-search-param-serialization" - }, - { - "label": "History Types", - "to": "framework/react/guide/history-types" - }, - { - "label": "Router Context", - "to": "framework/react/guide/router-context" - }, - { - "label": "Not Found Errors", - "to": "framework/react/guide/not-found-errors" - }, - { - "label": "Authenticated Routes", - "to": "framework/react/guide/authenticated-routes" - }, - { - "label": "Scroll Restoration", - "to": "framework/react/guide/scroll-restoration" - }, - { - "label": "Static Route Data", - "to": "framework/react/guide/static-route-data" - }, - { - "label": "SSR", - "to": "framework/react/guide/ssr" - }, - { - "label": "Render Optimizations", - "to": "framework/react/guide/render-optimizations" - } - ] + "children": [] }, { "label": "solid", - "children": [ - { - "label": "Code Splitting", - "to": "framework/solid/guide/code-splitting" - }, - { - "label": "Automatic Code Splitting", - "to": "framework/solid/guide/automatic-code-splitting" - }, - { - "label": "Creating a Router", - "to": "framework/solid/guide/creating-a-router" - }, - { - "label": "Outlets", - "to": "framework/solid/guide/outlets" - }, - { - "label": "Navigation", - "to": "framework/solid/guide/navigation" - }, - { - "label": "Path Params", - "to": "framework/solid/guide/path-params" - }, - { - "label": "Search Params", - "to": "framework/solid/guide/search-params" - }, - { - "label": "Link Options", - "to": "framework/solid/guide/link-options" - }, - { - "label": "Custom Links", - "to": "framework/solid/guide/custom-link" - }, - { - "label": "Data Loading", - "to": "framework/solid/guide/data-loading" - }, - { - "label": "Deferred Data Loading", - "to": "framework/solid/guide/deferred-data-loading" - }, - { - "label": "External Data Loading", - "to": "framework/solid/guide/external-data-loading" - }, - { - "label": "Data Mutations", - "to": "framework/solid/guide/data-mutations" - }, - { - "label": "Type Safety", - "to": "framework/solid/guide/type-safety" - }, - { - "label": "Type Utilities", - "to": "framework/solid/guide/type-utilities" - }, - { - "label": "Preloading", - "to": "framework/solid/guide/preloading" - }, - { - "label": "Document Head Management", - "to": "framework/solid/guide/document-head-management" - }, - { - "label": "Route Masking", - "to": "framework/solid/guide/route-masking" - }, - { - "label": "Navigation Blocking", - "to": "framework/solid/guide/navigation-blocking" - }, - { - "label": "Custom Search Param Serialization", - "to": "framework/solid/guide/custom-search-param-serialization" - }, - { - "label": "History Types", - "to": "framework/solid/guide/history-types" - }, - { - "label": "Router Context", - "to": "framework/solid/guide/router-context" - }, - { - "label": "Not Found Errors", - "to": "framework/solid/guide/not-found-errors" - }, - { - "label": "Authenticated Routes", - "to": "framework/solid/guide/authenticated-routes" - }, - { - "label": "Scroll Restoration", - "to": "framework/solid/guide/scroll-restoration" - }, - { - "label": "Static Route Data", - "to": "framework/solid/guide/static-route-data" - }, - { - "label": "SSR", - "to": "framework/solid/guide/ssr" - } - ] + "children": [] } ] }, { "label": "API", "children": [ + { + "label": "Router", + "to": "api/router" + }, { "label": "File-Based Routing", "to": "api/file-based-routing" @@ -453,12 +282,7 @@ "frameworks": [ { "label": "react", - "children": [ - { - "label": "Router", - "to": "framework/react/api/router" - } - ] + "children": [] }, { "label": "solid", @@ -468,16 +292,16 @@ }, { "label": "Integrations", - "children": [], + "children": [ + { + "label": "TanStack Query", + "to": "integrations/query" + } + ], "frameworks": [ { "label": "react", - "children": [ - { - "label": "TanStack Query", - "to": "integrations/query" - } - ] + "children": [] }, { "label": "solid", diff --git a/docs/router/framework/react/decisions-on-dx.md b/docs/router/decisions-on-dx.md similarity index 96% rename from docs/router/framework/react/decisions-on-dx.md rename to docs/router/decisions-on-dx.md index cdc82ec3bbf..26c366a9da7 100644 --- a/docs/router/framework/react/decisions-on-dx.md +++ b/docs/router/decisions-on-dx.md @@ -14,9 +14,12 @@ And they are all valid questions. For the most part, people are used to using ro But TanStack Router is different. It's not your average routing library. It's not your average state management library. It's not your average anything. +> [!TIP] +> The examples in this guide use React for components and code snippets, but the same principles apply to Solid. The only difference is in the syntax and API of the framework, but the underlying concepts and design decisions are the same. + ## TanStack Router's origin story -It's important to remember that TanStack Router's origins stem from [Nozzle.io](https://nozzle.io)'s need for a client-side routing solution that offered a first-in-class _URL Search Parameters_ experience without compromising on the **_type-safety_** that was required to power its complex dashboards. +It's important to remember that TanStack Router's origins stem from [Nozzle.io](https://nozzle.io?utm_source=tanstack)'s need for a client-side routing solution that offered a first-in-class _URL Search Parameters_ experience without compromising on the **_type-safety_** that was required to power its complex dashboards. And so, from TanStack Router's very inception, every facet of its design was meticulously thought out to ensure that its type-safety and developer experience were second to none. diff --git a/docs/router/framework/react/devtools.md b/docs/router/devtools.md similarity index 65% rename from docs/router/framework/react/devtools.md rename to docs/router/devtools.md index a3992cfd605..1ad33fb7a66 100644 --- a/docs/router/framework/react/devtools.md +++ b/docs/router/devtools.md @@ -12,48 +12,66 @@ When you begin your TanStack Router journey, you'll want these devtools by your The devtools are a separate package that you need to install: -```sh -npm install @tanstack/react-router-devtools -``` + -or +react: @tanstack/react-router-devtools +solid: @tanstack/solid-router-devtools -```sh -pnpm add @tanstack/react-router-devtools -``` + -or +## Import the Devtools -```sh -yarn add @tanstack/react-router-devtools -``` + -or +# React -```sh -bun add @tanstack/react-router-devtools +```tsx +import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' ``` -## Import the Devtools +# Solid -```js -import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' +```tsx +import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools' ``` + + ## Using Devtools in production The Devtools, if imported as `TanStackRouterDevtools` will not be shown in production. If you want to have devtools in an environment with `process.env.NODE_ENV === 'production'`, use instead `TanStackRouterDevtoolsInProd`, which has all the same options: + + +# React + ```tsx import { TanStackRouterDevtoolsInProd } from '@tanstack/react-router-devtools' ``` -## Using inside of the `RouterProvider` +# Solid + +```tsx +import { TanStackRouterDevtoolsInProd } from '@tanstack/solid-router-devtools' +``` + + + +## Using the Devtools in the root route The easiest way for the devtools to work is to render them inside of your root route (or any other route). This will automatically connect the devtools to the router instance. -```tsx -const rootRoute = createRootRoute({ + + +# React + + + +```tsx title="src/routes/__root.tsx" +import { createRootRoute, Outlet } from '@tanstack/react-router' +import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' + +export const Route = createRootRoute({ component: () => ( <> @@ -61,24 +79,40 @@ const rootRoute = createRootRoute({ ), }) +``` -const routeTree = rootRoute.addChildren([ - // ... other routes -]) + -const router = createRouter({ - routeTree, -}) +# Solid -function App() { - return -} + + +```tsx title="src/routes/__root.tsx" +import { createRootRoute, Outlet } from '@tanstack/solid-router' +import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools' + +export const Route = createRootRoute({ + component: () => ( + <> + + + + ), +}) ``` + + + + ## Manually passing the Router Instance If rendering the devtools inside of the `RouterProvider` isn't your cup of tea, a `router` prop for the devtools accepts the same `router` instance you pass to the `Router` component. This makes it possible to place the devtools anywhere on the page, not just inside the provider: + + +# React + ```tsx function App() { return ( @@ -90,25 +124,57 @@ function App() { } ``` +# Solid + +```tsx +function App() { + return ( + <> + + + + ) +} +``` + + + ## Floating Mode Floating Mode will mount the devtools as a fixed, floating element in your app and provide a toggle in the corner of the screen to show and hide the devtools. This toggle state will be stored and remembered in localStorage across reloads. -Place the following code as high in your React app as you can. The closer it is to the root of the page, the better it will work! +Place the following code as high in your app as you can. The closer it is to the root of the page, the better it will work! -```js -import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' + + +# React + +```tsx +function App() { + return ( + <> + + + + ) +} +``` + +# Solid +```tsx function App() { return ( <> - + ) } ``` + + ### Devtools Options - `router: Router` @@ -136,32 +202,63 @@ function App() { To control the position of the devtools, import the `TanStackRouterDevtoolsPanel`: -```js + + +# React + +```tsx import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools' ``` +# Solid + +```tsx +import { TanStackRouterDevtoolsPanel } from '@tanstack/solid-router-devtools' +``` + + + It can then be attached to provided shadow DOM target: -```js + + +# React + +```tsx + +``` + +# Solid + +```tsx ``` + + Click [here](https://tanstack.com/router/latest/docs/framework/react/examples/basic-devtools-panel) to see a live example of this in StackBlitz. ## Embedded Mode Embedded Mode will embed the devtools as a regular component in your application. You can style it however you'd like after that! -```js + + +# React + +```tsx import { TanStackRouterDevtoolsPanel } from '@tanstack/react-router-devtools' function App() { return ( <> - + + + + + ) +} +``` + + + ### DevtoolsPanel Options - `router: Router` - The router instance to connect to. -- `style: StyleObject` + + + +# React + +- `style?: StyleObject` - The standard React style object used to style a component with inline styles. -- `className: string` +- `className?: string` - The standard React className property used to style a component with classes. + +# Solid + +- `style?: StyleObject` + - The standard Solid style object used to style a component with inline styles. +- `class?: string` + - The standard Solid class property used to style a component with classes. + + + - `isOpen?: boolean` - A boolean variable indicating whether the panel is open or closed. - `setIsOpen?: (isOpen: boolean) => void` diff --git a/docs/router/framework/react/faq.md b/docs/router/faq.md similarity index 86% rename from docs/router/framework/react/faq.md rename to docs/router/faq.md index a7c0887298a..3d51140a0a1 100644 --- a/docs/router/framework/react/faq.md +++ b/docs/router/faq.md @@ -27,7 +27,6 @@ You should commit this file into git so that other developers can use it to buil ## Can I conditionally render the Root Route component? No, the root route is always rendered as it is the entry point of your application. - If you need to conditionally render a route's component, this usually means that the page content needs to be different based on some condition (e.g. user authentication). For this use case, you should use a [Layout Route](./routing/routing-concepts.md#layout-routes) or a [Pathless Layout Route](./routing/routing-concepts.md#pathless-layout-routes) to conditionally render the content. You can restrict access to these routes using a conditional check in the `beforeLoad` function of the route. @@ -35,6 +34,10 @@ You can restrict access to these routes using a conditional check in the `before
What does this look like? + + +# React + ```tsx // src/routes/_pathless-layout.tsx import { createFileRoute, Outlet } from '@tanstack/react-router' @@ -63,4 +66,36 @@ function PathlessLayoutRouteComponent() { } ``` +# Solid + +```tsx +// src/routes/_pathless-layout.tsx +import { createFileRoute, Outlet } from '@tanstack/solid-router' +import { isAuthenticated } from '../utils/auth' + +export const Route = createFileRoute('/_pathless-layout', { + beforeLoad: async () => { + // Check if the user is authenticated + const authed = await isAuthenticated() + if (!authed) { + // Redirect the user to the login page + return '/login' + } + }, + component: PathlessLayoutRouteComponent, + // ... +}) + +function PathlessLayoutRouteComponent() { + return ( +
+

You are authed

+ +
+ ) +} +``` + + +
diff --git a/docs/router/framework/react/installation/migrate-from-react-location.md b/docs/router/framework/react/installation/migrate-from-react-location.md index dada1bf9d64..788b6c71a78 100644 --- a/docs/router/framework/react/installation/migrate-from-react-location.md +++ b/docs/router/framework/react/installation/migrate-from-react-location.md @@ -2,27 +2,27 @@ title: Migration from React Location --- -Before you begin your journey in migrating from React Location, it's important that you have a good understanding of the [Routing Concepts](../routing/routing-concepts.md) and [Design Decisions](../decisions-on-dx.md) used by TanStack Router. +Before you begin your journey in migrating from React Location, it's important that you have a good understanding of the [Routing Concepts](../../../routing/routing-concepts.md) and [Design Decisions](../../../decisions-on-dx.md) used by TanStack Router. ## Differences between React Location and TanStack Router React Location and TanStack Router share much of same design decisions concepts, but there are some key differences that you should be aware of. - React Location uses _generics_ to infer types for routes, while TanStack Router uses _module declaration merging_ to infer types. -- Route configuration in React Location is done using a single array of route definitions, while in TanStack Router, route configuration is done using a tree of route definitions starting with the [root route](../routing/routing-concepts.md#the-root-route). -- [File-based routing](../routing/file-based-routing.md) is the recommended way to define routes in TanStack Router, while React Location only allows you to define routes in a single file using a code-based approach. - - TanStack Router does support a [code-based approach](../routing/code-based-routing.md) to defining routes, but it is not recommended for most use cases. You can read more about why, over here: [why is file-based routing the preferred way to define routes?](../decisions-on-dx.md#why-is-file-based-routing-the-preferred-way-to-define-routes) +- Route configuration in React Location is done using a single array of route definitions, while in TanStack Router, route configuration is done using a tree of route definitions starting with the [root route](../../../routing/routing-concepts.md#the-root-route). +- [File-based routing](../../../routing/file-based-routing.md) is the recommended way to define routes in TanStack Router, while React Location only allows you to define routes in a single file using a code-based approach. + - TanStack Router does support a [code-based approach](../../../routing/code-based-routing.md) to defining routes, but it is not recommended for most use cases. You can read more about why, over here: [why is file-based routing the preferred way to define routes?](../../../decisions-on-dx.md#why-is-file-based-routing-the-preferred-way-to-define-routes) ## Migration guide In this guide we'll go over the process of migrating the [React Location Basic example](https://github.com/TanStack/router/tree/react-location/examples/basic) over to TanStack Router using file-based routing, with the end goal of having the same functionality as the original example (styling and other non-routing related code will be omitted). > [!TIP] -> To use a code-based approach for defining your routes, you can read the [code-based Routing](../routing/code-based-routing.md) guide. +> To use a code-based approach for defining your routes, you can read the [code-based Routing](../../../routing/code-based-routing.md) guide. ### Step 1: Swap over to TanStack Router's dependencies -First, we need to install the dependencies for TanStack Router. For detailed installation instructions, see our [How to Install TanStack Router](../how-to/install.md) guide. +First, we need to install the dependencies for TanStack Router. For detailed installation instructions, see our [How to Install TanStack Router](../../../how-to/install.md) guide. ```sh npm install @tanstack/react-router @tanstack/router-devtools @@ -57,7 +57,7 @@ export default defineConfig({ }) ``` -However, if your application does not use Vite, you use one of our other [supported bundlers](../routing/file-based-routing.md#getting-started-with-file-based-routing), or you can use the `@tanstack/router-cli` package to watch for changes in your routes files and automatically update the routes configuration. +However, if your application does not use Vite, you use one of our other [supported bundlers](../../../routing/file-based-routing.md#getting-started-with-file-based-routing), or you can use the `@tanstack/router-cli` package to watch for changes in your routes files and automatically update the routes configuration. ### Step 3: Add the file-based configuration file to your project @@ -70,7 +70,7 @@ Create a `tsr.config.json` file in the root of your project with the following c } ``` -You can find the full list of options for the `tsr.config.json` file [here](../routing/file-based-routing.md). +You can find the full list of options for the `tsr.config.json` file [here](../../../routing/file-based-routing.md). ### Step 4: Create the routes directory @@ -249,19 +249,19 @@ You should now have successfully migrated your application from React Location t React Location also has a few more features that you might be using in your application. Here are some guides to help you migrate those features: -- [Search params](../guide/search-params.md) -- [Data loading](../guide/data-loading.md) -- [History types](../guide/history-types.md) -- [Wildcard / Splat / Catch-all routes](../routing/routing-concepts.md#splat--catch-all-routes) -- [Authenticated routes](../guide/authenticated-routes.md) +- [Search params](../../../guide/search-params.md) +- [Data loading](../../../guide/data-loading.md) +- [History types](../../../guide/history-types.md) +- [Wildcard / Splat / Catch-all routes](../../../routing/routing-concepts.md#splat--catch-all-routes) +- [Authenticated routes](../../../guide/authenticated-routes.md) TanStack Router also has a few more features that you might want to explore: -- [Router Context](../guide/router-context.md) -- [Preloading](../guide/preloading.md) -- [Pathless Layout Routes](../routing/routing-concepts.md#pathless-layout-routes) -- [Route masking](../guide/route-masking.md) -- [SSR](../guide/ssr.md) +- [Router Context](../../../guide/router-context.md) +- [Preloading](../../../guide/preloading.md) +- [Pathless Layout Routes](../../../routing/routing-concepts.md#pathless-layout-routes) +- [Route masking](../../../guide/route-masking.md) +- [SSR](../../../guide/ssr.md) - ... and more! If you are facing any issues or have any questions, feel free to ask for help in the TanStack Discord. diff --git a/docs/router/framework/react/installation/migrate-from-react-router.md b/docs/router/framework/react/installation/migrate-from-react-router.md index 6903673bf73..9e6cb618ae8 100644 --- a/docs/router/framework/react/installation/migrate-from-react-router.md +++ b/docs/router/framework/react/installation/migrate-from-react-router.md @@ -7,7 +7,7 @@ toc: false Here is the [example repo](https://github.com/Benanna2019/SickFitsForEveryone/tree/migrate-to-tanstack/router/React-Router) -- [ ] Install Router - `npm i @tanstack/react-router` (see [detailed installation guide](../how-to/install.md)) +- [ ] Install Router - `npm i @tanstack/react-router` (see [detailed installation guide](../../../how-to/install.md)) - [ ] **Optional:** Uninstall React Router to get TypeScript errors on imports. - At this point I don’t know if you can do a gradual migration, but it seems likely you could have multiple router providers, not desirable. - The api’s between React Router and TanStack Router are very similar and could most likely be handled in a sprint cycle or two if that is your companies way of doing things. diff --git a/docs/router/framework/react/installation/with-esbuild.md b/docs/router/framework/react/installation/with-esbuild.md deleted file mode 100644 index 63219f73a2a..00000000000 --- a/docs/router/framework/react/installation/with-esbuild.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -title: Installation with Esbuild ---- - -[//]: # 'BundlerConfiguration' - -To use file-based routing with **Esbuild**, you'll need to install the `@tanstack/router-plugin` package. - -```sh -npm install -D @tanstack/router-plugin -``` - -Once installed, you'll need to add the plugin to your configuration. - -```tsx -// esbuild.config.js -import { tanstackRouter } from '@tanstack/router-plugin/esbuild' - -export default { - // ... - plugins: [ - tanstackRouter({ - target: 'react', - autoCodeSplitting: true, - }), - ], -} -``` - -Or, you can clone our [Quickstart Esbuild example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-esbuild-file-based) and get started. - -Now that you've added the plugin to your Esbuild configuration, you're all set to start using file-based routing with TanStack Router. - -[//]: # 'BundlerConfiguration' - -## Ignoring the generated route tree file - -If your project is configured to use a linter and/or formatter, you may want to ignore the generated route tree file. This file is managed by TanStack Router and therefore shouldn't be changed by your linter or formatter. - -Here are some resources to help you ignore the generated route tree file: - -- Prettier - [https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore](https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore) -- ESLint - [https://eslint.org/docs/latest/use/configure/ignore#ignoring-files](https://eslint.org/docs/latest/use/configure/ignore#ignoring-files) -- Biome - [https://biomejs.dev/reference/configuration/#filesignore](https://biomejs.dev/reference/configuration/#filesignore) - -> [!WARNING] -> If you are using VSCode, you may experience the route tree file unexpectedly open (with errors) after renaming a route. - -You can prevent that from the VSCode settings by marking the file as readonly. Our recommendation is to also exclude it from search results and file watcher with the following settings: - -```json -{ - "files.readonlyInclude": { - "**/routeTree.gen.ts": true - }, - "files.watcherExclude": { - "**/routeTree.gen.ts": true - }, - "search.exclude": { - "**/routeTree.gen.ts": true - } -} -``` - -You can use those settings either at a user level or only for a single workspace by creating the file `.vscode/settings.json` at the root of your project. - -## Configuration - -When using the TanStack Router Plugin with Esbuild for File-based routing, it comes with some sane defaults that should work for most projects: - -```json -{ - "routesDirectory": "./src/routes", - "generatedRouteTree": "./src/routeTree.gen.ts", - "routeFileIgnorePrefix": "-", - "quoteStyle": "single" -} -``` - -If these defaults work for your project, you don't need to configure anything at all! However, if you need to customize the configuration, you can do so by editing the configuration object passed into the `tanstackRouter` function. - -You can find all the available configuration options in the [File-based Routing API Reference](../../../api/file-based-routing.md). diff --git a/docs/router/framework/react/installation/with-rspack.md b/docs/router/framework/react/installation/with-rspack.md deleted file mode 100644 index 1a0b0afcd7f..00000000000 --- a/docs/router/framework/react/installation/with-rspack.md +++ /dev/null @@ -1,88 +0,0 @@ ---- -title: Installation with Rspack ---- - -[//]: # 'BundlerConfiguration' - -To use file-based routing with **Rspack** or **Rsbuild**, you'll need to install the `@tanstack/router-plugin` package. - -```sh -npm install -D @tanstack/router-plugin -``` - -Once installed, you'll need to add the plugin to your configuration. - -```tsx -// rsbuild.config.ts -import { defineConfig } from '@rsbuild/core' -import { pluginReact } from '@rsbuild/plugin-react' -import { tanstackRouter } from '@tanstack/router-plugin/rspack' - -export default defineConfig({ - plugins: [pluginReact()], - tools: { - rspack: { - plugins: [ - tanstackRouter({ - target: 'react', - autoCodeSplitting: true, - }), - ], - }, - }, -}) -``` - -Or, you can clone our [Quickstart Rspack/Rsbuild example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-rspack-file-based) and get started. - -Now that you've added the plugin to your Rspack/Rsbuild configuration, you're all set to start using file-based routing with TanStack Router. - -[//]: # 'BundlerConfiguration' - -## Ignoring the generated route tree file - -If your project is configured to use a linter and/or formatter, you may want to ignore the generated route tree file. This file is managed by TanStack Router and therefore shouldn't be changed by your linter or formatter. - -Here are some resources to help you ignore the generated route tree file: - -- Prettier - [https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore](https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore) -- ESLint - [https://eslint.org/docs/latest/use/configure/ignore#ignoring-files](https://eslint.org/docs/latest/use/configure/ignore#ignoring-files) -- Biome - [https://biomejs.dev/reference/configuration/#filesignore](https://biomejs.dev/reference/configuration/#filesignore) - -> [!WARNING] -> If you are using VSCode, you may experience the route tree file unexpectedly open (with errors) after renaming a route. - -You can prevent that from the VSCode settings by marking the file as readonly. Our recommendation is to also exclude it from search results and file watcher with the following settings: - -```json -{ - "files.readonlyInclude": { - "**/routeTree.gen.ts": true - }, - "files.watcherExclude": { - "**/routeTree.gen.ts": true - }, - "search.exclude": { - "**/routeTree.gen.ts": true - } -} -``` - -You can use those settings either at a user level or only for a single workspace by creating the file `.vscode/settings.json` at the root of your project. - -## Configuration - -When using the TanStack Router Plugin with Rspack (or Rsbuild) for File-based routing, it comes with some sane defaults that should work for most projects: - -```json -{ - "routesDirectory": "./src/routes", - "generatedRouteTree": "./src/routeTree.gen.ts", - "routeFileIgnorePrefix": "-", - "quoteStyle": "single" -} -``` - -If these defaults work for your project, you don't need to configure anything at all! However, if you need to customize the configuration, you can do so by editing the configuration object passed into the `tanstackRouter` function. - -You can find all the available configuration options in the [File-based Routing API Reference](../../../api/file-based-routing.md). diff --git a/docs/router/framework/react/installation/with-webpack.md b/docs/router/framework/react/installation/with-webpack.md deleted file mode 100644 index 5ec487e439a..00000000000 --- a/docs/router/framework/react/installation/with-webpack.md +++ /dev/null @@ -1,81 +0,0 @@ ---- -title: Installation with Webpack ---- - -[//]: # 'BundlerConfiguration' - -To use file-based routing with **Webpack**, you'll need to install the `@tanstack/router-plugin` package. - -```sh -npm install -D @tanstack/router-plugin -``` - -Once installed, you'll need to add the plugin to your configuration. - -```tsx -// webpack.config.ts -import { tanstackRouter } from '@tanstack/router-plugin/webpack' - -export default { - plugins: [ - tanstackRouter({ - target: 'react', - autoCodeSplitting: true, - }), - ], -} -``` - -Or, you can clone our [Quickstart Webpack example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-webpack-file-based) and get started. - -Now that you've added the plugin to your Webpack configuration, you're all set to start using file-based routing with TanStack Router. - -[//]: # 'BundlerConfiguration' - -## Ignoring the generated route tree file - -If your project is configured to use a linter and/or formatter, you may want to ignore the generated route tree file. This file is managed by TanStack Router and therefore shouldn't be changed by your linter or formatter. - -Here are some resources to help you ignore the generated route tree file: - -- Prettier - [https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore](https://prettier.io/docs/en/ignore.html#ignoring-files-prettierignore) -- ESLint - [https://eslint.org/docs/latest/use/configure/ignore#ignoring-files](https://eslint.org/docs/latest/use/configure/ignore#ignoring-files) -- Biome - [https://biomejs.dev/reference/configuration/#filesignore](https://biomejs.dev/reference/configuration/#filesignore) - -> [!WARNING] -> If you are using VSCode, you may experience the route tree file unexpectedly open (with errors) after renaming a route. - -You can prevent that from the VSCode settings by marking the file as readonly. Our recommendation is to also exclude it from search results and file watcher with the following settings: - -```json -{ - "files.readonlyInclude": { - "**/routeTree.gen.ts": true - }, - "files.watcherExclude": { - "**/routeTree.gen.ts": true - }, - "search.exclude": { - "**/routeTree.gen.ts": true - } -} -``` - -You can use those settings either at a user level or only for a single workspace by creating the file `.vscode/settings.json` at the root of your project. - -## Configuration - -When using the TanStack Router Plugin with Webpack for File-based routing, it comes with some sane defaults that should work for most projects: - -```json -{ - "routesDirectory": "./src/routes", - "generatedRouteTree": "./src/routeTree.gen.ts", - "routeFileIgnorePrefix": "-", - "quoteStyle": "single" -} -``` - -If these defaults work for your project, you don't need to configure anything at all! However, if you need to customize the configuration, you can do so by editing the configuration object passed into the `tanstackRouter` function. - -You can find all the available configuration options in the [File-based Routing API Reference](../../../api/file-based-routing.md). diff --git a/docs/router/framework/solid/decisions-on-dx.md b/docs/router/framework/solid/decisions-on-dx.md deleted file mode 100644 index ab5f466f1fe..00000000000 --- a/docs/router/framework/solid/decisions-on-dx.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/decisions-on-dx.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/devtools.md b/docs/router/framework/solid/devtools.md deleted file mode 100644 index e0414970ec5..00000000000 --- a/docs/router/framework/solid/devtools.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Devtools -ref: docs/router/framework/react/devtools.md -replace: - { - 'react-router-devtools': 'solid-router-devtools', - 'React': 'Solid', - 'className': 'class', - 'react/examples/basic-devtools-panel': 'solid/examples/basic-devtools-panel', - } ---- diff --git a/docs/router/framework/solid/faq.md b/docs/router/framework/solid/faq.md deleted file mode 100644 index 38784ec02f8..00000000000 --- a/docs/router/framework/solid/faq.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/faq.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/authenticated-routes.md b/docs/router/framework/solid/guide/authenticated-routes.md deleted file mode 100644 index 9f97e148827..00000000000 --- a/docs/router/framework/solid/guide/authenticated-routes.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -ref: docs/router/framework/react/guide/authenticated-routes.md -replace: - { - 'react-router': 'solid-router', - 'auth.isAuthenticated': 'auth.isAuthenticated()', - 'react': 'solid', - } ---- diff --git a/docs/router/framework/solid/guide/automatic-code-splitting.md b/docs/router/framework/solid/guide/automatic-code-splitting.md deleted file mode 100644 index cd4802869b1..00000000000 --- a/docs/router/framework/solid/guide/automatic-code-splitting.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/automatic-code-splitting.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/code-splitting.md b/docs/router/framework/solid/guide/code-splitting.md deleted file mode 100644 index 2581c142400..00000000000 --- a/docs/router/framework/solid/guide/code-splitting.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/code-splitting.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/creating-a-router.md b/docs/router/framework/solid/guide/creating-a-router.md deleted file mode 100644 index b4b826b7393..00000000000 --- a/docs/router/framework/solid/guide/creating-a-router.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/creating-a-router.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/custom-link.md b/docs/router/framework/solid/guide/custom-link.md deleted file mode 100644 index a859e2a6ad6..00000000000 --- a/docs/router/framework/solid/guide/custom-link.md +++ /dev/null @@ -1,46 +0,0 @@ ---- -ref: docs/router/framework/react/guide/custom-link.md -replace: { 'react-router': 'solid-router' } ---- - -[//]: # 'BasicExampleImplementation' - -```tsx -import * as Solid from 'solid-js' -import { createLink, LinkComponent } from '@tanstack/solid-router' - -export const Route = createRootRoute({ - component: RootComponent, -}) - -type BasicLinkProps = Solid.JSX.IntrinsicElements['a'] & { - // Add any additional props you want to pass to the anchor element -} - -const BasicLinkComponent: Solid.Component = (props) => ( - - {props.children} - -) - -const CreatedLinkComponent = createLink(BasicLinkComponent) - -export const CustomLink: LinkComponent = (props) => { - return -} -``` - -[//]: # 'BasicExampleImplementation' -[//]: # 'ExamplesUsingThirdPartyLibs' - -## `createLink` with third party libraries - -Here are some examples of how you can use `createLink` with third-party libraries. - -### Some Library example - -```tsx -// TODO: Add this example. -``` - -[//]: # 'ExamplesUsingThirdPartyLibs' diff --git a/docs/router/framework/solid/guide/custom-search-param-serialization.md b/docs/router/framework/solid/guide/custom-search-param-serialization.md deleted file mode 100644 index c8f5bcd762a..00000000000 --- a/docs/router/framework/solid/guide/custom-search-param-serialization.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/custom-search-param-serialization.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/data-loading.md b/docs/router/framework/solid/guide/data-loading.md deleted file mode 100644 index b10e5d09534..00000000000 --- a/docs/router/framework/solid/guide/data-loading.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/data-loading.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/data-mutations.md b/docs/router/framework/solid/guide/data-mutations.md deleted file mode 100644 index 7070f8a2141..00000000000 --- a/docs/router/framework/solid/guide/data-mutations.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/data-mutations.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/deferred-data-loading.md b/docs/router/framework/solid/guide/deferred-data-loading.md deleted file mode 100644 index 3d25b32324d..00000000000 --- a/docs/router/framework/solid/guide/deferred-data-loading.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -ref: docs/router/framework/react/guide/deferred-data-loading.md -replace: { 'react-router': 'solid-router', 'react-query': 'solid-query' } ---- - -[//]: # 'DeferredWithAwaitFinalTip' -[//]: # 'DeferredWithAwaitFinalTip' -[//]: # 'SSRContent' -[//]: # 'SSRContent' diff --git a/docs/router/framework/solid/guide/document-head-management.md b/docs/router/framework/solid/guide/document-head-management.md deleted file mode 100644 index 1f5f7f40d78..00000000000 --- a/docs/router/framework/solid/guide/document-head-management.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/document-head-management.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/external-data-loading.md b/docs/router/framework/solid/guide/external-data-loading.md deleted file mode 100644 index 8913149651b..00000000000 --- a/docs/router/framework/solid/guide/external-data-loading.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/external-data-loading.md -replace: { 'react-router': 'solid-router', 'react-query': 'solid-query' } ---- diff --git a/docs/router/framework/solid/guide/history-types.md b/docs/router/framework/solid/guide/history-types.md deleted file mode 100644 index 6dbfe4f6ac4..00000000000 --- a/docs/router/framework/solid/guide/history-types.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/history-types.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/link-options.md b/docs/router/framework/solid/guide/link-options.md deleted file mode 100644 index 83270f50759..00000000000 --- a/docs/router/framework/solid/guide/link-options.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/link-options.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/navigation-blocking.md b/docs/router/framework/solid/guide/navigation-blocking.md deleted file mode 100644 index e3491cf3629..00000000000 --- a/docs/router/framework/solid/guide/navigation-blocking.md +++ /dev/null @@ -1,157 +0,0 @@ ---- -ref: docs/router/framework/react/guide/navigation-blocking.md -replace: { 'react-router': 'solid-router' } ---- - -[//]: # 'HookBasedBlockingExample' - -```tsx -import { useBlocker } from '@tanstack/solid-router' - -function MyComponent() { - const [formIsDirty, setFormIsDirty] = createSignal(false) - - useBlocker({ - shouldBlockFn: () => { - if (!formIsDirty()) return false - - const shouldLeave = confirm('Are you sure you want to leave?') - return !shouldLeave - }, - }) - - // ... -} -``` - -[//]: # 'HookBasedBlockingExample' -[//]: # 'ComponentBasedBlockingExample' - -```tsx -import { Block } from '@tanstack/solid-router' - -function MyComponent() { - const [formIsDirty, setFormIsDirty] = createSignal(false) - - return ( - { - if (!formIsDirty()) return false - - const shouldLeave = confirm('Are you sure you want to leave?') - return !shouldLeave - }} - /> - ) - - // OR - - return ( - !formIsDirty} withResolver> - {({ status, proceed, reset }) => <>{/* ... */}} - - ) -} -``` - -[//]: # 'ComponentBasedBlockingExample' -[//]: # 'HookBasedCustomUIBlockingWithResolverExample' - -```tsx -import { useBlocker } from '@tanstack/solid-router' - -function MyComponent() { - const [formIsDirty, setFormIsDirty] = createSignal(false) - - const { proceed, reset, status } = useBlocker({ - shouldBlockFn: () => formIsDirty(), - withResolver: true, - }) - - // ... - - return ( - <> - {/* ... */} - {status === 'blocked' && ( -
-

Are you sure you want to leave?

- - -
- )} - -} -``` - -[//]: # 'HookBasedCustomUIBlockingWithResolverExample' -[//]: # 'HookBasedCustomUIBlockingWithoutResolverExample' - -```tsx -import { useBlocker } from '@tanstack/solid-router' - -function MyComponent() { - const [formIsDirty, setFormIsDirty] = createSignal(false) - - useBlocker({ - shouldBlockFn: () => { - if (!formIsDirty()) { - return false - } - - const shouldBlock = new Promise((resolve) => { - // Using a modal manager of your choice - modals.open({ - title: 'Are you sure you want to leave?', - children: ( - { - modals.closeAll() - resolve(false) - }} - reject={() => { - modals.closeAll() - resolve(true) - }} - /> - ), - onClose: () => resolve(true), - }) - }) - return shouldBlock - }, - }) - - // ... -} -``` - -[//]: # 'HookBasedCustomUIBlockingWithoutResolverExample' -[//]: # 'ComponentBasedCustomUIBlockingExample' - -```tsx -import { Block } from '@tanstack/solid-router' - -function MyComponent() { - const [formIsDirty, setFormIsDirty] = createSignal(false) - - return ( - formIsDirty()} withResolver> - {({ status, proceed, reset }) => ( - <> - {/* ... */} - {status === 'blocked' && ( -
-

Are you sure you want to leave?

- - -
- )} - - )} -
- ) -} -``` - -[//]: # 'ComponentBasedCustomUIBlockingExample' diff --git a/docs/router/framework/solid/guide/navigation.md b/docs/router/framework/solid/guide/navigation.md deleted file mode 100644 index 12f132a02fa..00000000000 --- a/docs/router/framework/solid/guide/navigation.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/navigation.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/not-found-errors.md b/docs/router/framework/solid/guide/not-found-errors.md deleted file mode 100644 index 0ab152da2a0..00000000000 --- a/docs/router/framework/solid/guide/not-found-errors.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/not-found-errors.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/outlets.md b/docs/router/framework/solid/guide/outlets.md deleted file mode 100644 index e340e6bdfe0..00000000000 --- a/docs/router/framework/solid/guide/outlets.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/outlets.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/path-params.md b/docs/router/framework/solid/guide/path-params.md deleted file mode 100644 index 7851e94bd8d..00000000000 --- a/docs/router/framework/solid/guide/path-params.md +++ /dev/null @@ -1,769 +0,0 @@ ---- -title: Path Params ---- - -Path params are used to match a single segment (the text until the next `/`) and provide its value back to you as a **named** variable. They are defined by using the `$` character prefix in the path, followed by the key variable to assign it to. The following are valid path param paths: - -- `$postId` -- `$name` -- `$teamId` -- `about/$name` -- `team/$teamId` -- `blog/$postId` - -Because path param routes only match to the next `/`, child routes can be created to continue expressing hierarchy: - -Let's create a post route file that uses a path param to match the post ID: - -- `posts.$postId.tsx` - -```tsx -import { createFileRoute } from '@tanstack/solid-router' - -export const Route = createFileRoute('/posts/$postId')({ - loader: async ({ params }) => { - return fetchPost(params.postId) - }, -}) -``` - -## Path Params can be used by child routes - -Once a path param has been parsed, it is available to all child routes. This means that if we define a child route to our `postRoute`, we can use the `postId` variable from the URL in the child route's path! - -## Path Params in Loaders - -Path params are passed to the loader as a `params` object. The keys of this object are the names of the path params, and the values are the values that were parsed out of the actual URL path. For example, if we were to visit the `/blog/123` URL, the `params` object would be `{ postId: '123' }`: - -```tsx -export const Route = createFileRoute('/posts/$postId')({ - loader: async ({ params }) => { - return fetchPost(params.postId) - }, -}) -``` - -The `params` object is also passed to the `beforeLoad` option: - -```tsx -export const Route = createFileRoute('/posts/$postId')({ - beforeLoad: async ({ params }) => { - // do something with params.postId - }, -}) -``` - -## Path Params in Components - -If we add a component to our `postRoute`, we can access the `postId` variable from the URL by using the route's `useParams` hook: - -```tsx -export const Route = createFileRoute('/posts/$postId')({ - component: PostComponent, -}) - -function PostComponent() { - const params = Route.useParams() - return
Post {params().postId}
-} -``` - -> 🧠 Quick tip: If your component is code-split, you can use the [getRouteApi function](./code-splitting.md#manually-accessing-route-apis-in-other-files-with-the-getrouteapi-helper) to avoid having to import the `Route` configuration to get access to the typed `useParams()` hook. - -## Path Params outside of Routes - -You can also use the globally exported `useParams` hook to access any parsed path params from any component in your app. You'll need to pass the `strict: false` option to `useParams`, denoting that you want to access the params from an ambiguous location: - -```tsx -function PostComponent() { - const params = useParams({ strict: false }) - return
Post {params().postId}
-} -``` - -## Navigating with Path Params - -When navigating to a route with path params, TypeScript will require you to pass the params either as an object or as a function that returns an object of params. - -Let's see what an object style looks like: - -```tsx -function Component() { - return ( - - Post 123 - - ) -} -``` - -And here's what a function style looks like: - -```tsx -function Component() { - return ( - ({ ...prev, postId: '123' })}> - Post 123 - - ) -} -``` - -Notice that the function style is useful when you need to persist params that are already in the URL for other routes. This is because the function style will receive the current params as an argument, allowing you to modify them as needed and return the final params object. - -## Prefixes and Suffixes for Path Params - -You can also use **prefixes** and **suffixes** with path params to create more complex routing patterns. This allows you to match specific URL structures while still capturing the dynamic segments. - -When using either prefixes or suffixes, you can define them by wrapping the path param in curly braces `{}` and placing the prefix or suffix before or after the variable name. - -### Defining Prefixes - -Prefixes are defined by placing the prefix text outside the curly braces before the variable name. For example, if you want to match a URL that starts with `post-` followed by a post ID, you can define it like this: - -```tsx -// src/routes/posts/post-{$postId}.tsx -export const Route = createFileRoute('/posts/post-{$postId}')({ - component: PostComponent, -}) - -function PostComponent() { - const params = Route.useParams() - // postId will be the value after 'post-' - return
Post ID: {params().postId}
-} -``` - -You can even combines prefixes with wildcard routes to create more complex patterns: - -```tsx -// src/routes/on-disk/storage-{$} -export const Route = createFileRoute('/on-disk/storage-{$postId}/$')({ - component: StorageComponent, -}) - -function StorageComponent() { - const params = Route.useParams() - // _splat, will be value after 'storage-' - // i.e. my-drive/documents/foo.txt - return
Storage Location: /{params()._splat}
-} -``` - -### Defining Suffixes - -Suffixes are defined by placing the suffix text outside the curly braces after the variable name. For example, if you want to match a URL a filename that ends with `txt`, you can define it like this: - -```tsx -// src/routes/files/{$fileName}txt -export const Route = createFileRoute('/files/{$fileName}.txt')({ - component: FileComponent, -}) - -function FileComponent() { - const params = Route.useParams() - // fileName will be the value before 'txt' - return
File Name: {params().fileName}
-} -``` - -You can also combine suffixes with wildcards for more complex routing patterns: - -```tsx -// src/routes/files/{$}[.]txt -export const Route = createFileRoute('/files/{$fileName}[.]txt')({ - component: FileComponent, -}) - -function FileComponent() { - const params = Route.useParams() - // _splat will be the value before '.txt' - return
File Splat: {params()._splat}
-} -``` - -### Combining Prefixes and Suffixes - -You can combine both prefixes and suffixes to create very specific routing patterns. For example, if you want to match a URL that starts with `user-` and ends with `.json`, you can define it like this: - -```tsx -// src/routes/users/user-{$userId}.json -export const Route = createFileRoute('/users/user-{$userId}.json')({ - component: UserComponent, -}) - -function UserComponent() { - const params = Route.useParams() - // userId will be the value between 'user-' and '.json' - return
User ID: {params().userId}
-} -``` - -Similar to the previous examples, you can also use wildcards with prefixes and suffixes. Go wild! - -## Optional Path Parameters - -Optional path parameters allow you to define route segments that may or may not be present in the URL. They use the `{-$paramName}` syntax and provide flexible routing patterns where certain parameters are optional. - -### Defining Optional Parameters - -Optional path parameters are defined using curly braces with a dash prefix: `{-$paramName}` - -```tsx -// Single optional parameter -// src/routes/posts/{-$category}.tsx -export const Route = createFileRoute('/posts/{-$category}')({ - component: PostsComponent, -}) - -// Multiple optional parameters -// src/routes/posts/{-$category}/{-$slug}.tsx -export const Route = createFileRoute('/posts/{-$category}/{-$slug}')({ - component: PostComponent, -}) - -// Mixed required and optional parameters -// src/routes/users/$id/{-$tab}.tsx -export const Route = createFileRoute('/users/$id/{-$tab}')({ - component: UserComponent, -}) -``` - -### How Optional Parameters Work - -Optional parameters create flexible URL patterns: - -- `/posts/{-$category}` matches both `/posts` and `/posts/tech` -- `/posts/{-$category}/{-$slug}` matches `/posts`, `/posts/tech`, and `/posts/tech/hello-world` -- `/users/$id/{-$tab}` matches `/users/123` and `/users/123/settings` - -When an optional parameter is not present in the URL, its value will be `undefined` in your route handlers and components. - -### Accessing Optional Parameters - -Optional parameters work exactly like regular parameters in your components, but their values may be `undefined`: - -```tsx -function PostsComponent() { - const params = Route.useParams() - - return ( -
- {params().category ? `Posts in ${params().category}` : 'All Posts'} -
- ) -} -``` - -### Optional Parameters in Loaders - -Optional parameters are available in loaders and may be `undefined`: - -```tsx -export const Route = createFileRoute('/posts/{-$category}')({ - loader: async ({ params }) => { - // params.category might be undefined - return fetchPosts({ category: params.category }) - }, -}) -``` - -### Optional Parameters in beforeLoad - -Optional parameters work in `beforeLoad` handlers as well: - -```tsx -export const Route = createFileRoute('/posts/{-$category}')({ - beforeLoad: async ({ params }) => { - if (params.category) { - // Validate category exists - await validateCategory(params.category) - } - }, -}) -``` - -### Advanced Optional Parameter Patterns - -#### With Prefix and Suffix - -Optional parameters support prefix and suffix patterns: - -```tsx -// File route: /files/prefix{-$name}.txt -// Matches: /files/prefix.txt and /files/prefixdocument.txt -export const Route = createFileRoute('/files/prefix{-$name}.txt')({ - component: FileComponent, -}) - -function FileComponent() { - const params = Route.useParams() - return
File: {params().name || 'default'}
-} -``` - -#### All Optional Parameters - -You can create routes where all parameters are optional: - -```tsx -// Route: /{-$year}/{-$month}/{-$day} -// Matches: /, /2023, /2023/12, /2023/12/25 -export const Route = createFileRoute('/{-$year}/{-$month}/{-$day}')({ - component: DateComponent, -}) - -function DateComponent() { - const params = Route.useParams() - - if (!params().year) return
Select a year
- if (!params().month) return
Year: {params().year}
- if (!params().day) - return ( -
- Month: {params().year}/{params().month} -
- ) - - return ( -
- Date: {params().year}/{params().month}/{params().day} -
- ) -} -``` - -#### Optional Parameters with Wildcards - -Optional parameters can be combined with wildcards for complex routing patterns: - -```tsx -// Route: /docs/{-$version}/$ -// Matches: /docs/extra/path, /docs/v2/extra/path -export const Route = createFileRoute('/docs/{-$version}/$')({ - component: DocsComponent, -}) - -function DocsComponent() { - const params = Route.useParams() - - return ( -
- Version: {params().version || 'latest'} - Path: {params()._splat} -
- ) -} -``` - -### Navigating with Optional Parameters - -When navigating to routes with optional parameters, you have fine-grained control over which parameters to include: - -```tsx -function Navigation() { - return ( -
- {/* Navigate with optional parameter */} - - Tech Posts - - - {/* Navigate without optional parameter */} - - All Posts - - - {/* Navigate with multiple optional parameters */} - - Specific Post - -
- ) -} -``` - -### Type Safety with Optional Parameters - -TypeScript provides full type safety for optional parameters: - -```tsx -function PostsComponent() { - // TypeScript knows category might be undefined - const params = Route.useParams() // category: string | undefined - - // Safe navigation - const categoryUpper = params().category?.toUpperCase() - - return
{categoryUpper || 'All Categories'}
-} - -// Navigation is type-safe and flexible - - Tech Posts - - - - Category 123 - -``` - -## Internationalization (i18n) with Optional Path Parameters - -Optional path parameters are excellent for implementing internationalization (i18n) routing patterns. You can use prefix patterns to handle multiple languages while maintaining clean, SEO-friendly URLs. - -### Prefix-based i18n - -Use optional language prefixes to support URLs like `/en/about`, `/fr/about`, or just `/about` (default language): - -```tsx -// Route: /{-$locale}/about -export const Route = createFileRoute('/{-$locale}/about')({ - component: AboutComponent, -}) - -function AboutComponent() { - const params = Route.useParams() - const currentLocale = params().locale || 'en' // Default to English - - const content = { - en: { title: 'About Us', description: 'Learn more about our company.' }, - fr: { - title: 'Γ€ Propos', - description: 'En savoir plus sur notre entreprise.', - }, - es: { - title: 'Acerca de', - description: 'Conoce mΓ‘s sobre nuestra empresa.', - }, - } - - return ( -
-

{content[currentLocale]?.title}

-

{content[currentLocale]?.description}

-
- ) -} -``` - -This pattern matches: - -- `/about` (default locale) -- `/en/about` (explicit English) -- `/fr/about` (French) -- `/es/about` (Spanish) - -### Complex i18n Patterns - -Combine optional parameters for more sophisticated i18n routing: - -```tsx -// Route: /{-$locale}/blog/{-$category}/$slug -export const Route = createFileRoute('/{-$locale}/blog/{-$category}/$slug')({ - beforeLoad: async ({ params }) => { - const locale = params.locale || 'en' - const category = params.category - - // Validate locale and category - const validLocales = ['en', 'fr', 'es', 'de'] - if (locale && !validLocales.includes(locale)) { - throw new Error('Invalid locale') - } - - return { locale, category } - }, - loader: async ({ params, context }) => { - const { locale } = context - const { slug, category } = params - - return fetchBlogPost({ slug, category, locale }) - }, - component: BlogPostComponent, -}) - -function BlogPostComponent() { - const params = Route.useParams() - const data = Route.useLoaderData() - - return ( -
-

{data.title}

-

- Category: {params().category || 'All'} | Language:{' '} - {params().locale || 'en'} -

-
{data.content}
-
- ) -} -``` - -This supports URLs like: - -- `/blog/tech/my-post` (default locale, tech category) -- `/fr/blog/my-post` (French, no category) -- `/en/blog/tech/my-post` (explicit English, tech category) -- `/es/blog/tecnologia/mi-post` (Spanish, Spanish category) - -### Language Navigation - -Create language switchers using optional i18n parameters with function-style params: - -```tsx -function LanguageSwitcher() { - const currentParams = useParams({ strict: false }) - - const languages = [ - { code: 'en', name: 'English' }, - { code: 'fr', name: 'FranΓ§ais' }, - { code: 'es', name: 'EspaΓ±ol' }, - ] - - return ( -
- {languages.map(({ code, name }) => ( - ({ - ...prev, - locale: code === 'en' ? undefined : code, // Remove 'en' for clean URLs - })} - className={currentParams().locale === code ? 'active' : ''} - > - {name} - - ))} -
- ) -} -``` - -You can also create more sophisticated language switching logic: - -```tsx -function AdvancedLanguageSwitcher() { - const currentParams = useParams({ strict: false }) - - const handleLanguageChange = (newLocale: string) => { - return (prev: any) => { - // Preserve all existing params but update locale - const updatedParams = { ...prev } - - if (newLocale === 'en') { - // Remove locale for clean English URLs - delete updatedParams.locale - } else { - updatedParams.locale = newLocale - } - - return updatedParams - } - } - - return ( -
- - FranΓ§ais - - - - EspaΓ±ol - - - - English - -
- ) -} -``` - -### Advanced i18n with Optional Parameters - -Organize i18n routes using optional parameters for flexible locale handling: - -```tsx -// Route structure: -// routes/ -// {-$locale}/ -// index.tsx // /, /en, /fr -// about.tsx // /about, /en/about, /fr/about -// blog/ -// index.tsx // /blog, /en/blog, /fr/blog -// $slug.tsx // /blog/post, /en/blog/post, /fr/blog/post - -// routes/{-$locale}/index.tsx -export const Route = createFileRoute('/{-$locale}/')({ - component: HomeComponent, -}) - -function HomeComponent() { - const params = Route.useParams() - const isRTL = ['ar', 'he', 'fa'].includes(locale || '') - - return ( -
-

Welcome ({params().locale || 'en'})

- {/* Localized content */} -
- ) -} -``` - -```tsx -// routes/{-$locale}/about.tsx -export const Route = createFileRoute('/{-$locale}/about')({ - component: AboutComponent, -}) -``` - -### SEO and Canonical URLs - -Handle SEO for i18n routes properly: - -```tsx -export const Route = createFileRoute('/{-$locale}/products/$id')({ - component: ProductComponent, - head: ({ params, loaderData }) => { - const locale = params.locale || 'en' - const product = loaderData - - return { - title: product.title[locale] || product.title.en, - meta: [ - { - name: 'description', - content: product.description[locale] || product.description.en, - }, - { - property: 'og:locale', - content: locale, - }, - ], - links: [ - // Canonical URL (always use default locale format) - { - rel: 'canonical', - href: `https://example.com/products/${params.id}`, - }, - // Alternate language versions - { - rel: 'alternate', - hreflang: 'en', - href: `https://example.com/products/${params.id}`, - }, - { - rel: 'alternate', - hreflang: 'fr', - href: `https://example.com/fr/products/${params.id}`, - }, - { - rel: 'alternate', - hreflang: 'es', - href: `https://example.com/es/products/${params.id}`, - }, - ], - } - }, -}) -``` - -### Type Safety for i18n - -Ensure type safety for your i18n implementations: - -```tsx -// Define supported locales -type Locale = 'en' | 'fr' | 'es' | 'de' - -// Type-safe locale validation -function validateLocale(locale: string | undefined): locale is Locale { - return ['en', 'fr', 'es', 'de'].includes(locale as Locale) -} - -export const Route = createFileRoute('/{-$locale}/shop/{-$category}')({ - beforeLoad: async ({ params }) => { - const { locale } = params - - // Type-safe locale validation - if (locale && !validateLocale(locale)) { - throw redirect({ - to: '/shop/{-$category}', - params: { category: params.category }, - }) - } - - return { - locale: (locale as Locale) || 'en', - isDefaultLocale: !locale || locale === 'en', - } - }, - component: ShopComponent, -}) - -function ShopComponent() { - const params = Route.useParams() - const routeContext = Route.useRouteContext() - - // TypeScript knows locale is Locale | undefined - // and we have validated it in beforeLoad - - return ( -
-

Shop {params().category ? `- ${params().category}` : ''}

-

Language: {params().locale || 'en'}

- {!routeContext().isDefaultLocale && ( - - View in English - - )} -
- ) -} -``` - -Optional path parameters provide a powerful and flexible foundation for implementing internationalization in your TanStack Router applications. Whether you prefer prefix-based or combined approaches, you can create clean, SEO-friendly URLs while maintaining excellent developer experience and type safety. - -## Allowed Characters - -By default, path params are escaped with `encodeURIComponent`. If you want to allow other valid URI characters (e.g. `@` or `+`), you can specify that in your [RouterOptions](../api/router/RouterOptionsType.md#pathparamsallowedcharacters-property). - -Example usage: - -```tsx -const router = createRouter({ - // ... - pathParamsAllowedCharacters: ['@'], -}) -``` - -The following is the list of accepted allowed characters: - -- `;` -- `:` -- `@` -- `&` -- `=` -- `+` -- `$` -- `,` diff --git a/docs/router/framework/solid/guide/preloading.md b/docs/router/framework/solid/guide/preloading.md deleted file mode 100644 index 17ce2de1bb4..00000000000 --- a/docs/router/framework/solid/guide/preloading.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/preloading.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/route-masking.md b/docs/router/framework/solid/guide/route-masking.md deleted file mode 100644 index e72dc199f07..00000000000 --- a/docs/router/framework/solid/guide/route-masking.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/route-masking.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/router-context.md b/docs/router/framework/solid/guide/router-context.md deleted file mode 100644 index 824a6158151..00000000000 --- a/docs/router/framework/solid/guide/router-context.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/router-context.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/scroll-restoration.md b/docs/router/framework/solid/guide/scroll-restoration.md deleted file mode 100644 index 2326d33fc69..00000000000 --- a/docs/router/framework/solid/guide/scroll-restoration.md +++ /dev/null @@ -1,45 +0,0 @@ ---- -ref: docs/router/framework/react/guide/scroll-restoration.md -replace: { 'react-router': 'solid-router' } ---- - -[//]: # 'ManualRestorationExample' - -```tsx -function Component() { - // We need a unique ID for manual scroll restoration on a specific element - // It should be as unique as possible for this element across your app - const scrollRestorationId = 'myVirtualizedContent' - - // We use that ID to get the scroll entry for this element - const scrollEntry = useElementScrollRestoration({ - id: scrollRestorationId, - }) - - // Let's use TanStack Virtual to virtualize some content! - let virtualizerParentRef: any - const virtualizer = createVirtualizer({ - count: 10000, - getScrollElement: () => virtualizerParentRef, - estimateSize: () => 100, - // We pass the scrollY from the scroll restoration entry to the virtualizer - // as the initial offset - initialOffset: scrollEntry?.scrollY, - }) - - return ( -
- ... -
- ) -} -``` - -[//]: # 'ManualRestorationExample' diff --git a/docs/router/framework/solid/guide/search-params.md b/docs/router/framework/solid/guide/search-params.md deleted file mode 100644 index fa3e155483c..00000000000 --- a/docs/router/framework/solid/guide/search-params.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/search-params.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/ssr.md b/docs/router/framework/solid/guide/ssr.md deleted file mode 100644 index 46444ae87c3..00000000000 --- a/docs/router/framework/solid/guide/ssr.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -ref: docs/router/framework/react/guide/ssr.md -replace: { 'react-router': 'solid-router' } ---- - -[//]: # 'ClientEntryFileExample' - -```tsx -// src/entry-client.tsx -import { hydrate } from 'solid-js/web' -import { RouterClient } from '@tanstack/solid-router/ssr/client' -import { createRouter } from './router' - -const router = createRouter() - -hydrate(() => , document.body) -``` - -[//]: # 'ClientEntryFileExample' diff --git a/docs/router/framework/solid/guide/static-route-data.md b/docs/router/framework/solid/guide/static-route-data.md deleted file mode 100644 index e27b65770f2..00000000000 --- a/docs/router/framework/solid/guide/static-route-data.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/static-route-data.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/type-safety.md b/docs/router/framework/solid/guide/type-safety.md deleted file mode 100644 index 4aa41008be0..00000000000 --- a/docs/router/framework/solid/guide/type-safety.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/guide/type-safety.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/guide/type-utilities.md b/docs/router/framework/solid/guide/type-utilities.md deleted file mode 100644 index 525f884a2ef..00000000000 --- a/docs/router/framework/solid/guide/type-utilities.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -ref: docs/router/framework/react/guide/type-utilities.md -replace: - { 'react-router': 'solid-router', 'React.ReactNode': 'Solid.JSX.Element' } ---- - -[//]: # 'TypeCheckingNavigateOptionsWithValidateNavigateOptionsImpl' - -```tsx -export interface UseConditionalNavigateResult { - enable: () => void - disable: () => void - navigate: () => void -} - -export function useConditionalNavigate< - TRouter extends RegisteredRouter, - TOptions, ->( - navigateOptions: ValidateNavigateOptions, -): UseConditionalNavigateResult -export function useConditionalNavigate( - navigateOptions: ValidateNavigateOptions, -): UseConditionalNavigateResult { - const [enabled, setEnabled] = createSignal(false) - const navigate = useNavigate() - return { - enable: () => setEnabled(true), - disable: () => setEnabled(false), - navigate: () => { - if (enabled) { - navigate(navigateOptions) - } - }, - } -} -``` - -[//]: # 'TypeCheckingNavigateOptionsWithValidateNavigateOptionsImpl' diff --git a/docs/router/framework/solid/installation/manual.md b/docs/router/framework/solid/installation/manual.md deleted file mode 100644 index 0624eac8f3e..00000000000 --- a/docs/router/framework/solid/installation/manual.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -title: Manual Setup -ref: docs/router/framework/react/installation/manual.md -replace: { 'react-router': 'solid-router' } ---- - -[//]: # 'Requirements' - -- `solid-js`v1.x.x - -[//]: # 'Requirements' diff --git a/docs/router/framework/solid/installation/with-router-cli.md b/docs/router/framework/solid/installation/with-router-cli.md deleted file mode 100644 index 571ec4678f2..00000000000 --- a/docs/router/framework/solid/installation/with-router-cli.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -ref: docs/router/framework/react/installation/with-router-cli.md ---- - -[//]: # 'AfterScripts' - -If you are using TypeScript, you should also add the following options to your `tsconfig.json`: - -```json -{ - "compilerOptions": { - "jsx": "preserve", - "jsxImportSource": "solid-js" - } -} -``` - -With that, you're all set to start using file-based routing with TanStack Router. - -[//]: # 'AfterScripts' -[//]: # 'TargetConfiguration' - -Since you are using Solid, you should add the following to your `tsr.config.json` file: - -```json -{ - "target": "solid" -} -``` - -[//]: # 'TargetConfiguration' diff --git a/docs/router/framework/solid/installation/with-vite.md b/docs/router/framework/solid/installation/with-vite.md deleted file mode 100644 index 6e7085fc437..00000000000 --- a/docs/router/framework/solid/installation/with-vite.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -ref: docs/router/framework/react/installation/with-vite.md ---- - -[//]: # 'BundlerConfiguration' - -To use file-based routing with **Vite**, you'll need to install the `@tanstack/router-plugin` package. - -```sh -npm install -D @tanstack/router-plugin -``` - -Once installed, you'll need to add the plugin to your Vite configuration. - -```ts -// vite.config.ts -import { defineConfig } from 'vite' -import 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(), - // ... - ], -}) -``` - -If you are using TypeScript, you should also add the following options to your `tsconfig.json`: - -```json -{ - "compilerOptions": { - "jsx": "preserve", - "jsxImportSource": "solid-js" - } -} -``` - -Or, you can clone our [Quickstart Vite example](https://github.com/TanStack/router/tree/main/examples/solid/quickstart-file-based) and get started. - -Now that you've added the plugin to your Vite configuration, you're all set to start using file-based routing with TanStack Router. - -[//]: # 'BundlerConfiguration' diff --git a/docs/router/framework/solid/llm-support.md b/docs/router/framework/solid/llm-support.md deleted file mode 100644 index 7e92494073e..00000000000 --- a/docs/router/framework/solid/llm-support.md +++ /dev/null @@ -1,3 +0,0 @@ ---- -ref: docs/router/framework/react/llm-support.md ---- diff --git a/docs/router/framework/solid/overview.md b/docs/router/framework/solid/overview.md deleted file mode 100644 index 15434efbdc6..00000000000 --- a/docs/router/framework/solid/overview.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Overview -ref: docs/router/framework/react/overview.md ---- diff --git a/docs/router/framework/solid/quick-start.md b/docs/router/framework/solid/quick-start.md deleted file mode 100644 index 6a89405e5a9..00000000000 --- a/docs/router/framework/solid/quick-start.md +++ /dev/null @@ -1,65 +0,0 @@ ---- -ref: docs/router/framework/react/quick-start.md -replace: { 'React': 'Solid', 'react': 'solid' } ---- - -[//]: # 'createAppCommand' - -```sh -npx create-tsrouter-app@latest --framework solid -``` - -[//]: # 'createAppCommand' -[//]: # 'CLIPrompts' - -- File-based or code-based route configuration -- TypeScript support -- Toolchain setup -- Git initialization - -[//]: # 'CLIPrompts' -[//]: # 'createAppCommandFileBased' - -```sh -npx create-tsrouter-app@latest my-app --framework solid --template file-router -``` - -[//]: # 'createAppCommandFileBased' -[//]: # 'createAppCommandCodeBased' - -```sh -npx create-tsrouter-app@latest my-app --framework solid -``` - -[//]: # 'createAppCommandCodeBased' -[//]: # 'Requirements' - -- `solid-js` v1.x.x - -[//]: # 'Requirements' -[//]: # 'installCommand' - -```sh -npm install @tanstack/solid-router -# or -pnpm add @tanstack/solid-router -#or -yarn add @tanstack/solid-router -# or -bun add @tanstack/solid-router -# or -deno add npm:@tanstack/solid-router -``` - -[//]: # 'installCommand' -[//]: # 'packageJson' - -```json -{ - "dependencies": { - "@tanstack/solid-router": "^x.x.x" - } -} -``` - -[//]: # 'packageJson' diff --git a/docs/router/framework/solid/routing/code-based-routing.md b/docs/router/framework/solid/routing/code-based-routing.md deleted file mode 100644 index d85d52ad471..00000000000 --- a/docs/router/framework/solid/routing/code-based-routing.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/routing/code-based-routing.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/routing/file-based-routing.md b/docs/router/framework/solid/routing/file-based-routing.md deleted file mode 100644 index e759ac98bfb..00000000000 --- a/docs/router/framework/solid/routing/file-based-routing.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -ref: docs/router/framework/react/routing/file-based-routing.md -replace: { 'react-router': 'solid-router' } ---- - -[//]: # 'SupportedBundlersList' - -- [Configuration with Vite](#configuration-with-vite) - -[//]: # 'SupportedBundlersList' -[//]: # 'ConfigurationBundlerVite' - -### Configuration with Vite - -To use file-based routing with **Vite**, you'll need to install the `@tanstack/router-plugin` package. - -```sh -npm install -D @tanstack/router-plugin -``` - -Once installed, you'll need to add the plugin to your Vite configuration. - -```ts -// vite.config.ts -import { defineConfig } from 'vite' -import 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(), - // ... - ], -}) -``` - -Or, you can clone our [Quickstart Vite example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-file-based) and get started. - -Now that you've added the plugin to your Vite configuration, you're all set to start using file-based routing with TanStack Router. - -You shouldn't forget to _ignore_ the generated route tree file. Head over to the [Ignoring the generated route tree file](#ignoring-the-generated-route-tree-file) section to learn more. - -[//]: # 'ConfigurationBundlerVite' -[//]: # 'ConfigurationBundlerRspack' -[//]: # 'ConfigurationBundlerRspack' -[//]: # 'ConfigurationBundlerWebpack' -[//]: # 'ConfigurationBundlerWebpack' -[//]: # 'ConfigurationBundlerEsbuild' -[//]: # 'ConfigurationBundlerEsbuild' diff --git a/docs/router/framework/solid/routing/file-naming-conventions.md b/docs/router/framework/solid/routing/file-naming-conventions.md deleted file mode 100644 index 6598516d4ab..00000000000 --- a/docs/router/framework/solid/routing/file-naming-conventions.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/routing/file-naming-conventions.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/routing/route-matching.md b/docs/router/framework/solid/routing/route-matching.md deleted file mode 100644 index 6b389cee5b7..00000000000 --- a/docs/router/framework/solid/routing/route-matching.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/routing/route-matching.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/routing/route-trees.md b/docs/router/framework/solid/routing/route-trees.md deleted file mode 100644 index d58bd8f19cb..00000000000 --- a/docs/router/framework/solid/routing/route-trees.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/routing/route-trees.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/routing/routing-concepts.md b/docs/router/framework/solid/routing/routing-concepts.md deleted file mode 100644 index 30e7bbb999e..00000000000 --- a/docs/router/framework/solid/routing/routing-concepts.md +++ /dev/null @@ -1,4 +0,0 @@ ---- -ref: docs/router/framework/react/routing/routing-concepts.md -replace: { 'react-router': 'solid-router' } ---- diff --git a/docs/router/framework/solid/routing/virtual-file-routes.md b/docs/router/framework/solid/routing/virtual-file-routes.md deleted file mode 100644 index 9cc7be3b4e6..00000000000 --- a/docs/router/framework/solid/routing/virtual-file-routes.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -ref: docs/router/framework/react/routing/virtual-file-routes.md -replace: - { 'react-router': 'solid-router', "target: 'react'": "target: 'solid'" } ---- diff --git a/docs/router/framework/react/guide/authenticated-routes.md b/docs/router/guide/authenticated-routes.md similarity index 74% rename from docs/router/framework/react/guide/authenticated-routes.md rename to docs/router/guide/authenticated-routes.md index 4de520bbfea..706e89955c7 100644 --- a/docs/router/framework/react/guide/authenticated-routes.md +++ b/docs/router/guide/authenticated-routes.md @@ -55,6 +55,10 @@ export const Route = createFileRoute('/_authenticated')({ If your authentication check can throw errors (network failures, token validation, etc.), wrap it in try/catch: + + +# React + ```tsx import { createFileRoute, redirect, isRedirect } from '@tanstack/react-router' @@ -84,6 +88,39 @@ export const Route = createFileRoute('/_authenticated')({ }) ``` +# Solid + +```tsx +import { createFileRoute, redirect, isRedirect } from '@tanstack/solid-router' + +// src/routes/_authenticated.tsx +export const Route = createFileRoute('/_authenticated')({ + beforeLoad: async ({ location }) => { + try { + const user = await verifySession() // might throw on network error + if (!user) { + throw redirect({ + to: '/login', + search: { redirect: location.href }, + }) + } + return { user } + } catch (error) { + // Re-throw redirects (they're intentional, not errors) + if (isRedirect(error)) throw error + + // Auth check failed (network error, etc.) - redirect to login + throw redirect({ + to: '/login', + search: { redirect: location.href }, + }) + } + }, +}) +``` + + + The [`isRedirect()`](../api/router/isRedirectFunction.md) helper distinguishes between actual errors and intentional redirects. Once you have authenticated a user, it's also common practice to redirect them back to the page they were trying to access. To do this, you can utilize the `redirect` search param that we added in our original redirect. Since we'll be replacing the entire URL with what it was, `router.history.push` is better suited for this than `router.navigate`: @@ -122,9 +159,13 @@ We'll cover the `router.context` options in-detail in the [Router Context](./rou Here's an example that uses React context and hooks for protecting authenticated routes in TanStack Router. See the entire working setup in the [Authenticated Routes example](https://github.com/TanStack/router/tree/main/examples/react/authenticated-routes). -- `src/routes/__root.tsx` + -```tsx +# React + + + +```tsx title="src/routes/__root.tsx" import { createRootRouteWithContext } from '@tanstack/react-router' interface MyRouterContext { @@ -137,9 +178,7 @@ export const Route = createRootRouteWithContext()({ }) ``` -- `src/router.tsx` - -```tsx +```tsx title="src/router.tsx" import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' @@ -154,9 +193,7 @@ export const router = createRouter({ }) ``` -- `src/App.tsx` - -```tsx +```tsx title="src/App.tsx" import { RouterProvider } from '@tanstack/react-router' import { AuthProvider, useAuth } from './auth' @@ -177,11 +214,72 @@ function App() { } ``` + + +# Solid + + + +```tsx title="src/routes/__root.tsx" +import { createRootRouteWithContext } from '@tanstack/solid-router' + +interface MyRouterContext { + // The ReturnType of your useAuth hook or the value of your AuthContext + auth: AuthState +} + +export const Route = createRootRouteWithContext()({ + component: () => , +}) +``` + +```tsx title="src/router.tsx" +import { createRouter } from '@tanstack/solid-router' + +import { routeTree } from './routeTree.gen' + +export const router = createRouter({ + routeTree, + context: { + // auth will initially be undefined + // We'll be passing down the auth state from within a React component + auth: undefined!, + }, +}) +``` + +```tsx title="src/App.tsx" +import { RouterProvider } from '@tanstack/solid-router' + +import { AuthProvider, useAuth } from './auth' + +import { router } from './router' + +function InnerApp() { + const auth = useAuth() + return +} + +function App() { + return ( + + + + ) +} +``` + + + + + Then in the authenticated route, you can check the auth state using the `beforeLoad` function, and **throw a `redirect()`** to your **Login route** if the user is not signed-in. -- `src/routes/dashboard.route.tsx` + -```tsx +# React + +```tsx title="src/routes/dashboard.route.tsx" import { createFileRoute, redirect } from '@tanstack/react-router' export const Route = createFileRoute('/dashboard')({ @@ -198,6 +296,27 @@ export const Route = createFileRoute('/dashboard')({ }) ``` +# Solid + +```tsx title="src/routes/dashboard.route.tsx" +import { createFileRoute, redirect } from '@tanstack/solid-router' + +export const Route = createFileRoute('/dashboard')({ + beforeLoad: ({ context, location }) => { + if (!context.auth.isAuthenticated()) { + throw redirect({ + to: '/login', + search: { + redirect: location.href, + }, + }) + } + }, +}) +``` + + + You can _optionally_, also use the [Non-Redirected Authentication](#non-redirected-authentication) approach to show a login form instead of calling a **redirect**. This approach can also be used in conjunction with Pathless or Layout Route to protect all routes under their parent route. diff --git a/docs/router/framework/react/guide/automatic-code-splitting.md b/docs/router/guide/automatic-code-splitting.md similarity index 98% rename from docs/router/framework/react/guide/automatic-code-splitting.md rename to docs/router/guide/automatic-code-splitting.md index 8c980d7d82f..4612c298c60 100644 --- a/docs/router/framework/react/guide/automatic-code-splitting.md +++ b/docs/router/guide/automatic-code-splitting.md @@ -83,8 +83,6 @@ For automatic code splitting to work, there are some rules in-place to make sure Route properties like `component`, `loader`, etc., should not be exported from the route file. Exporting these properties results in them being bundled into the main application bundle, which means that they will not be code-split. ```tsx -import { createRoute } from '@tanstack/react-router' - export const Route = createRoute('/posts')({ // ... notFoundComponent: PostsNotFoundComponent, @@ -116,8 +114,7 @@ You can change how TanStack Router splits your routes by changing the `defaultBe For example, to bundle all UI-related components into a single chunk, you could configure it like this: -```ts -// vite.config.ts +```ts title="vite.config.ts" import { defineConfig } from 'vite' import { tanstackRouter } from '@tanstack/router-plugin/vite' @@ -144,8 +141,7 @@ export default defineConfig({ For complex rulesets, you can use the `splitBehavior` function in your vite config to programmatically define how routes should be split into chunks based on their `routeId`. This function allows you to implement custom logic for grouping properties together, giving you fine-grained control over the code splitting behavior. -```ts -// vite.config.ts +```ts title="vite.config.ts" import { defineConfig } from 'vite' import { tanstackRouter } from '@tanstack/router-plugin/vite' @@ -171,9 +167,7 @@ export default defineConfig({ For ultimate control, you can override the global configuration directly inside a route file by adding a `codeSplitGroupings` property. This is useful for routes that have unique optimization needs. -```tsx -// src/routes/posts.route.tsx -import { createFileRoute } from '@tanstack/react-router' +```tsx title="src/routes/posts.route.tsx" import { loadPostsData } from './-heavy-posts-utils' export const Route = createFileRoute('/posts')({ diff --git a/docs/router/framework/react/guide/code-splitting.md b/docs/router/guide/code-splitting.md similarity index 76% rename from docs/router/framework/react/guide/code-splitting.md rename to docs/router/guide/code-splitting.md index e422562725d..698bf262b5a 100644 --- a/docs/router/framework/react/guide/code-splitting.md +++ b/docs/router/guide/code-splitting.md @@ -119,8 +119,11 @@ When you are using `.lazy.tsx` you can split your route into two files to enable **Before (Single File)** -```tsx -// src/routes/posts.tsx + + +# React + +```tsx title="src/routes/posts.tsx" import { createFileRoute } from '@tanstack/react-router' import { fetchPosts } from './api' @@ -134,13 +137,33 @@ function Posts() { } ``` +# Solid + +```tsx title="src/routes/posts.tsx" +import { createFileRoute } from '@tanstack/solid-router' +import { fetchPosts } from './api' + +export const Route = createFileRoute('/posts')({ + loader: fetchPosts, + component: Posts, +}) + +function Posts() { + // ... +} +``` + + + **After (Split into two files)** This file would contain the critical route configuration: -```tsx -// src/routes/posts.tsx + + +# React +```tsx title="src/routes/posts.tsx" import { createFileRoute } from '@tanstack/react-router' import { fetchPosts } from './api' @@ -149,10 +172,26 @@ export const Route = createFileRoute('/posts')({ }) ``` +# Solid + +```tsx title="src/routes/posts.tsx" +import { createFileRoute } from '@tanstack/solid-router' +import { fetchPosts } from './api' + +export const Route = createFileRoute('/posts')({ + loader: fetchPosts, +}) +``` + + + With the non-critical route configuration going into the file with the `.lazy.tsx` suffix: -```tsx -// src/routes/posts.lazy.tsx + + +# React + +```tsx title="src/routes/posts.lazy.tsx" import { createLazyFileRoute } from '@tanstack/react-router' export const Route = createLazyFileRoute('/posts')({ @@ -164,14 +203,35 @@ function Posts() { } ``` +# Solid + +```tsx title="src/routes/posts.lazy.tsx" +import { createLazyFileRoute } from '@tanstack/solid-router' + +export const Route = createLazyFileRoute('/posts')({ + component: Posts, +}) + +function Posts() { + // ... +} +``` + + + ## Using Virtual Routes You might run into a situation where you end up splitting out everything from a route file, leaving it empty! In this case, simply **delete the route file entirely**! A virtual route will automatically be generated for you to serve as an anchor for your code split files. This virtual route will live directly in the generated route tree file. **Before (Virtual Routes)** -```tsx -// src/routes/posts.tsx + + +# React + + + +```tsx title="src/routes/posts.tsx" import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/posts')({ @@ -179,8 +239,7 @@ export const Route = createFileRoute('/posts')({ }) ``` -```tsx -// src/routes/posts.lazy.tsx +```tsx title="src/routes/posts.lazy.tsx" import { createLazyFileRoute } from '@tanstack/react-router' export const Route = createLazyFileRoute('/posts')({ @@ -192,10 +251,43 @@ function Posts() { } ``` + + +# Solid + + + +```tsx title="src/routes/posts.tsx" +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/posts')({ + // Hello? +}) +``` + +```tsx title="src/routes/posts.lazy.tsx" +import { createLazyFileRoute } from '@tanstack/solid-router' + +export const Route = createLazyFileRoute('/posts')({ + component: Posts, +}) + +function Posts() { + // ... +} +``` + + + + + **After (Virtual Routes)** -```tsx -// src/routes/posts.lazy.tsx + + +# React + +```tsx title="src/routes/posts.lazy.tsx" import { createLazyFileRoute } from '@tanstack/react-router' export const Route = createLazyFileRoute('/posts')({ @@ -207,6 +299,22 @@ function Posts() { } ``` +# Solid + +```tsx title="src/routes/posts.lazy.tsx" +import { createLazyFileRoute } from '@tanstack/solid-router' + +export const Route = createLazyFileRoute('/posts')({ + component: Posts, +}) + +function Posts() { + // ... +} +``` + + + Tada! πŸŽ‰ ## Code-Based Splitting @@ -215,8 +323,7 @@ If you are using code-based routing, you can still code-split your routes using Create a lazy route using the `createLazyRoute` function. -```tsx -// src/posts.lazy.tsx +```tsx title="src/posts.lazy.tsx" export const Route = createLazyRoute('/posts')({ component: MyComponent, }) @@ -228,8 +335,7 @@ function MyComponent() { Then, call the `.lazy` method on the route definition in your `app.tsx` to import the lazy/code-split route with the non-critical route configuration. -```tsx -// src/app.tsx +```tsx title="src/app.tsx" const postsRoute = createRoute({ getParentRoute: () => rootRoute, path: '/posts', @@ -244,6 +350,10 @@ It can be a powerful tool to reduce bundle size, but it comes with a cost as men You can code split your data loading logic using the Route's `loader` option. While this process makes it difficult to maintain type-safety with the parameters passed to your loader, you can always use the generic `LoaderContext` type to get you most of the way there: + + +# React + ```tsx import { lazyFn } from '@tanstack/react-router' @@ -259,15 +369,38 @@ export const loader = async (context: LoaderContext) => { } ``` +# Solid + +```tsx +import { lazyFn } from '@tanstack/solid-router' + +const route = createRoute({ + path: '/my-route', + component: MyComponent, + loader: lazyFn(() => import('./loader'), 'loader'), +}) + +// In another file...a +export const loader = async (context: LoaderContext) => { + /// ... +} +``` + + + If you are using file-based routing, you'll only be able to split your `loader` if you are using [Automatic Code Splitting](#using-automatic-code-splitting) with customized bundling options. ## Manually accessing Route APIs in other files with the `getRouteApi` helper As you might have guessed, placing your component code in a separate file than your route can make it difficult to consume the route itself. To help with this, TanStack Router exports a handy `getRouteApi` function that you can use to access a route's type-safe APIs in a file without importing the route itself. -- `my-route.tsx` + -```tsx +# React + + + +```tsx title="src/my-route.tsx" import { createRoute } from '@tanstack/react-router' import { MyComponent } from './MyComponent' @@ -280,9 +413,7 @@ const route = createRoute({ }) ``` -- `MyComponent.tsx` - -```tsx +```tsx title="src/MyComponent.tsx" import { getRouteApi } from '@tanstack/react-router' const route = getRouteApi('/my-route') @@ -295,6 +426,42 @@ export function MyComponent() { } ``` + + +# Solid + + + +```tsx title="src/my-route.tsx" +import { createRoute } from '@tanstack/solid-router' +import { MyComponent } from './MyComponent' + +const route = createRoute({ + path: '/my-route', + loader: () => ({ + foo: 'bar', + }), + component: MyComponent, +}) +``` + +```tsx title="src/MyComponent.tsx" +import { getRouteApi } from '@tanstack/solid-router' + +const route = getRouteApi('/my-route') + +export function MyComponent() { + const loaderData = route.useLoaderData() + // ^? { foo: string } + + return
...
+} +``` + + + + + The `getRouteApi` function is useful for accessing other type-safe APIs: - `useLoaderData` diff --git a/docs/router/framework/react/guide/creating-a-router.md b/docs/router/guide/creating-a-router.md similarity index 83% rename from docs/router/framework/react/guide/creating-a-router.md rename to docs/router/guide/creating-a-router.md index d84815bd7ce..f8919d6b511 100644 --- a/docs/router/framework/react/guide/creating-a-router.md +++ b/docs/router/guide/creating-a-router.md @@ -2,11 +2,15 @@ title: Creating a Router --- -## The `Router` Class +## The `createRouter` function When you're ready to start using your router, you'll need to create a new `Router` instance. The router instance is the core brains of TanStack Router and is responsible for managing the route tree, matching routes, and coordinating navigations and route transitions. It also serves as a place to configure router-wide settings. -```tsx + + +# React + +```tsx title="src/router.tsx" import { createRouter } from '@tanstack/react-router' const router = createRouter({ @@ -14,6 +18,18 @@ const router = createRouter({ }) ``` +# Solid + +```tsx title="src/router.tsx" +import { createRouter } from '@tanstack/solid-router' + +const router = createRouter({ + // ... +}) +``` + + + ## Route Tree You'll probably notice quickly that the `Router` constructor requires a `routeTree` option. This is the route tree that the router will use to match routes and render components. @@ -45,7 +61,11 @@ const routeTree = rootRoute.addChildren([ TanStack Router provides amazing support for TypeScript, even for things you wouldn't expect like bare imports straight from the library! To make this possible, you must register your router's types using TypeScripts' [Declaration Merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) feature. This is done by extending the `Register` interface on `@tanstack/react-router` with a `router` property that has the type of your `router` instance: -```tsx + + +# React + +```tsx title="src/router.tsx" declare module '@tanstack/react-router' { interface Register { // This infers the type of our router and registers it across your entire project @@ -54,6 +74,19 @@ declare module '@tanstack/react-router' { } ``` +# Solid + +```tsx title="src/router.tsx" +declare module '@tanstack/solid-router' { + interface Register { + // This infers the type of our router and registers it across your entire project + router: typeof router + } +} +``` + + + With your router registered, you'll now get type-safety across your entire project for anything related to routing. ## 404 Not Found Route diff --git a/docs/router/framework/react/guide/custom-link.md b/docs/router/guide/custom-link.md similarity index 77% rename from docs/router/framework/react/guide/custom-link.md rename to docs/router/guide/custom-link.md index 1ffb55fce80..dd7038cecd8 100644 --- a/docs/router/framework/react/guide/custom-link.md +++ b/docs/router/guide/custom-link.md @@ -12,7 +12,9 @@ While repeating yourself can be acceptable in many situations, you might find th If you want to create a basic custom link component, you can do so with the following: -[//]: # 'BasicExampleImplementation' + + +# React ```tsx import * as React from 'react' @@ -37,7 +39,34 @@ export const CustomLink: LinkComponent = (props) => { } ``` -[//]: # 'BasicExampleImplementation' +# Solid + +```tsx +import * as Solid from 'solid-js' +import { createLink, LinkComponent } from '@tanstack/solid-router' + +export const Route = createRootRoute({ + component: RootComponent, +}) + +type BasicLinkProps = Solid.JSX.IntrinsicElements['a'] & { + // Add any additional props you want to pass to the anchor element +} + +const BasicLinkComponent: Solid.Component = (props) => ( + + {props.children} + +) + +const CreatedLinkComponent = createLink(BasicLinkComponent) + +export const CustomLink: LinkComponent = (props) => { + return +} +``` + + You can then use your newly created `Link` component as any other `Link` @@ -45,17 +74,21 @@ You can then use your newly created `Link` component as any other `Link` ``` -[//]: # 'ExamplesUsingThirdPartyLibs' - ## `createLink` with third party libraries Here are some examples of how you can use `createLink` with third-party libraries. + + +# React + ### React Aria Components example React Aria Components v1.11.0 and later works with TanStack Router's `preload (intent)` prop. Use `createLink` to wrap each React Aria component that you use as a link. -```tsx + + +```tsx title="RACLink.tsx" import { createLink } from '@tanstack/react-router' import { Link as RACLink, MenuItem } from 'react-aria-components' @@ -63,9 +96,7 @@ export const Link = createLink(RACLink) export const MenuItemLink = createLink(MenuItem) ``` -To use React Aria's render props, including the `className`, `style`, and `children` functions, create a wrapper component and pass that to `createLink`. - -```tsx +```tsx title="CustomRACLink.tsx" import { createLink } from '@tanstack/react-router' import { Link as RACLink, type LinkProps } from 'react-aria-components' @@ -87,9 +118,19 @@ function MyLink(props: MyLinkProps) { export const Link = createLink(MyLink) ``` + + +To use React Aria's render props, including the `className`, `style`, and `children` functions, create a wrapper component and pass that to `createLink`. + + + + + +# React + ### Chakra UI example -```tsx +```tsx title="ChakraLinkComponent.tsx" import * as React from 'react' import { createLink, LinkComponent } from '@tanstack/react-router' import { Link } from '@chakra-ui/react' @@ -125,6 +166,12 @@ export const CustomLink: LinkComponent = ( } ``` + + + + +# React + ### MUI example There is an [example](https://github.com/TanStack/router/tree/main/examples/react/start-material-ui) available which uses these patterns. @@ -133,16 +180,22 @@ There is an [example](https://github.com/TanStack/router/tree/main/examples/reac If the MUI `Link` should simply behave like the router `Link`, it can be just wrapped with `createLink`: -```tsx + + +```tsx title="CustomLink.tsx" import { createLink } from '@tanstack/react-router' import { Link } from '@mui/material' export const CustomLink = createLink(Link) ``` + + If the `Link` should be customized this approach can be used: -```tsx + + +```tsx title="CustomLink.tsx" import React from 'react' import { createLink } from '@tanstack/react-router' import { Link } from '@mui/material' @@ -166,11 +219,15 @@ export const CustomLink: LinkComponent = (props) => { // Can also be styled ``` + + #### `Button` If a `Button` should be used as a router `Link`, the `component` should be set as `a`: -```tsx + + +```tsx title="CustomButtonLink.tsx" import React from 'react' import { createLink } from '@tanstack/react-router' import { Button } from '@mui/material' @@ -195,11 +252,15 @@ export const CustomButtonLink: LinkComponent = ( } ``` + + #### Usage with `styled` Any of these MUI approaches can then be used with `styled`: -```tsx + + +```tsx title="StyledCustomLink.tsx" import { css, styled } from '@mui/material' import { CustomLink } from './CustomLink' @@ -210,9 +271,19 @@ const StyledCustomLink = styled(CustomLink)( ) ``` + + + + + + +# React + ### Mantine example -```tsx + + +```tsx title="CustomLink.tsx" import * as React from 'react' import { createLink, LinkComponent } from '@tanstack/react-router' import { Anchor, AnchorProps } from '@mantine/core' @@ -237,4 +308,22 @@ export const CustomLink: LinkComponent = ( } ``` -[//]: # 'ExamplesUsingThirdPartyLibs' + + + + + + +# Solid + +### Some Library example + + + +```tsx title="UntitledLink.tsx" +// TODO: Add this example. +``` + + + + diff --git a/docs/router/framework/react/guide/custom-search-param-serialization.md b/docs/router/guide/custom-search-param-serialization.md similarity index 73% rename from docs/router/framework/react/guide/custom-search-param-serialization.md rename to docs/router/guide/custom-search-param-serialization.md index 33124513e8a..7e87191a5a7 100644 --- a/docs/router/framework/react/guide/custom-search-param-serialization.md +++ b/docs/router/guide/custom-search-param-serialization.md @@ -22,6 +22,10 @@ It would be serialized and escaped into the following search string: We can implement the default behavior with the following code: + + +# React + ```tsx import { createRouter, @@ -36,6 +40,24 @@ const router = createRouter({ }) ``` +# Solid + +```tsx +import { + createRouter, + parseSearchWith, + stringifySearchWith, +} from '@tanstack/solid-router' + +const router = createRouter({ + // ... + parseSearch: parseSearchWith(JSON.parse), + stringifySearch: stringifySearchWith(JSON.stringify), +}) +``` + + + However, this default behavior may not be suitable for all use cases. For example, you may want to use a different serialization format, such as base64 encoding, or you may want to use a purpose-built serialization/deserialization library, like [query-string](https://github.com/sindresorhus/query-string), [JSURL2](https://github.com/wmertens/jsurl2), or [Zipson](https://jgranstrom.github.io/zipson/). This can be achieved by providing your own serialization and deserialization functions to the `parseSearch` and `stringifySearch` options in the [`Router`](../api/router/RouterOptionsType.md#stringifysearch-method) configuration. When doing this, you can utilize TanStack Router's built-in helper functions, `parseSearchWith` and `stringifySearchWith`, to simplify the process. @@ -51,6 +73,10 @@ Here are some examples of how you can customize the search param serialization i It's common to base64 encode your search params to achieve maximum compatibility across browsers and URL unfurlers, etc. This can be done with the following code: + + +# React + ```tsx import { Router, @@ -84,6 +110,43 @@ function encodeToBinary(str: string): string { } ``` +# Solid + +```tsx +import { + Router, + parseSearchWith, + stringifySearchWith, +} from '@tanstack/solid-router' + +const router = createRouter({ + parseSearch: parseSearchWith((value) => JSON.parse(decodeFromBinary(value))), + stringifySearch: stringifySearchWith((value) => + encodeToBinary(JSON.stringify(value)), + ), +}) + +function decodeFromBinary(str: string): string { + return decodeURIComponent( + Array.prototype.map + .call(atob(str), function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + }) + .join(''), + ) +} + +function encodeToBinary(str: string): string { + return btoa( + encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { + return String.fromCharCode(parseInt(p1, 16)) + }), + ) +} +``` + + + > [⚠️ Why does this snippet not use atob/btoa?](#safe-binary-encodingdecoding) So, if we were to turn the previous object into a search string using this configuration, it would look like this: @@ -99,6 +162,10 @@ So, if we were to turn the previous object into a search string using this confi The [query-string](https://github.com/sindresorhus/query-string) library is a popular for being able to reliably parse and stringify query strings. You can use it to customize the serialization format of your search params. This can be done with the following code: + + +# React + ```tsx import { createRouter } from '@tanstack/react-router' import qs from 'query-string' @@ -118,6 +185,29 @@ const router = createRouter({ }) ``` +# Solid + +```tsx +import { createRouter } from '@tanstack/solid-router' +import qs from 'query-string' + +const router = createRouter({ + // ... + stringifySearch: stringifySearchWith((value) => + qs.stringify(value, { + // ...options + }), + ), + parseSearch: parseSearchWith((value) => + qs.parse(value, { + // ...options + }), + ), +}) +``` + + + So, if we were to turn the previous object into a search string using this configuration, it would look like this: ```txt @@ -128,6 +218,10 @@ So, if we were to turn the previous object into a search string using this confi [JSURL2](https://github.com/wmertens/jsurl2) is a non-standard library that can compress URLs while still maintaining readability. This can be done with the following code: + + +# React + ```tsx import { Router, @@ -143,6 +237,25 @@ const router = createRouter({ }) ``` +# Solid + +```tsx +import { + Router, + parseSearchWith, + stringifySearchWith, +} from '@tanstack/solid-router' +import { parse, stringify } from 'jsurl2' + +const router = createRouter({ + // ... + parseSearch: parseSearchWith(parse), + stringifySearch: stringifySearchWith(stringify), +}) +``` + + + So, if we were to turn the previous object into a search string using this configuration, it would look like this: ```txt @@ -153,6 +266,10 @@ So, if we were to turn the previous object into a search string using this confi [Zipson](https://jgranstrom.github.io/zipson/) is a very user-friendly and performant JSON compression library (both in runtime performance and the resulting compression performance). To compress your search params with it (which requires escaping/unescaping and base64 encoding/decoding them as well), you can use the following code: + + +# React + ```tsx import { Router, @@ -187,6 +304,44 @@ function encodeToBinary(str: string): string { } ``` +# Solid + +```tsx +import { + Router, + parseSearchWith, + stringifySearchWith, +} from '@tanstack/solid-router' +import { stringify, parse } from 'zipson' + +const router = createRouter({ + parseSearch: parseSearchWith((value) => parse(decodeFromBinary(value))), + stringifySearch: stringifySearchWith((value) => + encodeToBinary(stringify(value)), + ), +}) + +function decodeFromBinary(str: string): string { + return decodeURIComponent( + Array.prototype.map + .call(atob(str), function (c) { + return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2) + }) + .join(''), + ) +} + +function encodeToBinary(str: string): string { + return btoa( + encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { + return String.fromCharCode(parseInt(p1, 16)) + }), + ) +} +``` + + + > [⚠️ Why does this snippet not use atob/btoa?](#safe-binary-encodingdecoding) So, if we were to turn the previous object into a search string using this configuration, it would look like this: @@ -197,13 +352,13 @@ So, if we were to turn the previous object into a search string using this confi
-### Safe Binary Encoding/Decoding +## Safe Binary Encoding/Decoding In the browser, the `atob` and `btoa` functions are not guaranteed to work properly with non-UTF8 characters. We recommend using these encoding/decoding utilities instead: To encode from a string to a binary string: -```typescript +```ts export function encodeToBinary(str: string): string { return btoa( encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, function (match, p1) { @@ -215,7 +370,7 @@ export function encodeToBinary(str: string): string { To decode from a binary string to a string: -```typescript +```ts export function decodeFromBinary(str: string): string { return decodeURIComponent( Array.prototype.map diff --git a/docs/router/framework/react/guide/data-loading.md b/docs/router/guide/data-loading.md similarity index 96% rename from docs/router/framework/react/guide/data-loading.md rename to docs/router/guide/data-loading.md index c9cc4bd4243..80e85c13668 100644 --- a/docs/router/framework/react/guide/data-loading.md +++ b/docs/router/guide/data-loading.md @@ -60,7 +60,7 @@ The router cache is built-in and is as easy as returning data from any route's ` Route `loader` functions are called when a route match is loaded. They are called with a single parameter which is an object containing many helpful properties. We'll go over those in a bit, but first, let's look at an example of a route `loader` function: ```tsx -// routes/posts.tsx +// src/routes/posts.tsx export const Route = createFileRoute('/posts')({ loader: () => fetchPosts(), }) @@ -97,6 +97,10 @@ const posts = Route.useLoaderData() If you don't have ready access to your route object (i.e. you're deep in the component tree for the current route), you can use `getRouteApi` to access the same hook (as well as the other hooks on the Route object). This should be preferred over importing the Route object, which is likely to create circular dependencies. + + +# React + ```tsx import { getRouteApi } from '@tanstack/react-router' @@ -106,6 +110,19 @@ const routeApi = getRouteApi('/posts') const data = routeApi.useLoaderData() ``` +# Solid + +```tsx +import { getRouteApi } from '@tanstack/solid-router' + +// in your component + +const routeApi = getRouteApi('/posts') +const data = routeApi.useLoaderData() +``` + + + ## Dependency-based Stale-While-Revalidate Caching TanStack Router provides a built-in Stale-While-Revalidate caching layer for route loaders that is keyed on the dependencies of a route: @@ -280,6 +297,10 @@ export const fetchPosts = async () => { - `/routes/__root.tsx` + + +# React + ```tsx import { createRootRouteWithContext } from '@tanstack/react-router' @@ -289,11 +310,22 @@ export const Route = createRootRouteWithContext<{ }>()() // NOTE: the double call is on purpose, since createRootRouteWithContext is a factory ;) ``` -- `/routes/posts.tsx` +# Solid ```tsx -import { createFileRoute } from '@tanstack/react-router' +import { createRootRouteWithContext } from '@tanstack/solid-router' + +// Create a root route using the createRootRouteWithContext<{...}>() function and pass it whatever types you would like to be available in your router context. +export const Route = createRootRouteWithContext<{ + fetchPosts: typeof fetchPosts +}>()() // NOTE: the double call is on purpose, since createRootRouteWithContext is a factory ;) +``` + + +- `/routes/posts.tsx` + +```tsx // Notice how our postsRoute references context to get our fetchPosts function // This can be a powerful tool for dependency injection across your router // and routes. @@ -323,7 +355,7 @@ const router = createRouter({ To use path params in your `loader` function, access them via the `params` property on the function's parameters. Here's an example: ```tsx -// routes/posts.$postId.tsx +// src/routes/posts.$postId.tsx export const Route = createFileRoute('/posts/$postId')({ loader: ({ params: { postId } }) => fetchPostById(postId), }) @@ -334,9 +366,7 @@ export const Route = createFileRoute('/posts/$postId')({ Passing down global context to your router is great, but what if you want to provide context that is specific to a route? This is where the `beforeLoad` option comes in. The `beforeLoad` option is a function that runs right before attempting to load a route and receives the same parameters as `loader`. Beyond its ability to redirect potential matches, block loader requests, etc, it can also return an object that will be merged into the route's context. Let's take a look at an example where we inject some data into our route context via the `beforeLoad` option: ```tsx -// /routes/posts.tsx -import { createFileRoute } from '@tanstack/react-router' - +// src/routes/posts.tsx export const Route = createFileRoute('/posts')({ // Pass the fetchPosts function to the route context beforeLoad: () => ({ @@ -398,7 +428,7 @@ export const Route = createFileRoute('/posts')({ The `abortController` property of the `loader` function is an [AbortController](https://developer.mozilla.org/en-US/docs/Web/API/AbortController). Its signal is cancelled when the route is unloaded or when the `loader` call becomes outdated. This is useful for cancelling network requests when the route is unloaded or when the route's params change. Here is an example using it with a fetch call: ```tsx -// routes/posts.tsx +// src/routes/posts.tsx export const Route = createFileRoute('/posts')({ loader: ({ abortController }) => fetchPosts({ @@ -413,7 +443,7 @@ export const Route = createFileRoute('/posts')({ The `preload` property of the `loader` function is a boolean which is `true` when the route is being preloaded instead of loaded. Some data loading libraries may handle preloading differently than a standard fetch, so you may want to pass `preload` to your data loading library, or use it to execute the appropriate data loading logic: ```tsx -// routes/posts.tsx +// src/routes/posts.tsx export const Route = createFileRoute('/posts')({ loader: async ({ preload }) => fetchPosts({ @@ -454,7 +484,7 @@ TanStack Router provides a few ways to handle errors that occur during the route The `routeOptions.onError` option is a function that is called when an error occurs during the route loading. ```tsx -// routes/posts.tsx +// src/routes/posts.tsx export const Route = createFileRoute('/posts')({ loader: () => fetchPosts(), onError: ({ error }) => { @@ -469,7 +499,7 @@ export const Route = createFileRoute('/posts')({ The `routeOptions.onCatch` option is a function that is called whenever an error was caught by the router's CatchBoundary. ```tsx -// routes/posts.tsx +// src/routes/posts.tsx export const Route = createFileRoute('/posts')({ onCatch: ({ error, errorInfo }) => { // Log the error @@ -486,7 +516,7 @@ The `routeOptions.errorComponent` option is a component that is rendered when an - `reset` - A function to reset the internal `CatchBoundary` ```tsx -// routes/posts.tsx +// src/routes/posts.tsx export const Route = createFileRoute('/posts')({ loader: () => fetchPosts(), errorComponent: ({ error }) => { @@ -499,7 +529,7 @@ export const Route = createFileRoute('/posts')({ The `reset` function can be used to allow the user to retry rendering the error boundaries normal children: ```tsx -// routes/posts.tsx +// src/routes/posts.tsx export const Route = createFileRoute('/posts')({ loader: () => fetchPosts(), errorComponent: ({ error, reset }) => { @@ -523,7 +553,7 @@ export const Route = createFileRoute('/posts')({ If the error was the result of a route load, you should instead call `router.invalidate()`, which will coordinate both a router reload and an error boundary reset: ```tsx -// routes/posts.tsx +// src/routes/posts.tsx export const Route = createFileRoute('/posts')({ loader: () => fetchPosts(), errorComponent: ({ error, reset }) => { @@ -551,9 +581,7 @@ export const Route = createFileRoute('/posts')({ TanStack Router provides a default `ErrorComponent` that is rendered when an error occurs during the route loading or rendering lifecycle. If you choose to override your routes' error components, it's still wise to always fall back to rendering any uncaught errors with the default `ErrorComponent`: ```tsx -// routes/posts.tsx -import { createFileRoute, ErrorComponent } from '@tanstack/react-router' - +// src/routes/posts.tsx export const Route = createFileRoute('/posts')({ loader: () => fetchPosts(), errorComponent: ({ error }) => { diff --git a/docs/router/framework/react/guide/data-mutations.md b/docs/router/guide/data-mutations.md similarity index 100% rename from docs/router/framework/react/guide/data-mutations.md rename to docs/router/guide/data-mutations.md diff --git a/docs/router/framework/react/guide/deferred-data-loading.md b/docs/router/guide/deferred-data-loading.md similarity index 81% rename from docs/router/framework/react/guide/deferred-data-loading.md rename to docs/router/guide/deferred-data-loading.md index 8ba29da9e32..9956542ebd3 100644 --- a/docs/router/framework/react/guide/deferred-data-loading.md +++ b/docs/router/guide/deferred-data-loading.md @@ -68,12 +68,14 @@ The `Await` component resolves the promise by triggering the nearest suspense bo If the promise is rejected, the `Await` component will throw the serialized error, which can be caught by the nearest error boundary. -[//]: # 'DeferredWithAwaitFinalTip' + + +# React > [!TIP] > In React 19, you can use the `use()` hook instead of `Await` -[//]: # 'DeferredWithAwaitFinalTip' + ## Deferred Data Loading with External libraries @@ -81,6 +83,10 @@ When your strategy for fetching information for the route relies on [External Da So, instead of using `defer` and `Await`, you'll instead want to use the Route's `loader` to kick off the data fetching and then use the library's hooks to access the data in your components. + + +# React + ```tsx // src/routes/posts.$postId.tsx import { createFileRoute } from '@tanstack/react-router' @@ -97,8 +103,32 @@ export const Route = createFileRoute('/posts/$postId')({ }) ``` +# Solid + +```tsx +// src/routes/posts.$postId.tsx +import { createFileRoute } from '@tanstack/solid-router' +import { slowDataOptions, fastDataOptions } from '~/api/query-options' + +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ context: { queryClient } }) => { + // Kick off the fetching of some slower data, but do not await it + queryClient.prefetchQuery(slowDataOptions()) + + // Fetch and await some data that resolves quickly + await queryClient.ensureQueryData(fastDataOptions()) + }, +}) +``` + + + Then in your component, you can use the library's hooks to access the data: + + +# React + ```tsx // src/routes/posts.$postId.tsx import { createFileRoute } from '@tanstack/react-router' @@ -129,11 +159,47 @@ function SlowDataComponent() { } ``` +# Solid + +```tsx +// src/routes/posts.$postId.tsx +import { createFileRoute } from '@tanstack/solid-router' +import { useSuspenseQuery } from '@tanstack/solid-query' +import { slowDataOptions, fastDataOptions } from '~/api/query-options' + +export const Route = createFileRoute('/posts/$postId')({ + // ... + component: PostIdComponent, +}) + +function PostIdComponent() { + const fastData = useSuspenseQuery(fastDataOptions()) + + // do something with fastData + + return ( + Loading...}> + + + ) +} + +function SlowDataComponent() { + const data = useSuspenseQuery(slowDataOptions()) + + return
{data()}
+} +``` + + + ## Caching and Invalidation Streamed promises follow the same lifecycle as the loader data they are associated with. They can even be preloaded! -[//]: # 'SSRContent' + + +# React ## SSR & Streaming Deferred Data @@ -159,4 +225,4 @@ The following is a high-level overview of how deferred data streaming works with - Client - The suspended placeholder promises within `` are resolved with the streamed data/error responses and either render the result or throw the error to the nearest error boundary -[//]: # 'SSRContent' + diff --git a/docs/router/framework/react/guide/document-head-management.md b/docs/router/guide/document-head-management.md similarity index 77% rename from docs/router/framework/react/guide/document-head-management.md rename to docs/router/guide/document-head-management.md index fdf63656fde..cf66544a4af 100644 --- a/docs/router/framework/react/guide/document-head-management.md +++ b/docs/router/guide/document-head-management.md @@ -2,13 +2,13 @@ title: Document Head Management --- -Document head management is the process of managing the head, title, meta, link, and script tags of a document and TanStack Router provides a robust way to manage the document head for full-stack applications that use Start and for single-page applications that use `@tanstack/react-router`. It provides: +Document head management is the process of managing the head, title, meta, link, and script tags of a document and TanStack Router provides a robust way to manage the document head for full-stack applications that use Start and for single-page applications that use TanStack Router. It provides: - Automatic deduping of `title` and `meta` tags - Automatic loading/unloading of tags based on route visibility - A composable way to merge `title` and `meta` tags from nested routes -For full-stack applications that use Start, and even for single-page applications that use `@tanstack/react-router`, managing the document head is a crucial part of any application for the following reasons: +For full-stack applications that use Start, and even for single-page applications that use TanStack Router, managing the document head is a crucial part of any application for the following reasons: - SEO - Social media sharing @@ -70,6 +70,10 @@ It should be **rendered either in the `` tag of your root layout or as hig ### Start/Full-Stack Applications + + +# React + ```tsx import { HeadContent } from '@tanstack/react-router' @@ -87,10 +91,35 @@ export const Route = createRootRoute({ }) ``` +# Solid + +```tsx +import { HeadContent } from '@tanstack/solid-router' + +export const Route = createRootRoute({ + component: () => ( + + + + + + + + + ), +}) +``` + + + ### Single-Page Applications First, remove the `` tag from the index.html if you have set any. +<!-- ::start:framework --> + +# React + ```tsx import { HeadContent } from '@tanstack/react-router' @@ -104,6 +133,23 @@ const rootRoute = createRootRoute({ }) ``` +# Solid + +```tsx +import { HeadContent } from '@tanstack/solid-router' + +const rootRoute = createRootRoute({ + component: () => ( + <> + <HeadContent /> + <Outlet /> + </> + ), +}) +``` + +<!-- ::end:framework --> + ## Managing Body Scripts In addition to scripts that can be rendered in the `<head>` tag, you can also render scripts in the `<body>` tag using the `routeOptions.scripts` property. This is useful for loading scripts (even inline scripts) that require the DOM to be loaded, but before the main entry point of your application (which includes hydration if you're using Start or a full-stack implementation of TanStack Router). @@ -129,9 +175,13 @@ The `<Scripts />` component is **required** to render the body scripts of a docu ### Example +<!-- ::start:framework --> + +# React + ```tsx -import { createFileRoute, Scripts } from '@tanstack/react-router' -export const Router = createFileRoute('/')({ +import { createRootRoute, Scripts } from '@tanstack/react-router' +export const Route = createFileRoute('/')({ component: () => ( <html> <head /> @@ -144,23 +194,33 @@ export const Router = createFileRoute('/')({ }) ``` -```tsx -import { Scripts, createRootRoute } from '@tanstack/react-router' +# Solid -export const Route = createRootRoute({ +```tsx +import { createFileRoute, Scripts } from '@tanstack/solid-router' +export const Route = createRootRoute('/')({ component: () => ( - <> - <Outlet /> - <Scripts /> - </> + <html> + <head /> + <body> + <Outlet /> + <Scripts /> + </body> + </html> ), }) ``` +<!-- ::end:framework --> + ## Inline Scripts with ScriptOnce For scripts that must run before React hydrates (like theme detection), use `ScriptOnce`. This is particularly useful for avoiding flash of unstyled content (FOUC) or theme flicker. +<!-- ::start:framework --> + +# React + ```tsx import { ScriptOnce } from '@tanstack/react-router' @@ -184,6 +244,33 @@ function ThemeProvider({ children }) { } ``` +# Solid + +```tsx +import { ScriptOnce } from '@tanstack/solid-router' + +const themeScript = `(function() { + try { + const theme = localStorage.getItem('theme') || 'auto'; + const resolved = theme === 'auto' + ? (matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') + : theme; + document.documentElement.classList.add(resolved); + } catch (e) {} +})();` + +function ThemeProvider({ children }) { + return ( + <> + <ScriptOnce children={themeScript} /> + {children} + </> + ) +} +``` + +<!-- ::end:framework --> + ### How ScriptOnce Works 1. During SSR, renders a `<script>` tag with the provided code @@ -191,6 +278,10 @@ function ThemeProvider({ children }) { 3. After execution, the script removes itself from the DOM 4. On client-side navigation, nothing is rendered (prevents duplicate execution) +<!-- ::start:framework --> + +# React + ### Preventing Hydration Warnings If your script modifies the DOM before hydration (like adding a class to `<html>`), use `suppressHydrationWarning` to prevent React warnings: @@ -213,6 +304,8 @@ export const Route = createRootRoute({ }) ``` +<!-- ::end:framework --> + ### Common Use Cases - **Theme/dark mode detection** - Apply theme class before hydration to prevent flash diff --git a/docs/router/framework/react/guide/external-data-loading.md b/docs/router/guide/external-data-loading.md similarity index 100% rename from docs/router/framework/react/guide/external-data-loading.md rename to docs/router/guide/external-data-loading.md diff --git a/docs/router/framework/react/guide/history-types.md b/docs/router/guide/history-types.md similarity index 65% rename from docs/router/framework/react/guide/history-types.md rename to docs/router/guide/history-types.md index c65afccd277..9e20736854d 100644 --- a/docs/router/framework/react/guide/history-types.md +++ b/docs/router/guide/history-types.md @@ -12,6 +12,10 @@ If you don't create a history instance, a browser-oriented instance of this API Once you have a history instance, you can pass it to the `Router` constructor: +<!-- ::start:framework --> + +# React + ```ts import { createMemoryHistory, createRouter } from '@tanstack/react-router' @@ -22,6 +26,20 @@ const memoryHistory = createMemoryHistory({ const router = createRouter({ routeTree, history: memoryHistory }) ``` +# Solid + +```ts +import { createMemoryHistory, createRouter } from '@tanstack/solid-router' + +const memoryHistory = createMemoryHistory({ + initialEntries: ['/'], // Pass your initial url +}) + +const router = createRouter({ routeTree, history: memoryHistory }) +``` + +<!-- ::end:framework --> + ## Browser Routing The `createBrowserHistory` is the default history type. It uses the browser's history API to manage the browser history. @@ -30,6 +48,10 @@ The `createBrowserHistory` is the default history type. It uses the browser's hi Hash routing can be helpful if your server doesn't support rewrites to index.html for HTTP requests (among other environments that don't have a server). +<!-- ::start:framework --> + +# React + ```ts import { createHashHistory, createRouter } from '@tanstack/react-router' @@ -38,10 +60,26 @@ const hashHistory = createHashHistory() const router = createRouter({ routeTree, history: hashHistory }) ``` +# Solid + +```ts +import { createHashHistory, createRouter } from '@tanstack/solid-router' + +const hashHistory = createHashHistory() + +const router = createRouter({ routeTree, history: hashHistory }) +``` + +<!-- ::end:framework --> + ## Memory Routing Memory routing is useful in environments that are not a browser or when you do not want components to interact with the URL. +<!-- ::start:framework --> + +# React + ```ts import { createMemoryHistory, createRouter } from '@tanstack/react-router' @@ -52,4 +90,18 @@ const memoryHistory = createMemoryHistory({ const router = createRouter({ routeTree, history: memoryHistory }) ``` -Refer to the [SSR Guide](./ssr.md#server-history) for usage on the server for server-side rendering. +# Solid + +```ts +import { createMemoryHistory, createRouter } from '@tanstack/solid-router' + +const memoryHistory = createMemoryHistory({ + initialEntries: ['/'], // Pass your initial url +}) + +const router = createRouter({ routeTree, history: memoryHistory }) +``` + +<!-- ::end:framework --> + +Refer to the [SSR Guide](./ssr.md#automatic-server-history) for usage on the server for server-side rendering. diff --git a/docs/router/framework/react/guide/internationalization-i18n.md b/docs/router/guide/internationalization-i18n.md similarity index 100% rename from docs/router/framework/react/guide/internationalization-i18n.md rename to docs/router/guide/internationalization-i18n.md diff --git a/docs/router/framework/react/guide/link-options.md b/docs/router/guide/link-options.md similarity index 100% rename from docs/router/framework/react/guide/link-options.md rename to docs/router/guide/link-options.md diff --git a/docs/router/framework/react/guide/navigation-blocking.md b/docs/router/guide/navigation-blocking.md similarity index 63% rename from docs/router/framework/react/guide/navigation-blocking.md rename to docs/router/guide/navigation-blocking.md index 5fa852301e6..aa0dd72d0e6 100644 --- a/docs/router/framework/react/guide/navigation-blocking.md +++ b/docs/router/guide/navigation-blocking.md @@ -33,7 +33,9 @@ There are 2 ways to use navigation blocking: Let's imagine we want to prevent navigation if a form is dirty. We can do this by using the `useBlocker` hook: -[//]: # 'HookBasedBlockingExample' +<!-- ::start:framework --> + +# React ```tsx import { useBlocker } from '@tanstack/react-router' @@ -54,10 +56,35 @@ function MyComponent() { } ``` -[//]: # 'HookBasedBlockingExample' +# Solid + +```tsx +import { useBlocker } from '@tanstack/solid-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = createSignal(false) + + useBlocker({ + shouldBlockFn: () => { + if (!formIsDirty()) return false + + const shouldLeave = confirm('Are you sure you want to leave?') + return !shouldLeave + }, + }) + + // ... +} +``` + +<!-- ::end:framework --> `shouldBlockFn` gives you type safe access to the `current` and `next` location: +<!-- ::start:framework --> + +# React + ```tsx import { useBlocker } from '@tanstack/react-router' @@ -79,9 +106,36 @@ function MyComponent() { } ``` +# Solid + +```tsx +import { useBlocker } from '@tanstack/solid-router' + +function MyComponent() { + // always block going from /foo to /bar/123?hello=world + const { proceed, reset, status } = useBlocker({ + shouldBlockFn: ({ current, next }) => { + return ( + current.routeId === '/foo' && + next.fullPath === '/bar/$id' && + next.params.id === 123 && + next.search.hello === 'world' + ) + }, + withResolver: true, + }) + + // ... +} +``` + +<!-- ::end:framework --> + Note that even if `shouldBlockFn` returns `false`, the browser's `beforeunload` event may still be triggered on page reloads or tab closing. To gain control over this, you can use the `enableBeforeUnload` option to conditionally register the `beforeunload` handler: -[//]: # 'HookBasedBlockingExample' +<!-- ::start:framework --> + +# React ```tsx import { useBlocker } from '@tanstack/react-router' @@ -98,16 +152,37 @@ function MyComponent() { } ``` +# Solid + +```tsx +import { useBlocker } from '@tanstack/solid-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = useState(false) + + useBlocker({ + {/* ... */} + enableBeforeUnload: formIsDirty(), + }) + + // ... +} +``` + +<!-- ::end:framework --> + You can find more information about the `useBlocker` hook in the [API reference](../api/router/useBlockerHook.md). ## Component-based blocking In addition to logical/hook based blocking, you can use the `Block` component to achieve similar results: -[//]: # 'ComponentBasedBlockingExample' +<!-- ::start:framework --> + +# React ```tsx -import { Block } from '@tanstack/react-router' +import { Block } from '@tanstack/solid-router' function MyComponent() { const [formIsDirty, setFormIsDirty] = useState(false) @@ -138,7 +213,36 @@ function MyComponent() { } ``` -[//]: # 'ComponentBasedBlockingExample' +# Solid + +```tsx +import { Block } from '@tanstack/solid-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = createSignal(false) + + return ( + <Block + shouldBlockFn={() => { + if (!formIsDirty()) return false + + const shouldLeave = confirm('Are you sure you want to leave?') + return !shouldLeave + }} + /> + ) + + // OR + + return ( + <Block shouldBlockFn={() => !formIsDirty} withResolver> + {({ status, proceed, reset }) => <>{/* ... */}</>} + </Block> + ) +} +``` + +<!-- ::end:framework --> ## How can I show a custom UI? @@ -150,7 +254,9 @@ However, in some situations, you might want to show a custom UI that is intentio ### Hook/logical-based custom UI with resolver -[//]: # 'HookBasedCustomUIBlockingWithResolverExample' +<!-- ::start:framework --> + +# React ```tsx import { useBlocker } from '@tanstack/react-router' @@ -179,11 +285,42 @@ function MyComponent() { } ``` -[//]: # 'HookBasedCustomUIBlockingWithResolverExample' +# Solid + +```tsx +import { useBlocker } from '@tanstack/solid-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = createSignal(false) + + const { proceed, reset, status } = useBlocker({ + shouldBlockFn: () => formIsDirty(), + withResolver: true, + }) + + // ... + + return ( + <> + {/* ... */} + {status === 'blocked' && ( + <div> + <p>Are you sure you want to leave?</p> + <button onClick={proceed}>Yes</button> + <button onClick={reset}>No</button> + </div> + )} + </> +} +``` + +<!-- ::end:framework --> ### Hook/logical-based custom UI without resolver -[//]: # 'HookBasedCustomUIBlockingWithoutResolverExample' +<!-- ::start:framework --> + +# React ```tsx import { useBlocker } from '@tanstack/react-router' @@ -224,13 +361,56 @@ function MyComponent() { } ``` -[//]: # 'HookBasedCustomUIBlockingWithoutResolverExample' +# Solid + +```tsx +import { useBlocker } from '@tanstack/solid-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = createSignal(false) + + useBlocker({ + shouldBlockFn: () => { + if (!formIsDirty()) { + return false + } + + const shouldBlock = new Promise<boolean>((resolve) => { + // Using a modal manager of your choice + modals.open({ + title: 'Are you sure you want to leave?', + children: ( + <SaveBlocker + confirm={() => { + modals.closeAll() + resolve(false) + }} + reject={() => { + modals.closeAll() + resolve(true) + }} + /> + ), + onClose: () => resolve(true), + }) + }) + return shouldBlock + }, + }) + + // ... +} +``` + +<!-- ::end:framework --> ### Component-based custom UI Similarly to the hook, the `Block` component returns the same state and functions as render props: -[//]: # 'ComponentBasedCustomUIBlockingExample' +<!-- ::start:framework --> + +# React ```tsx import { Block } from '@tanstack/react-router' @@ -257,4 +437,31 @@ function MyComponent() { } ``` -[//]: # 'ComponentBasedCustomUIBlockingExample' +# Solid + +```tsx +import { Block } from '@tanstack/solid-router' + +function MyComponent() { + const [formIsDirty, setFormIsDirty] = createSignal(false) + + return ( + <Block shouldBlockFn={() => formIsDirty()} withResolver> + {({ status, proceed, reset }) => ( + <> + {/* ... */} + {status === 'blocked' && ( + <div> + <p>Are you sure you want to leave?</p> + <button onClick={proceed}>Yes</button> + <button onClick={reset}>No</button> + </div> + )} + </> + )} + </Block> + ) +} +``` + +<!-- ::end:framework --> diff --git a/docs/router/framework/react/guide/navigation.md b/docs/router/guide/navigation.md similarity index 99% rename from docs/router/framework/react/guide/navigation.md rename to docs/router/guide/navigation.md index d40829ada7c..5e7d1d332a8 100644 --- a/docs/router/framework/react/guide/navigation.md +++ b/docs/router/guide/navigation.md @@ -154,12 +154,26 @@ export type LinkProps< Let's make a simple static link! +<!-- ::start:framework --> + +# React + ```tsx import { Link } from '@tanstack/react-router' const link = <Link to="/about">About</Link> ``` +# Solid + +```tsx +import { Link } from '@tanstack/solid-router' + +const link = <Link to="/about">About</Link> +``` + +<!-- ::end:framework --> + ### Dynamic Links Dynamic links are links that have dynamic segments in them. For example, a link to a blog post might look like this: diff --git a/docs/router/framework/react/guide/not-found-errors.md b/docs/router/guide/not-found-errors.md similarity index 99% rename from docs/router/framework/react/guide/not-found-errors.md rename to docs/router/guide/not-found-errors.md index ed156cb99d7..b4c6bc59a63 100644 --- a/docs/router/framework/react/guide/not-found-errors.md +++ b/docs/router/guide/not-found-errors.md @@ -208,8 +208,6 @@ export const Route = createFileRoute('/_pathless/route-a')({ You can also target the root route by passing the exported `rootRouteId` variable to the `notFound` function's `route` property: ```tsx -import { rootRouteId } from '@tanstack/react-router' - export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params: { postId } }) => { const post = await getPost(postId) @@ -274,8 +272,7 @@ The main differences are: To migrate from `NotFoundRoute` to `notFoundComponent`, you'll just need to make a few changes: -```tsx -// router.tsx +```tsx title='src/router.tsx' import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen.' - import { notFoundRoute } from './notFoundRoute' // [!code --] diff --git a/docs/router/framework/react/guide/outlets.md b/docs/router/guide/outlets.md similarity index 73% rename from docs/router/framework/react/guide/outlets.md rename to docs/router/guide/outlets.md index 4d92476bff4..56122b5e7f0 100644 --- a/docs/router/framework/react/guide/outlets.md +++ b/docs/router/guide/outlets.md @@ -13,6 +13,10 @@ The `Outlet` component is used to render the next potentially matching child rou A great example is configuring the root route of your application. Let's give our root route a component that renders a title, then an `<Outlet />` for our top-level routes to render. +<!-- ::start:framework --> + +# React + ```tsx import { createRootRoute, Outlet } from '@tanstack/react-router' @@ -29,3 +33,24 @@ function RootComponent() { ) } ``` + +# Solid + +```tsx +import { createRootRoute, Outlet } from '@tanstack/solid-router' + +export const Route = createRootRoute({ + component: RootComponent, +}) + +function RootComponent() { + return ( + <div> + <h1>My App</h1> + <Outlet /> {/* This is where child routes will render */} + </div> + ) +} +``` + +<!-- ::end:framework --> diff --git a/docs/router/framework/react/guide/parallel-routes.md b/docs/router/guide/parallel-routes.md similarity index 100% rename from docs/router/framework/react/guide/parallel-routes.md rename to docs/router/guide/parallel-routes.md diff --git a/docs/router/framework/react/guide/path-params.md b/docs/router/guide/path-params.md similarity index 59% rename from docs/router/framework/react/guide/path-params.md rename to docs/router/guide/path-params.md index 4ebd477ace2..7755047b13c 100644 --- a/docs/router/framework/react/guide/path-params.md +++ b/docs/router/guide/path-params.md @@ -15,9 +15,11 @@ Because path param routes only match to the next `/`, child routes can be create Let's create a post route file that uses a path param to match the post ID: -- `posts.$postId.tsx` +<!-- ::start:framework --> -```tsx +# React + +```tsx title="src/routes/posts.$postId.tsx" import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/posts/$postId')({ @@ -27,6 +29,20 @@ export const Route = createFileRoute('/posts/$postId')({ }) ``` +# Solid + +```tsx title="src/routes/posts.$postId.tsx" +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/posts/$postId')({ + loader: async ({ params }) => { + return fetchPost(params.postId) + }, +}) +``` + +<!-- ::end:framework --> + ## Path Params can be used by child routes Once a path param has been parsed, it is available to all child routes. This means that if we define a child route to our `postRoute`, we can use the `postId` variable from the URL in the child route's path! @@ -35,7 +51,7 @@ Once a path param has been parsed, it is available to all child routes. This mea Path params are passed to the loader as a `params` object. The keys of this object are the names of the path params, and the values are the values that were parsed out of the actual URL path. For example, if we were to visit the `/blog/123` URL, the `params` object would be `{ postId: '123' }`: -```tsx +```tsx title="src/routes/posts.$postId.tsx" export const Route = createFileRoute('/posts/$postId')({ loader: async ({ params }) => { return fetchPost(params.postId) @@ -45,7 +61,7 @@ export const Route = createFileRoute('/posts/$postId')({ The `params` object is also passed to the `beforeLoad` option: -```tsx +```tsx title="src/routes/posts.$postId.tsx" export const Route = createFileRoute('/posts/$postId')({ beforeLoad: async ({ params }) => { // do something with params.postId @@ -57,7 +73,11 @@ export const Route = createFileRoute('/posts/$postId')({ If we add a component to our `postRoute`, we can access the `postId` variable from the URL by using the route's `useParams` hook: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/posts.$postId.tsx" export const Route = createFileRoute('/posts/$postId')({ component: PostComponent, }) @@ -68,19 +88,49 @@ function PostComponent() { } ``` +# Solid + +```tsx title="src/routes/posts.$postId.tsx" +export const Route = createFileRoute('/posts/$postId')({ + component: PostComponent, +}) + +function PostComponent() { + const params = Route.useParams() + return <div>Post {params().postId}</div> +} +``` + +<!-- ::end:framework --> + > 🧠 Quick tip: If your component is code-split, you can use the [getRouteApi function](./code-splitting.md#manually-accessing-route-apis-in-other-files-with-the-getrouteapi-helper) to avoid having to import the `Route` configuration to get access to the typed `useParams()` hook. ## Path Params outside of Routes You can also use the globally exported `useParams` hook to access any parsed path params from any component in your app. You'll need to pass the `strict: false` option to `useParams`, denoting that you want to access the params from an ambiguous location: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/components/PostComponent.tsx" function PostComponent() { const { postId } = useParams({ strict: false }) return <div>Post {postId}</div> } ``` +# Solid + +```tsx title="src/components/PostComponent.tsx" +function PostComponent() { + const params = useParams({ strict: false }) + return <div>Post {params().postId}</div> +} +``` + +<!-- ::end:framework --> + ## Navigating with Path Params When navigating to a route with path params, TypeScript will require you to pass the params either as an object or as a function that returns an object of params. @@ -121,8 +171,11 @@ When using either prefixes or suffixes, you can define them by wrapping the path Prefixes are defined by placing the prefix text outside the curly braces before the variable name. For example, if you want to match a URL that starts with `post-` followed by a post ID, you can define it like this: -```tsx -// src/routes/posts/post-{$postId}.tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/posts/post-{$postId}.tsx" export const Route = createFileRoute('/posts/post-{$postId}')({ component: PostComponent, }) @@ -134,10 +187,29 @@ function PostComponent() { } ``` +# Solid + +```tsx title="src/routes/posts/post-{$postId}.tsx" +export const Route = createFileRoute('/posts/post-{$postId}')({ + component: PostComponent, +}) + +function PostComponent() { + const params = Route.useParams() + // postId will be the value after 'post-' + return <div>Post ID: {params().postId}</div> +} +``` + +<!-- ::end:framework --> + You can even combines prefixes with wildcard routes to create more complex patterns: -```tsx -// src/routes/on-disk/storage-{$} +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/on-disk/storage-{$postId}/$.tsx" export const Route = createFileRoute('/on-disk/storage-{$postId}/$')({ component: StorageComponent, }) @@ -150,12 +222,32 @@ function StorageComponent() { } ``` +# Solid + +```tsx title="src/routes/on-disk/storage-{$postId}/$.tsx" +export const Route = createFileRoute('/on-disk/storage-{$postId}/$')({ + component: StorageComponent, +}) + +function StorageComponent() { + const params = Route.useParams() + // _splat, will be value after 'storage-' + // i.e. my-drive/documents/foo.txt + return <div>Storage Location: /{params()._splat}</div> +} +``` + +<!-- ::end:framework --> + ### Defining Suffixes Suffixes are defined by placing the suffix text outside the curly braces after the variable name. For example, if you want to match a URL a filename that ends with `txt`, you can define it like this: -```tsx -// src/routes/files/{$fileName}txt +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/files/{$fileName}.txt" export const Route = createFileRoute('/files/{$fileName}.txt')({ component: FileComponent, }) @@ -167,10 +259,29 @@ function FileComponent() { } ``` +# Solid + +```tsx title="src/routes/files/{$fileName}.txt" +export const Route = createFileRoute('/files/{$fileName}.txt')({ + component: FileComponent, +}) + +function FileComponent() { + const params = Route.useParams() + // fileName will be the value before 'txt' + return <div>File Name: {params().fileName}</div> +} +``` + +<!-- ::end:framework --> + You can also combine suffixes with wildcards for more complex routing patterns: -```tsx -// src/routes/files/{$}[.]txt +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/files/{$}[.]txt" export const Route = createFileRoute('/files/{$fileName}[.]txt')({ component: FileComponent, }) @@ -182,12 +293,31 @@ function FileComponent() { } ``` +# Solid + +```tsx title="src/routes/files/{$}[.]txt" +export const Route = createFileRoute('/files/{$fileName}[.]txt')({ + component: FileComponent, +}) + +function FileComponent() { + const params = Route.useParams() + // _splat will be the value before '.txt' + return <div>File Splat: {params()._splat}</div> +} +``` + +<!-- ::end:framework --> + ### Combining Prefixes and Suffixes You can combine both prefixes and suffixes to create very specific routing patterns. For example, if you want to match a URL that starts with `user-` and ends with `.json`, you can define it like this: -```tsx -// src/routes/users/user-{$userId}.json +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/users/user-{$userId}.json" export const Route = createFileRoute('/users/user-{$userId}.json')({ component: UserComponent, }) @@ -199,6 +329,22 @@ function UserComponent() { } ``` +# Solid + +```tsx title="src/routes/users/user-{$userId}.json" +export const Route = createFileRoute('/users/user-{$userId}.json')({ + component: UserComponent, +}) + +function UserComponent() { + const params = Route.useParams() + // userId will be the value between 'user-' and '.json' + return <div>User ID: {params().userId}</div> +} +``` + +<!-- ::end:framework --> + Similar to the previous examples, you can also use wildcards with prefixes and suffixes. Go wild! ## Optional Path Parameters @@ -243,7 +389,11 @@ When an optional parameter is not present in the URL, its value will be `undefin Optional parameters work exactly like regular parameters in your components, but their values may be `undefined`: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/posts/{-$category}.tsx" function PostsComponent() { const { category } = Route.useParams() @@ -251,6 +401,22 @@ function PostsComponent() { } ``` +# Solid + +```tsx title="src/routes/posts/{-$category}.tsx" +function PostsComponent() { + const params = Route.useParams() + + return ( + <div> + {params().category ? `Posts in ${params().category}` : 'All Posts'} + </div> + ) +} +``` + +<!-- ::end:framework --> + ### Optional Parameters in Loaders Optional parameters are available in loaders and may be `undefined`: @@ -285,8 +451,12 @@ export const Route = createFileRoute('/posts/{-$category}')({ Optional parameters support prefix and suffix patterns: -```tsx -// File route: /files/prefix{-$name}.txt +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/files/prefix{-$name}.txt" +// Route: /files/prefix{-$name}.txt // Matches: /files/prefix.txt and /files/prefixdocument.txt export const Route = createFileRoute('/files/prefix{-$name}.txt')({ component: FileComponent, @@ -298,11 +468,32 @@ function FileComponent() { } ``` +# Solid + +```tsx title="src/routes/files/prefix{-$name}.txt" +// Route: /files/prefix{-$name}.txt +// Matches: /files/prefix.txt and /files/prefixdocument.txt +export const Route = createFileRoute('/files/prefix{-$name}.txt')({ + component: FileComponent, +}) + +function FileComponent() { + const params = Route.useParams() + return <div>File: {params().name || 'default'}</div> +} +``` + +<!-- ::end:framework --> + #### All Optional Parameters You can create routes where all parameters are optional: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/{-$year}/{-$month}/{-$day}.tsx" // Route: /{-$year}/{-$month}/{-$day} // Matches: /, /2023, /2023/12, /2023/12/25 export const Route = createFileRoute('/{-$year}/{-$month}/{-$day}')({ @@ -329,11 +520,46 @@ function DateComponent() { } ``` +# Solid + +```tsx title="src/routes/{-$year}/{-$month}/{-$day}.tsx" +// Route: /{-$year}/{-$month}/{-$day} +// Matches: /, /2023, /2023/12, /2023/12/25 +export const Route = createFileRoute('/{-$year}/{-$month}/{-$day}')({ + component: DateComponent, +}) + +function DateComponent() { + const params = Route.useParams() + + if (!params().year) return <div>Select a year</div> + if (!params().month) return <div>Year: {params().year}</div> + if (!params().day) + return ( + <div> + Month: {params().year}/{params().month} + </div> + ) + + return ( + <div> + Date: {params().year}/{params().month}/{params().day} + </div> + ) +} +``` + +<!-- ::end:framework --> + #### Optional Parameters with Wildcards Optional parameters can be combined with wildcards for complex routing patterns: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/docs/{-$version}/$.tsx" // Route: /docs/{-$version}/$ // Matches: /docs/extra/path, /docs/v2/extra/path export const Route = createFileRoute('/docs/{-$version}/$')({ @@ -353,6 +579,29 @@ function DocsComponent() { } ``` +# Solid + +```tsx title="src/routes/docs/{-$version}/$.tsx" +// Route: /docs/{-$version}/$ +// Matches: /docs/extra/path, /docs/v2/extra/path +export const Route = createFileRoute('/docs/{-$version}/$')({ + component: DocsComponent, +}) + +function DocsComponent() { + const params = Route.useParams() + + return ( + <div> + Version: {params().version || 'latest'} + Path: {params()._splat} + </div> + ) +} +``` + +<!-- ::end:framework --> + ### Navigating with Optional Parameters When navigating to routes with optional parameters, you have fine-grained control over which parameters to include: @@ -387,7 +636,11 @@ function Navigation() { TypeScript provides full type safety for optional parameters: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/posts/{-$category}.tsx" function PostsComponent() { // TypeScript knows category might be undefined const { category } = Route.useParams() // category: string | undefined @@ -414,6 +667,37 @@ function PostsComponent() { </Link> ``` +# Solid + +```tsx title="src/routes/posts/{-$category}.tsx" +function PostsComponent() { + // TypeScript knows category might be undefined + const params = Route.useParams() // category: string | undefined + + // Safe navigation + const categoryUpper = params().category?.toUpperCase() + + return <div>{categoryUpper || 'All Categories'}</div> +} + +// Navigation is type-safe and flexible +<Link + to="/posts/{-$category}" + params={{ category: 'tech' }} // βœ… Valid - string +> + Tech Posts +</Link> + +<Link + to="/posts/{-$category}" + params={{ category: 123 }} // βœ… Valid - number (auto-stringified) +> + Category 123 +</Link> +``` + +<!-- ::end:framework --> + ## Internationalization (i18n) with Optional Path Parameters Optional path parameters are excellent for implementing internationalization (i18n) routing patterns. You can use prefix patterns to handle multiple languages while maintaining clean, SEO-friendly URLs. @@ -422,7 +706,11 @@ Optional path parameters are excellent for implementing internationalization (i1 Use optional language prefixes to support URLs like `/en/about`, `/fr/about`, or just `/about` (default language): -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/{-$locale}/about.tsx" // Route: /{-$locale}/about export const Route = createFileRoute('/{-$locale}/about')({ component: AboutComponent, @@ -453,6 +741,41 @@ function AboutComponent() { } ``` +# Solid + +```tsx title="src/routes/{-$locale}/about.tsx" +// Route: /{-$locale}/about +export const Route = createFileRoute('/{-$locale}/about')({ + component: AboutComponent, +}) + +function AboutComponent() { + const params = Route.useParams() + const currentLocale = params().locale || 'en' // Default to English + + const content = { + en: { title: 'About Us', description: 'Learn more about our company.' }, + fr: { + title: 'Γ€ Propos', + description: 'En savoir plus sur notre entreprise.', + }, + es: { + title: 'Acerca de', + description: 'Conoce mΓ‘s sobre nuestra empresa.', + }, + } + + return ( + <div> + <h1>{content[currentLocale]?.title}</h1> + <p>{content[currentLocale]?.description}</p> + </div> + ) +} +``` + +<!-- ::end:framework --> + This pattern matches: - `/about` (default locale) @@ -464,7 +787,11 @@ This pattern matches: Combine optional parameters for more sophisticated i18n routing: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/{-$locale}/blog/{-$category}/$slug.tsx" // Route: /{-$locale}/blog/{-$category}/$slug export const Route = createFileRoute('/{-$locale}/blog/{-$category}/$slug')({ beforeLoad: async ({ params }) => { @@ -504,6 +831,51 @@ function BlogPostComponent() { } ``` +# Solid + +```tsx title="src/routes/{-$locale}/blog/{-$category}/$slug.tsx" +// Route: /{-$locale}/blog/{-$category}/$slug +export const Route = createFileRoute('/{-$locale}/blog/{-$category}/$slug')({ + beforeLoad: async ({ params }) => { + const locale = params.locale || 'en' + const category = params.category + + // Validate locale and category + const validLocales = ['en', 'fr', 'es', 'de'] + if (locale && !validLocales.includes(locale)) { + throw new Error('Invalid locale') + } + + return { locale, category } + }, + loader: async ({ params, context }) => { + const { locale } = context + const { slug, category } = params + + return fetchBlogPost({ slug, category, locale }) + }, + component: BlogPostComponent, +}) + +function BlogPostComponent() { + const params = Route.useParams() + const data = Route.useLoaderData() + + return ( + <article> + <h1>{data.title}</h1> + <p> + Category: {params().category || 'All'} | Language:{' '} + {params().locale || 'en'} + </p> + <div>{data.content}</div> + </article> + ) +} +``` + +<!-- ::end:framework --> + This supports URLs like: - `/blog/tech/my-post` (default locale, tech category) @@ -515,7 +887,11 @@ This supports URLs like: Create language switchers using optional i18n parameters with function-style params: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/components/LanguageSwitcher.tsx" function LanguageSwitcher() { const currentParams = useParams({ strict: false }) @@ -545,8 +921,45 @@ function LanguageSwitcher() { } ``` +# Solid + +```tsx title="src/components/LanguageSwitcher.tsx" +function LanguageSwitcher() { + const currentParams = useParams({ strict: false }) + + const languages = [ + { code: 'en', name: 'English' }, + { code: 'fr', name: 'FranΓ§ais' }, + { code: 'es', name: 'EspaΓ±ol' }, + ] + + return ( + <div class="language-switcher"> + {languages.map(({ code, name }) => ( + <Link + to="/{-$locale}/blog/{-$category}/$slug" + params={(prev) => ({ + ...prev, + locale: code === 'en' ? undefined : code, // Remove 'en' for clean URLs + })} + class={currentParams().locale === code ? 'active' : ''} + > + {name} + </Link> + ))} + </div> + ) +} +``` + +<!-- ::end:framework --> + You can also create more sophisticated language switching logic: +<!-- ::start:framework --> + +# React + ```tsx function AdvancedLanguageSwitcher() { const currentParams = useParams({ strict: false }) @@ -594,10 +1007,65 @@ function AdvancedLanguageSwitcher() { } ``` +# Solid + +```tsx +function AdvancedLanguageSwitcher() { + const currentParams = useParams({ strict: false }) + + const handleLanguageChange = (newLocale: string) => { + return (prev: any) => { + // Preserve all existing params but update locale + const updatedParams = { ...prev } + + if (newLocale === 'en') { + // Remove locale for clean English URLs + delete updatedParams.locale + } else { + updatedParams.locale = newLocale + } + + return updatedParams + } + } + + return ( + <div class="language-switcher"> + <Link + to="/{-$locale}/blog/{-$category}/$slug" + params={handleLanguageChange('fr')} + > + FranΓ§ais + </Link> + + <Link + to="/{-$locale}/blog/{-$category}/$slug" + params={handleLanguageChange('es')} + > + EspaΓ±ol + </Link> + + <Link + to="/{-$locale}/blog/{-$category}/$slug" + params={handleLanguageChange('en')} + > + English + </Link> + </div> + ) +} +``` + +<!-- ::end:framework --> + ### Advanced i18n with Optional Parameters Organize i18n routes using optional parameters for flexible locale handling: +<!-- ::start:framework --> + +# React + ```tsx // Route structure: // routes/ @@ -631,11 +1099,101 @@ export const Route = createFileRoute('/{-$locale}/about')({ }) ``` +# Solid + +```tsx +// Route structure: +// routes/ +// {-$locale}/ +// index.tsx // /, /en, /fr +// about.tsx // /about, /en/about, /fr/about +// blog/ +// index.tsx // /blog, /en/blog, /fr/blog +// $slug.tsx // /blog/post, /en/blog/post, /fr/blog/post + +// routes/{-$locale}/index.tsx +export const Route = createFileRoute('/{-$locale}/')({ + component: HomeComponent, +}) + +function HomeComponent() { + const params = Route.useParams() + const isRTL = ['ar', 'he', 'fa'].includes(params().locale || '') + + return ( + <div dir={isRTL ? 'rtl' : 'ltr'}> + <h1>Welcome ({params().locale || 'en'})</h1> + {/* Localized content */} + </div> + ) +} + +// routes/{-$locale}/about.tsx +export const Route = createFileRoute('/{-$locale}/about')({ + component: AboutComponent, +}) +``` + +<!-- ::end:framework --> + ### SEO and Canonical URLs Handle SEO for i18n routes properly: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/{-$locale}/products/$id.tsx" +export const Route = createFileRoute('/{-$locale}/products/$id')({ + component: ProductComponent, + head: ({ params, loaderData }) => { + const locale = params.locale || 'en' + const product = loaderData + + return { + title: product.title[locale] || product.title.en, + meta: [ + { + name: 'description', + content: product.description[locale] || product.description.en, + }, + { + property: 'og:locale', + content: locale, + }, + ], + links: [ + // Canonical URL (always use default locale format) + { + rel: 'canonical', + href: `https://example.com/products/${params.id}`, + }, + // Alternate language versions + { + rel: 'alternate', + hreflang: 'en', + href: `https://example.com/products/${params.id}`, + }, + { + rel: 'alternate', + hreflang: 'fr', + href: `https://example.com/fr/products/${params.id}`, + }, + { + rel: 'alternate', + hreflang: 'es', + href: `https://example.com/es/products/${params.id}`, + }, + ], + } + }, +}) +``` + +# Solid + +```tsx title="src/routes/{-$locale}/products/$id.tsx" export const Route = createFileRoute('/{-$locale}/products/$id')({ component: ProductComponent, head: ({ params, loaderData }) => { @@ -682,11 +1240,17 @@ export const Route = createFileRoute('/{-$locale}/products/$id')({ }) ``` +<!-- ::end:framework --> + ### Type Safety for i18n Ensure type safety for your i18n implementations: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/{-$locale}/shop/{-$category}.tsx" // Define supported locales type Locale = 'en' | 'fr' | 'es' | 'de' @@ -736,6 +1300,60 @@ function ShopComponent() { } ``` +# Solid + +```tsx title="src/routes/{-$locale}/shop/{-$category}.tsx" +// Define supported locales +type Locale = 'en' | 'fr' | 'es' | 'de' + +// Type-safe locale validation +function validateLocale(locale: string | undefined): locale is Locale { + return ['en', 'fr', 'es', 'de'].includes(locale as Locale) +} + +export const Route = createFileRoute('/{-$locale}/shop/{-$category}')({ + beforeLoad: async ({ params }) => { + const { locale } = params + + // Type-safe locale validation + if (locale && !validateLocale(locale)) { + throw redirect({ + to: '/shop/{-$category}', + params: { category: params.category }, + }) + } + + return { + locale: (locale as Locale) || 'en', + isDefaultLocale: !locale || locale === 'en', + } + }, + component: ShopComponent, +}) + +function ShopComponent() { + const params = Route.useParams() + const routeContext = Route.useRouteContext() + + // TypeScript knows locale is Locale | undefined + // and we have validated it in beforeLoad + + return ( + <div> + <h1>Shop {params().category ? `- ${params().category}` : ''}</h1> + <p>Language: {params().locale || 'en'}</p> + {!routeContext().isDefaultLocale && ( + <Link to="/shop/{-$category}" params={{ category: params().category }}> + View in English + </Link> + )} + </div> + ) +} +``` + +<!-- ::end:framework --> + Optional path parameters provide a powerful and flexible foundation for implementing internationalization in your TanStack Router applications. Whether you prefer prefix-based or combined approaches, you can create clean, SEO-friendly URLs while maintaining excellent developer experience and type safety. ## Allowed Characters diff --git a/docs/router/framework/react/guide/preloading.md b/docs/router/guide/preloading.md similarity index 76% rename from docs/router/framework/react/guide/preloading.md rename to docs/router/guide/preloading.md index 5695245c750..453294ab6e0 100644 --- a/docs/router/framework/react/guide/preloading.md +++ b/docs/router/guide/preloading.md @@ -27,6 +27,10 @@ If you need more control over preloading, caching and/or garbage collection of p The simplest way to preload routes for your application is to set the `defaultPreload` option to `intent` for your entire router: +<!-- ::start:framework --> + +# React + ```tsx import { createRouter } from '@tanstack/react-router' @@ -36,12 +40,29 @@ const router = createRouter({ }) ``` +# Solid + +```tsx +import { createRouter } from '@tanstack/solid-router' + +const router = createRouter({ + // ... + defaultPreload: 'intent', +}) +``` + +<!-- ::end:framework --> + This will turn on `intent` preloading by default for all `<Link>` components in your application. You can also set the `preload` prop on individual `<Link>` components to override the default behavior. ## Preload Delay By default, preloading will start after **50ms** of the user hovering or touching a `<Link>` component. You can change this delay by setting the `defaultPreloadDelay` option on your router: +<!-- ::start:framework --> + +# React + ```tsx import { createRouter } from '@tanstack/react-router' @@ -51,6 +72,19 @@ const router = createRouter({ }) ``` +# Solid + +```tsx +import { createRouter } from '@tanstack/solid-router' + +const router = createRouter({ + // ... + defaultPreloadDelay: 100, +}) +``` + +<!-- ::end:framework --> + You can also set the `preloadDelay` prop on individual `<Link>` components to override the default behavior on a per-link basis. ## Built-in Preloading & `preloadStaleTime` @@ -59,6 +93,10 @@ If you're using the built-in loaders, you can control how long preloaded data is To change this, you can set the `defaultPreloadStaleTime` option on your router: +<!-- ::start:framework --> + +# React + ```tsx import { createRouter } from '@tanstack/react-router' @@ -68,6 +106,19 @@ const router = createRouter({ }) ``` +# Solid + +```tsx +import { createRouter } from '@tanstack/solid-router' + +const router = createRouter({ + // ... + defaultPreloadStaleTime: 10_000, +}) +``` + +<!-- ::end:framework --> + Or, you can use the `routeOptions.preloadStaleTime` option on individual routes: ```tsx @@ -87,6 +138,10 @@ To customize the preloading behavior in TanStack Router and fully leverage your For example: +<!-- ::start:framework --> + +# React + ```tsx import { createRouter } from '@tanstack/react-router' @@ -96,12 +151,29 @@ const router = createRouter({ }) ``` +# Solid + +```tsx +import { createRouter } from '@tanstack/solid-router' + +const router = createRouter({ + // ... + defaultPreloadStaleTime: 0, +}) +``` + +<!-- ::end:framework --> + This would then allow you, for instance, to use an option like React Query's `staleTime` to control the freshness of your preloads. ## Preloading Manually If you need to manually preload a route, you can use the router's `preloadRoute` method. It accepts a standard TanStack `NavigateOptions` object and returns a promise that resolves when the route is preloaded. +<!-- ::start:framework --> + +# React + ```tsx function Component() { const router = useRouter() @@ -125,8 +197,39 @@ function Component() { } ``` +# Solid + +```tsx +function Component() { + const router = useRouter() + + createEffect(() => { + async function preload() { + try { + const matches = await router.preloadRoute({ + to: postRoute, + params: { id: 1 }, + }) + } catch (err) { + // Failed to preload route + } + } + + preload() + }) + + return <div /> +} +``` + +<!-- ::end:framework --> + If you need to preload only the JS chunk of a route, you can use the router's `loadRouteChunk` method. It accepts a route object and returns a promise that resolves when the route chunk is loaded. +<!-- ::start:framework --> + +# React + ```tsx function Component() { const router = useRouter() @@ -151,3 +254,32 @@ function Component() { return <div /> } ``` + +# Solid + +```tsx +function Component() { + const router = useRouter() + + createEffect(() => { + async function preloadRouteChunks() { + try { + const postsRoute = router.routesByPath['/posts'] + await Promise.all([ + router.loadRouteChunk(router.routesByPath['/']), + router.loadRouteChunk(postsRoute), + router.loadRouteChunk(postsRoute.parentRoute), + ]) + } catch (err) { + // Failed to preload route chunk + } + } + + preloadRouteChunks() + }) + + return <div /> +} +``` + +<!-- ::end:framework --> diff --git a/docs/router/framework/react/guide/render-optimizations.md b/docs/router/guide/render-optimizations.md similarity index 100% rename from docs/router/framework/react/guide/render-optimizations.md rename to docs/router/guide/render-optimizations.md diff --git a/docs/router/framework/react/guide/route-masking.md b/docs/router/guide/route-masking.md similarity index 94% rename from docs/router/framework/react/guide/route-masking.md rename to docs/router/guide/route-masking.md index b31647db810..a734b75e17a 100644 --- a/docs/router/framework/react/guide/route-masking.md +++ b/docs/router/guide/route-masking.md @@ -93,7 +93,9 @@ function onOpenPhoto() { In addition to the imperative API, you can also use the Router's `routeMasks` option to declaratively mask routes. Instead of needing to pass the `mask` option to every `<Link>` or `navigate()` call, you can instead create a route mask on the Router to mask routes that match a certain pattern. Here's an example of the same route mask from above, but using the `routeMasks` option instead: -// Use the following for the example below +<!-- ::start:framework --> + +# React ```tsx import { createRouteMask } from '@tanstack/react-router' @@ -113,6 +115,28 @@ const router = createRouter({ }) ``` +# Solid + +```tsx +import { createRouteMask } from '@tanstack/solid-router' + +const photoModalToPhotoMask = createRouteMask({ + routeTree, + from: '/photos/$photoId/modal', + to: '/photos/$photoId', + params: (prev) => ({ + photoId: prev.photoId, + }), +}) + +const router = createRouter({ + routeTree, + routeMasks: [photoModalToPhotoMask], +}) +``` + +<!-- ::end:framework --> + When creating a route mask, you'll need to pass 1 argument with at least: - `routeTree` - The route tree that the route mask will be applied to diff --git a/docs/router/framework/react/guide/router-context.md b/docs/router/guide/router-context.md similarity index 72% rename from docs/router/framework/react/guide/router-context.md rename to docs/router/guide/router-context.md index 9e02803de77..5fc4001010f 100644 --- a/docs/router/framework/react/guide/router-context.md +++ b/docs/router/guide/router-context.md @@ -17,6 +17,10 @@ These are just suggested uses of the router context. You can use it for whatever Like everything else, the root router context is strictly typed. This type can be augmented via any route's `beforeLoad` option as it is merged down the route match tree. To constrain the type of the root router context, you must use the `createRootRouteWithContext<YourContextTypeHere>()(routeOptions)` function to create a new router context instead of the `createRootRoute()` function to create your root route. Here's an example: +<!-- ::start:framework --> + +# React + ```tsx import { createRootRouteWithContext, @@ -42,6 +46,35 @@ const router = createRouter({ }) ``` +# Solid + +```tsx +import { + createRootRouteWithContext, + createRouter, +} from '@tanstack/solid-router' + +interface MyRouterContext { + user: User +} + +// Use the routerContext to create your root route +const rootRoute = createRootRouteWithContext<MyRouterContext>()({ + component: App, +}) + +const routeTree = rootRoute.addChildren([ + // ... +]) + +// Use the routerContext to create your router +const router = createRouter({ + routeTree, +}) +``` + +<!-- ::end:framework --> + > [!TIP] > `MyRouterContext` only needs to contain content that will be passed directly to `createRouter` below. All other context added in `beforeLoad` will be inferred. @@ -52,6 +85,10 @@ The router context is passed to the router at instantiation time. You can pass t > [!TIP] > If your context has any required properties, you will see a TypeScript error if you don't pass them in the initial router context. If all of your context properties are optional, you will not see a TypeScript error and passing the context will be optional. If you don't pass a router context, it defaults to `{}`. +<!-- ::start:framework --> + +# React + ```tsx import { createRouter } from '@tanstack/react-router' @@ -67,10 +104,33 @@ const router = createRouter({ }) ``` +# Solid + +```tsx +import { createRouter } from '@tanstack/solid-router' + +// Use the routerContext you created to create your router +const router = createRouter({ + routeTree, + context: { + user: { + id: '123', + name: 'John Doe', + }, + }, +}) +``` + +<!-- ::end:framework --> + ### Invalidating the Router Context If you need to invalidate the context state you are passing into the router, you can call the `invalidate` method to tell the router to recompute the context. This is useful when you need to update the context state and have the router recompute the context for all routes. +<!-- ::start:framework --> + +# React + ```tsx function useAuth() { const router = useRouter() @@ -89,6 +149,28 @@ function useAuth() { } ``` +# Solid + +```tsx +function useAuth() { + const router = useRouter() + const [user, setUser] = createSignal<User | null>(null) + + createEffect(() => { + const unsubscribe = auth.onAuthStateChanged((user) => { + setUser(user) + router.invalidate() + }) + + return unsubscribe + }, []) + + return user() +} +``` + +<!-- ::end:framework --> + ## Using the Router Context Once you have defined the router context type, you can use it in your route definitions: @@ -133,6 +215,10 @@ export const Route = createFileRoute('/todos')({ ### How about an external data fetching library? +<!-- ::start:framework --> + +# React + ```tsx import { createRootRouteWithContext, @@ -157,6 +243,34 @@ const router = createRouter({ }) ``` +# Solid + +```tsx +import { + createRootRouteWithContext, + createRouter, +} from '@tanstack/solid-router' + +interface MyRouterContext { + queryClient: QueryClient +} + +const rootRoute = createRootRouteWithContext<MyRouterContext>()({ + component: App, +}) + +const queryClient = new QueryClient() + +const router = createRouter({ + routeTree: rootRoute, + context: { + queryClient, + }, +}) +``` + +<!-- ::end:framework --> + Then, in your route: ```tsx @@ -172,6 +286,10 @@ export const Route = createFileRoute('/todos')({ }) ``` +<!-- ::start:framework --> + +# React + ## How about using React Context/Hooks? When trying to use React Context or Hooks in your route's `beforeLoad` or `loader` functions, it's important to remember React's [Rules of Hooks](https://react.dev/reference/rules/rules-of-hooks). You can't use hooks in a non-React function, so you can't use hooks in your `beforeLoad` or `loader` functions. @@ -180,9 +298,9 @@ So, how do we use React Context or Hooks in our route's `beforeLoad` or `loader` Let's look at the setup for an example, where we pass down a `useNetworkStrength` hook to our route's `loader` function: -- `src/routes/__root.tsx` +<!-- ::start:tabs variant="files" --> -```tsx +```tsx title="src/routes/__root.tsx" // First, make sure the context for the root route is typed import { createRootRouteWithContext } from '@tanstack/react-router' import { useNetworkStrength } from '@/hooks/useNetworkStrength' @@ -196,11 +314,13 @@ export const Route = createRootRouteWithContext<MyRouterContext>()({ }) ``` +<!-- ::end:tabs --> + In this example, we'd instantiate the hook before rendering the router using the `<RouterProvider />`. This way, the hook would be called in React-land, therefore adhering to the Rules of Hooks. -- `src/router.tsx` +<!-- ::start:tabs variant="files" --> -```tsx +```tsx title="src/router.tsx" import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' @@ -213,9 +333,13 @@ export const router = createRouter({ }) ``` -- `src/main.tsx` +<!-- ::end:tabs --> -```tsx +Then, we can call the `useNetworkStrength` hook in our `App` component and pass the returned value into the router context via the `<RouterProvider />`: + +<!-- ::start:tabs variant="files" --> + +```tsx title="src/main.tsx" import { RouterProvider } from '@tanstack/react-router' import { router } from './router' @@ -230,11 +354,13 @@ function App() { // ... ``` +<!-- ::end:tabs --> + So, now in our route's `loader` function, we can access the `networkStrength` hook from the router context: -- `src/routes/posts.tsx` +<!-- ::start:tabs variant="files" --> -```tsx +```tsx title="src/routes/posts.tsx" import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/posts')({ @@ -247,13 +373,21 @@ export const Route = createFileRoute('/posts')({ }) ``` +<!-- ::end:tabs --> + +<!-- ::end:framework --> + ## Modifying the Router Context The router context is passed down the route tree and is merged at each route. This means that you can modify the context at each route and the modifications will be available to all child routes. Here's an example: -- `src/routes/__root.tsx` +<!-- ::start:framework --> -```tsx +# React + +<!-- ::start:tabs variant="files" --> + +```tsx title="src/routes/__root.tsx" import { createRootRouteWithContext } from '@tanstack/react-router' interface MyRouterContext { @@ -265,9 +399,11 @@ export const Route = createRootRouteWithContext<MyRouterContext>()({ }) ``` -- `src/router.tsx` +<!-- ::end:tabs --> -```tsx +<!-- ::start:tabs variant="files" --> + +```tsx title="src/router.tsx" import { createRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' @@ -280,9 +416,11 @@ const router = createRouter({ }) ``` -- `src/routes/todos.tsx` +<!-- ::end:tabs --> -```tsx +<!-- ::start:tabs variant="files" --> + +```tsx title="src/routes/todos.tsx" import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/todos')({ @@ -299,12 +437,71 @@ export const Route = createFileRoute('/todos')({ }) ``` +<!-- ::end:tabs --> + +# Solid + +<!-- ::start:tabs variant="files" --> + +```tsx title="src/routes/__root.tsx" +import { createRootRouteWithContext } from '@tanstack/solid-router' + +interface MyRouterContext { + foo: boolean +} + +export const Route = createRootRouteWithContext<MyRouterContext>()({ + component: App, +}) +``` + +<!-- ::end:tabs --> + +<!-- ::start:tabs variant="files" --> + +```tsx title="src/router.tsx" +import { createRouter } from '@tanstack/solid-router' + +import { routeTree } from './routeTree.gen' + +const router = createRouter({ + routeTree, + context: { + foo: true, + }, +}) +``` + +<!-- ::end:tabs --> + +<!-- ::start:tabs variant="files" --> + +```tsx title="src/routes/todos.tsx" +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/todos')({ + component: Todos, + beforeLoad: () => { + return { + bar: true, + } + }, + loader: ({ context }) => { + context.foo // true + context.bar // true + }, +}) +``` + +<!-- ::end:tabs --> + +<!-- ::end:framework --> + ## Processing Accumulated Route Context Context, especially the isolated route `context` objects, make it trivial to accumulate and process the route context objects for all matched routes. Here's an example where we use all of the matched route contexts to generate a breadcrumb trail: -```tsx -// src/routes/__root.tsx +```tsx title="src/routes/__root.tsx" export const Route = createRootRoute({ component: () => { const matches = useRouterState({ select: (s) => s.matches }) @@ -325,8 +522,7 @@ export const Route = createRootRoute({ Using that same route context, we could also generate a title tag for our page's `<head>`: -```tsx -// src/routes/__root.tsx +```tsx title="src/routes/__root.tsx" export const Route = createRootRoute({ component: () => { const matches = useRouterState({ select: (s) => s.matches }) diff --git a/docs/router/framework/react/guide/scroll-restoration.md b/docs/router/guide/scroll-restoration.md similarity index 86% rename from docs/router/framework/react/guide/scroll-restoration.md rename to docs/router/guide/scroll-restoration.md index 9aa5ec7f3ba..23450915101 100644 --- a/docs/router/framework/react/guide/scroll-restoration.md +++ b/docs/router/guide/scroll-restoration.md @@ -55,8 +55,6 @@ It does this by: That may sound like a lot, but for you, it's as simple as this: ```tsx -import { createRouter } from '@tanstack/react-router' - const router = createRouter({ scrollRestoration: true, }) @@ -80,8 +78,6 @@ The default `getKey` is `(location) => location.state.__TSR_key!`, where `__TSR_ You could sync scrolling to the pathname: ```tsx -import { createRouter } from '@tanstack/react-router' - const router = createRouter({ getScrollRestorationKey: (location) => location.pathname, }) @@ -90,8 +86,6 @@ const router = createRouter({ You can conditionally sync only some paths, then use the key for the rest: ```tsx -import { createRouter } from '@tanstack/react-router' - const router = createRouter({ getScrollRestorationKey: (location) => { const paths = ['/', '/chat'] @@ -118,8 +112,6 @@ Most of the time, you won't need to do anything special to get scroll restoratio To manually control scroll restoration for virtualized lists within the whole browser window: -[//]: # 'VirtualizedWindowScrollRestorationExample' - ```tsx function Component() { const scrollEntry = useElementScrollRestoration({ @@ -145,11 +137,11 @@ function Component() { } ``` -[//]: # 'VirtualizedWindowScrollRestorationExample' - To manually control scroll restoration for a specific element, you can use the `useElementScrollRestoration` hook and the `data-scroll-restoration-id` DOM attribute: -[//]: # 'ManualRestorationExample' +<!-- ::start:framework --> + +# React ```tsx function Component() { @@ -188,15 +180,52 @@ function Component() { } ``` -[//]: # 'ManualRestorationExample' +# Solid + +```tsx +function Component() { + // We need a unique ID for manual scroll restoration on a specific element + // It should be as unique as possible for this element across your app + const scrollRestorationId = 'myVirtualizedContent' + + // We use that ID to get the scroll entry for this element + const scrollEntry = useElementScrollRestoration({ + id: scrollRestorationId, + }) + + // Let's use TanStack Virtual to virtualize some content! + let virtualizerParentRef: any + const virtualizer = createVirtualizer({ + count: 10000, + getScrollElement: () => virtualizerParentRef, + estimateSize: () => 100, + // We pass the scrollY from the scroll restoration entry to the virtualizer + // as the initial offset + initialOffset: scrollEntry?.scrollY, + }) + + return ( + <div + ref={virtualizerParentRef} + // We pass the scroll restoration ID to the element + // as a custom attribute that will get picked up by the + // scroll restoration watcher + data-scroll-restoration-id={scrollRestorationId} + class="flex-1 border rounded-lg overflow-auto relative" + > + ... + </div> + ) +} +``` + +<!-- ::end:framework --> ## Scroll Behavior To control the scroll behavior when navigating between pages, you can use the `scrollRestorationBehavior` option. This allows you to make the transition between pages instant instead of a smooth scroll. The global configuration of scroll restoration behavior has the same options as those supported by the browser, which are `smooth`, `instant`, and `auto` (see [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView#behavior) for more information). ```tsx -import { createRouter } from '@tanstack/react-router' - const router = createRouter({ scrollRestorationBehavior: 'instant', }) diff --git a/docs/router/framework/react/guide/search-params.md b/docs/router/guide/search-params.md similarity index 91% rename from docs/router/framework/react/guide/search-params.md rename to docs/router/guide/search-params.md index ce3096cefbf..53e9cd1c8a1 100644 --- a/docs/router/framework/react/guide/search-params.md +++ b/docs/router/guide/search-params.md @@ -90,9 +90,7 @@ Despite TanStack Router being able to parse search params into reliable JSON, th TanStack Router provides convenient APIs for validating and typing search params. This all starts with the `Route`'s `validateSearch` option: -```tsx -// /routes/shop.products.tsx - +```tsx title="src/routes/shop/products.tsx" type ProductSearchSortOptions = 'newest' | 'oldest' | 'price' type ProductSearch = { @@ -121,9 +119,7 @@ The `validateSearch` option is a function that is provided the JSON parsed (but Here's an example: -```tsx -// /routes/shop.products.tsx - +```tsx title="src/routes/shop/products.tsx" type ProductSearchSortOptions = 'newest' | 'oldest' | 'price' type ProductSearch = { @@ -146,9 +142,7 @@ export const Route = createFileRoute('/shop/products')({ Here's an example using the [Zod](https://zod.dev/) library (but feel free to use any validation library you want) to both validate and type the search params in a single step: -```tsx -// /routes/shop.products.tsx - +```tsx title="src/routes/shop/products.tsx" import { z } from 'zod' const productSearchSchema = z.object({ @@ -179,7 +173,6 @@ The underlying mechanics why this works relies on the `validateSearch` function When using a library like [Zod](https://zod.dev/) to validate search params you might want to `transform` search params before committing the search params to the URL. A common `zod` `transform` is `default` for example. ```tsx -import { createFileRoute } from '@tanstack/react-router' import { z } from 'zod' const productSearchSchema = z.object({ @@ -206,7 +199,6 @@ For validation libraries we recommend using adapters which infer the correct `in An adapter is provided for [Zod](https://zod.dev/) which will pipe through the correct `input` type and `output` type ```tsx -import { createFileRoute } from '@tanstack/react-router' import { zodValidator } from '@tanstack/zod-adapter' import { z } from 'zod' @@ -230,7 +222,6 @@ The important part here is the following use of `Link` no longer requires `searc However the use of `catch` here overrides the types and makes `page`, `filter` and `sort` `unknown` causing type loss. We have handled this case by providing a `fallback` generic function which retains the types but provides a `fallback` value when validation fails ```tsx -import { createFileRoute } from '@tanstack/react-router' import { fallback, zodValidator } from '@tanstack/zod-adapter' import { z } from 'zod' @@ -279,7 +270,6 @@ This provides flexibility in which type you want to infer for navigation and whi When using [Valibot](https://valibot.dev/) an adapter is not needed to ensure the correct `input` and `output` types are used for navigation and reading search params. This is because `valibot` implements [Standard Schema](https://github.com/standard-schema/standard-schema) ```tsx -import { createFileRoute } from '@tanstack/react-router' import * as v from 'valibot' const productSearchSchema = v.object({ @@ -304,7 +294,6 @@ export const Route = createFileRoute('/shop/products/')({ When using [ArkType](https://arktype.io/) an adapter is not needed to ensure the correct `input` and `output` types are used for navigation and reading search params. This is because [ArkType](https://arktype.io/) implements [Standard Schema](https://github.com/standard-schema/standard-schema) ```tsx -import { createFileRoute } from '@tanstack/react-router' import { type } from 'arktype' const productSearchSchema = type({ @@ -323,7 +312,6 @@ export const Route = createFileRoute('/shop/products/')({ When using [Effect/Schema](https://effect.website/docs/schema/introduction/) an adapter is not needed to ensure the correct `input` and `output` types are used for navigation and reading search params. This is because [Effect/Schema](https://effect.website/docs/schema/standard-schema/) implements [Standard Schema](https://github.com/standard-schema/standard-schema) ```tsx -import { createFileRoute } from '@tanstack/react-router' import { Schema as S } from 'effect' const productSearchSchema = S.standardSchemaV1( @@ -369,9 +357,9 @@ Please read the [Search Params in Loaders](./data-loading.md#using-loaderdeps-to The search parameters and types of parents are merged as you go down the route tree, so child routes also have access to their parent's search params: -- `shop.products.tsx` +<!-- ::start:tabs variant="files" --> -```tsx +```tsx title="src/routes/shop/products.tsx" const productSearchSchema = z.object({ page: z.number().catch(1), filter: z.string().catch(''), @@ -385,9 +373,7 @@ export const Route = createFileRoute('/shop/products')({ }) ``` -- `shop.products.$productId.tsx` - -```tsx +```tsx title="src/routes/shop/products/$productId.tsx" export const Route = createFileRoute('/shop/products/$productId')({ beforeLoad: ({ search }) => { search @@ -396,13 +382,13 @@ export const Route = createFileRoute('/shop/products/$productId')({ }) ``` +<!-- ::end:tabs --> + ### Search Params in Components You can access your route's validated search params in your route's `component` via the `useSearch` hook. -```tsx -// /routes/shop.products.tsx - +```tsx title="src/routes/shop/products.tsx" export const Route = createFileRoute('/shop/products')({ validateSearch: productSearchSchema, }) @@ -422,7 +408,7 @@ const ProductList = () => { You can access your route's validated search params anywhere in your app using the `useSearch` hook. By passing the `from` id/path of your origin route, you'll get even better type safety: ```tsx -// /routes/shop.products.tsx +// src/routes/shop.products.tsx export const Route = createFileRoute('/shop/products')({ validateSearch: productSearchSchema, // ... @@ -430,7 +416,7 @@ export const Route = createFileRoute('/shop/products')({ // Somewhere else... -// /components/product-list-sidebar.tsx +// src/components/product-list-sidebar.tsx const routeApi = getRouteApi('/shop/products') const ProductList = () => { @@ -474,8 +460,7 @@ The best way to update search params is to use the `search` prop on the `<Link / If the search for the current page shall be updated and the `from` prop is specified, the `to` prop can be omitted. Here's an example: -```tsx -// /routes/shop.products.tsx +```tsx title="src/routes/shop/products.tsx" export const Route = createFileRoute('/shop/products')({ validateSearch: productSearchSchema, }) @@ -531,8 +516,7 @@ const PageSelector = () => { The `navigate` function also accepts a `search` option that works the same way as the `search` prop on `<Link />`: -```tsx -// /routes/shop.products.tsx +```tsx title="src/routes/shop/products.tsx" export const Route = createFileRoute('/shop/products/$productId')({ validateSearch: productSearchSchema, }) @@ -576,7 +560,6 @@ The following example shows how to make sure that for **every** link that is bei ```tsx import { z } from 'zod' -import { createFileRoute } from '@tanstack/react-router' import { zodValidator } from '@tanstack/zod-adapter' const searchSchema = z.object({ @@ -601,6 +584,10 @@ export const Route = createRootRoute({ Since this specific use case is quite common, TanStack Router provides a generic implementation to retain search params via `retainSearchParams`: +<!-- ::start:framework --> + +# React + ```tsx import { z } from 'zod' import { createFileRoute, retainSearchParams } from '@tanstack/react-router' @@ -618,8 +605,33 @@ export const Route = createRootRoute({ }) ``` +# Solid + +```tsx +import { z } from 'zod' +import { createFileRoute, retainSearchParams } from '@tanstack/solid-router' +import { zodValidator } from '@tanstack/zod-adapter' + +const searchSchema = z.object({ + rootValue: z.string().optional(), +}) + +export const Route = createRootRoute({ + validateSearch: zodValidator(searchSchema), + search: { + middlewares: [retainSearchParams(['rootValue'])], + }, +}) +``` + +<!-- ::end:framework --> + Another common use case is to strip out search params from links if their default value is set. TanStack Router provides a generic implementation for this use case via `stripSearchParams`: +<!-- ::start:framework --> + +# React + ```tsx import { z } from 'zod' import { createFileRoute, stripSearchParams } from '@tanstack/react-router' @@ -644,8 +656,40 @@ export const Route = createFileRoute('/hello')({ }) ``` +# Solid + +```tsx +import { z } from 'zod' +import { createFileRoute, stripSearchParams } from '@tanstack/solid-router' +import { zodValidator } from '@tanstack/zod-adapter' + +const defaultValues = { + one: 'abc', + two: 'xyz', +} + +const searchSchema = z.object({ + one: z.string().default(defaultValues.one), + two: z.string().default(defaultValues.two), +}) + +export const Route = createFileRoute('/hello')({ + validateSearch: zodValidator(searchSchema), + search: { + // strip default values + middlewares: [stripSearchParams(defaultValues)], + }, +}) +``` + +<!-- ::end:framework --> + Multiple middlewares can be chained. The following example shows how to combine both `retainSearchParams` and `stripSearchParams`. +<!-- ::start:framework --> + +# React + ```tsx import { Link, @@ -674,3 +718,36 @@ export const Route = createFileRoute('/search')({ }, }) ``` + +# Solid + +```tsx +import { + Link, + createFileRoute, + retainSearchParams, + stripSearchParams, +} from '@tanstack/solid-router' +import { z } from 'zod' +import { zodValidator } from '@tanstack/zod-adapter' + +const defaultValues = ['foo', 'bar'] + +export const Route = createFileRoute('/search')({ + validateSearch: zodValidator( + z.object({ + retainMe: z.string().optional(), + arrayWithDefaults: z.string().array().default(defaultValues), + required: z.string(), + }), + ), + search: { + middlewares: [ + retainSearchParams(['retainMe']), + stripSearchParams({ arrayWithDefaults: defaultValues }), + ], + }, +}) +``` + +<!-- ::end:framework --> diff --git a/docs/router/framework/react/guide/ssr.md b/docs/router/guide/ssr.md similarity index 69% rename from docs/router/framework/react/guide/ssr.md rename to docs/router/guide/ssr.md index 73ab21276fe..b51f1f2f7de 100644 --- a/docs/router/framework/react/guide/ssr.md +++ b/docs/router/guide/ssr.md @@ -24,6 +24,10 @@ Non-Streaming server-side rendering is the classic process of rendering the mark To implement non-streaming SSR with TanStack Router, you will need the following utilities: +<!-- ::start:framework --> + +# React + - `RouterClient` from `@tanstack/react-router` - e.g. `<RouterClient router={router} />` - Rendering this component in your client entry will render your application and also automatically implement the `Wrap` component option on `Router` @@ -36,6 +40,22 @@ To implement non-streaming SSR with TanStack Router, you will need the following - `RouterServer` from `@tanstack/react-router` - This implements the `Wrap` component option on `Router` +# Solid + +- `RouterClient` from `@tanstack/solid-router` + - e.g. `<RouterClient router={router} />` + - Rendering this component in your client entry will render your application and also automatically implement the `Wrap` component option on `Router` +- And, either: + - `defaultRenderHandler` from `@tanstack/solid-router` + - This will render your application in your server entry and also automatically handle application-level hydration/dehydration and also automatically implement the RouterServer component. + or: + - `renderRouterToString` from `@tanstack/solid-router` + - This differs from defaultRenderHandler in that it allows you to manually specify the `Wrap` component option on `Router` together with any other providers you may need to wrap it with. + - `RouterServer` from `@tanstack/solid-router` + - This implements the `Wrap` component option on `Router` + +<!-- ::end:framework --> + ### Automatic Server History On the client, Router defaults to using an instance of `createBrowserHistory`, which is the preferred type of history to use on the client. On the server, however, you will want to use an instance of `createMemoryHistory` instead. This is because `createBrowserHistory` uses the `window` object, which does not exist on the server. This is handled automatically for you in the RouterServer component. @@ -52,8 +72,11 @@ For more information on how to utilize data loading, see the [Data Loading](./da Since your router will exist both on the server and the client, it's important that you create your router in a way that is consistent between both of these environments. The easiest way to do this is to expose a `createRouter` function in a shared file that can be imported and called by both your server and client entry files. -```tsx -// src/router.tsx +<!-- ::start:framework --> + +# React + +```tsx title='src/router.tsx' import { createRouter as createTanstackRouter } from '@tanstack/react-router' import { routeTree } from './routeTree.gen' @@ -68,14 +91,36 @@ declare module '@tanstack/react-router' { } ``` +# Solid + +```tsx title='src/router.tsx' +import { createRouter as createTanstackRouter } from '@tanstack/solid-router' +import { routeTree } from './routeTree.gen' + +export function createRouter() { + return createTanstackRouter({ routeTree }) +} + +declare module '@tanstack/solid-router' { + interface Register { + router: ReturnType<typeof createRouter> + } +} +``` + +<!-- ::end:framework --> + ### Rendering the 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 `defaultRenderHandler` -```tsx -// src/entry-server.tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/entry-server.tsx" import { createRequestHandler, defaultRenderToString, @@ -89,10 +134,31 @@ export async function render({ request }: { request: Request }) { } ``` +# Solid + +```tsx title="src/entry-server.tsx" +import { + createRequestHandler, + defaultRenderToString, +} from '@tanstack/solid-router/ssr/server' +import { createRouter } from './router' + +export async function render({ request }: { request: Request }) { + const handler = createRequestHandler({ request, createRouter }) + + return await handler(defaultRenderHandler) +} +``` + +<!-- ::end:framework --> + using `renderRouterToString` -```tsx -// src/entry-server.tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/entry-server.tsx" import { createRequestHandler, renderRouterToString, @@ -114,6 +180,32 @@ export function render({ request }: { request: Request }) { } ``` +# Solid + +```tsx title="src/entry-server.tsx" +import { + createRequestHandler, + renderRouterToString, + RouterServer, +} from '@tanstack/react-router/ssr/server' +import { createRouter } from './router' + +export function render({ request }: { request: Request }) { + const handler = createRequestHandler({ request, createRouter }) + + return handler(({ request, responseHeaders, router }) => + renderRouterToString({ + request, + responseHeaders, + router, + children: <RouterServer router={router} />, + }), + ) +} +``` + +<!-- ::end:framework --> + NOTE: The createRequestHandler method requires a web api standard Request object, while the handler method will return a web api standard Response promise. Should you be using a server framework like Express that uses its own Request and Response objects you would need to convert from the one to the other. Please have a look at the examples for how such an implementation might look like. @@ -125,10 +217,11 @@ On the client, things are much simpler. - Create your router instance - Render your application using the `<RouterClient />` component -[//]: # 'ClientEntryFileExample' +<!-- ::start:framework --> -```tsx -// src/entry-client.tsx +# React + +```tsx title="src/entry-client.tsx" import { hydrateRoot } from 'react-dom/client' import { RouterClient } from '@tanstack/react-router/ssr/client' import { createRouter } from './router' @@ -138,7 +231,19 @@ const router = createRouter() hydrateRoot(document, <RouterClient router={router} />) ``` -[//]: # 'ClientEntryFileExample' +# Solid + +```tsx title="src/entry-client.tsx" +import { hydrate } from 'solid-js/web' +import { RouterClient } from '@tanstack/solid-router/ssr/client' +import { createRouter } from './router' + +const router = createRouter() + +hydrate(() => <RouterClient router={router} />, document.body) +``` + +<!-- ::end:framework --> With this setup, your application will be rendered on the server and then hydrated on the client! @@ -153,8 +258,11 @@ This pattern can be useful for pages that have slow or high-latency data fetchin using `defaultStreamHandler` -```tsx -// src/entry-server.tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/entry-server.tsx" import { createRequestHandler, defaultStreamHandler, @@ -168,10 +276,31 @@ export async function render({ request }: { request: Request }) { } ``` +# Solid + +```tsx title="src/entry-server.tsx" +import { + createRequestHandler, + defaultStreamHandler, +} from '@tanstack/solid-router/ssr/server' +import { createRouter } from './router' + +export async function render({ request }: { request: Request }) { + const handler = createRequestHandler({ request, createRouter }) + + return await handler(defaultStreamHandler) +} +``` + +<!-- ::end:framework --> + using `renderRouterToStream` -```tsx -// src/entry-server.tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/entry-server.tsx" import { createRequestHandler, renderRouterToStream, @@ -193,6 +322,32 @@ export function render({ request }: { request: Request }) { } ``` +# Solid + +```tsx title="src/entry-server.tsx" +import { + createRequestHandler, + renderRouterToStream, + RouterServer, +} from '@tanstack/solid-router/ssr/server' +import { createRouter } from './router' + +export function render({ request }: { request: Request }) { + const handler = createRequestHandler({ request, createRouter }) + + return handler(({ request, responseHeaders, router }) => + renderRouterToStream({ + request, + responseHeaders, + router, + children: <RouterServer router={router} />, + }), + ) +} +``` + +<!-- ::end:framework --> + ## Streaming Dehydration/Hydration Streaming dehydration/hydration is an advanced pattern that goes beyond markup and allows you to dehydrate and stream any supporting data from the server to the client and rehydrate it on arrival. This is useful for applications that may need to further use/manage the underlying data that was used to render the initial markup on the server. diff --git a/docs/router/framework/react/guide/static-route-data.md b/docs/router/guide/static-route-data.md similarity index 57% rename from docs/router/framework/react/guide/static-route-data.md rename to docs/router/guide/static-route-data.md index f2442253d5b..2df546330bb 100644 --- a/docs/router/framework/react/guide/static-route-data.md +++ b/docs/router/guide/static-route-data.md @@ -8,9 +8,13 @@ In addition to being able to access this data from the route itself, you can als ## Example -- `posts.tsx` +<!-- ::start:framework --> -```tsx +# React + +<!-- ::start:tabs variant="files" --> + +```tsx title='src/routes/posts.tsx' import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/posts')({ @@ -20,11 +24,13 @@ export const Route = createFileRoute('/posts')({ }) ``` +<!-- ::end:tabs --> + You can then access this data anywhere you have access to your routes, including matches that can be mapped back to their routes. -- `__root.tsx` +<!-- ::start:tabs variant="files" --> -```tsx +```tsx title='src/routes/__root.tsx' import { createRootRoute } from '@tanstack/react-router' export const Route = createRootRoute({ @@ -42,10 +48,59 @@ export const Route = createRootRoute({ }) ``` +<!-- ::end:tabs --> + +# Solid + +<!-- ::start:tabs variant="files" --> + +```tsx title='src/routes/posts.tsx' +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/posts')({ + staticData: { + customData: 'Hello!', + }, +}) +``` + +<!-- ::end:tabs --> + +You can then access this data anywhere you have access to your routes, including matches that can be mapped back to their routes. + +<!-- ::start:tabs variant="files" --> + +```tsx title='src/routes/__root.tsx' +import { createRootRoute, useMatches } from '@tanstack/solid-router' +import { For } from 'solid-js' + +export const Route = createRootRoute({ + component: () => { + const matches = useMatches() + + return ( + <div> + <For each={matches()}> + {(match) => <div>{match.staticData.customData}</div>} + </For> + </div> + ) + }, +}) +``` + +<!-- ::end:tabs --> + +<!-- ::end:framework --> + ## Enforcing Static Data If you want to enforce that a route has static data, you can use declaration merging to add a type to the route's static option: +<!-- ::start:framework --> + +# React + ```tsx declare module '@tanstack/react-router' { interface StaticDataRouteOption { @@ -54,11 +109,21 @@ declare module '@tanstack/react-router' { } ``` -Now, if you try to create a route without the `customData` property, you'll get a type error: +# Solid ```tsx -import { createFileRoute } from '@tanstack/react-router' +declare module '@tanstack/solid-router' { + interface StaticDataRouteOption { + customData: string + } +} +``` + +<!-- ::end:framework --> +Now, if you try to create a route without the `customData` property, you'll get a type error: + +```tsx export const Route = createFileRoute('/posts')({ staticData: { // Property 'customData' is missing in type '{ customData: number; }' but required in type 'StaticDataRouteOption'.ts(2741) @@ -70,6 +135,10 @@ export const Route = createFileRoute('/posts')({ If you want to make static data optional, simply add a `?` to the property: +<!-- ::start:framework --> + +# React + ```tsx declare module '@tanstack/react-router' { interface StaticDataRouteOption { @@ -78,6 +147,18 @@ declare module '@tanstack/react-router' { } ``` +# Solid + +```tsx +declare module '@tanstack/solid-router' { + interface StaticDataRouteOption { + customData?: string + } +} +``` + +<!-- ::end:framework --> + As long as there are any required properties on the `StaticDataRouteOption`, you'll be required to pass in an object. ## Common Patterns @@ -86,14 +167,19 @@ As long as there are any required properties on the `StaticDataRouteOption`, you Use staticData to control which routes show or hide layout elements: -```tsx -// routes/admin/route.tsx +<!-- ::start:tabs variant="files" --> + +```tsx title='src/routes/admin/route.tsx' export const Route = createFileRoute('/admin')({ staticData: { showNavbar: false }, component: AdminLayout, }) ``` +<!-- ::end:tabs --> + +<!-- ::start:tabs variant="files" --> + ```tsx // routes/__root.tsx function RootComponent() { @@ -112,10 +198,17 @@ function RootComponent() { } ``` +<!-- ::end:tabs --> + ### Route Titles for Breadcrumbs -```tsx -// routes/posts/$postId.tsx +<!-- ::start:framework --> + +# React + +<!-- ::start:tabs variant="files" --> + +```tsx title='src/routes/posts/$postId.tsx' export const Route = createFileRoute('/posts/$postId')({ staticData: { getTitle: () => 'Post Details', @@ -123,8 +216,11 @@ export const Route = createFileRoute('/posts/$postId')({ }) ``` -```tsx -// In a Breadcrumb component +<!-- ::end:tabs --> + +<!-- ::start:tabs variant="files" --> + +```tsx title='src/components/Breadcrumbs.tsx' function Breadcrumbs() { const matches = useMatches() @@ -140,6 +236,45 @@ function Breadcrumbs() { } ``` +<!-- ::end:tabs --> + +# Solid + +<!-- ::start:tabs variant="files" --> + +```tsx title='src/routes/posts/$postId.tsx' +export const Route = createFileRoute('/posts/$postId')({ + staticData: { + getTitle: () => 'Post Details', + }, +}) +``` + +<!-- ::end:tabs --> + +<!-- ::start:tabs variant="files" --> + +```tsx title='src/components/Breadcrumbs.tsx' +import { useMatches } from '@tanstack/solid-router' +import { For } from 'solid-js' + +function Breadcrumbs() { + const matches = useMatches() + + return ( + <nav> + <For each={matches().filter((m) => m.staticData?.getTitle)}> + {(m) => <span>{m.staticData.getTitle()}</span>} + </For> + </nav> + ) +} +``` + +<!-- ::end:tabs --> + +<!-- ::end:framework --> + ### When to Use staticData vs Context | staticData | context | diff --git a/docs/router/framework/react/guide/type-safety.md b/docs/router/guide/type-safety.md similarity index 98% rename from docs/router/framework/react/guide/type-safety.md rename to docs/router/guide/type-safety.md index fe17ae3d69a..2adffc58c56 100644 --- a/docs/router/framework/react/guide/type-safety.md +++ b/docs/router/guide/type-safety.md @@ -28,6 +28,10 @@ const parentRoute = createRoute({ For the types of your router to work with top-level exports like `Link`, `useNavigate`, `useParams`, etc. they must permeate the TypeScript module boundary and be registered right into the library. To do this, we use declaration merging on the exported `Register` interface. +<!-- ::start:framework --> + +# React + ```ts const router = createRouter({ // ... @@ -40,6 +44,22 @@ declare module '@tanstack/react-router' { } ``` +# Solid + +```ts +const router = createRouter({ + // ... +}) + +declare module '@tanstack/solid-router' { + interface Register { + router: typeof router + } +} +``` + +<!-- ::end:framework --> + By registering your router with the module, you can now use the exported hooks, components, and utilities with your router's exact types. ## Fixing the Component Context Problem diff --git a/docs/router/framework/react/guide/type-utilities.md b/docs/router/guide/type-utilities.md similarity index 69% rename from docs/router/framework/react/guide/type-utilities.md rename to docs/router/guide/type-utilities.md index a0b39abcd5b..8db99d28e21 100644 --- a/docs/router/framework/react/guide/type-utilities.md +++ b/docs/router/guide/type-utilities.md @@ -9,6 +9,10 @@ Most types exposed by TanStack Router are internal, subject to breaking changes `ValidateLinkOptions` type checks object literal types to ensure they conform to `Link` options at inference sites. For example, you may have a generic `HeadingLink` component which accepts a `title` prop along with `linkOptions`, the idea being this component can be re-used for any navigation. +<!-- ::start:framework --> + +# React + ```tsx export interface HeaderLinkProps< TRouter extends RegisteredRouter = RegisteredRouter, @@ -31,6 +35,32 @@ export function HeadingLink(props: HeaderLinkProps): React.ReactNode { } ``` +# Solid + +```tsx +export interface HeaderLinkProps< + TRouter extends RegisteredRouter = RegisteredRouter, + TOptions = unknown, +> { + title: string + linkOptions: ValidateLinkOptions<TRouter, TOptions> +} + +export function HeadingLink<TRouter extends RegisteredRouter, TOptions>( + props: HeaderLinkProps<TRouter, TOptions>, +): Solid.JSX.Element +export function HeadingLink(props: HeaderLinkProps): Solid.JSX.Element { + return ( + <> + <h1>{props.title}</h1> + <Link {...props.linkOptions} /> + </> + ) +} +``` + +<!-- ::end:framework --> + A more permissive overload of `HeadingLink` is used to avoid type assertions you would otherwise have to do with the generic signature. Using a looser signature without type parameters is an easy way to avoid type assertions in the implementation of `HeadingLink` All type parameters for utilities are optional but for the best TypeScript performance `TRouter` should always be specified for the public facing signature. And `TOptions` should always be used at inference sites like `HeadingLink` to infer the `linkOptions` to correctly narrow `params` and `search` @@ -46,6 +76,10 @@ The result of this is that `linkOptions` in the following is completely type-saf All navigation type utilities have an array variant. `ValidateLinkOptionsArray` enables type checking of an array of `Link` options. For example, you might have a generic `Menu` component where each item is a `Link`. +<!-- ::start:framework --> + +# React + ```tsx export interface MenuProps< TRouter extends RegisteredRouter = RegisteredRouter, @@ -71,6 +105,39 @@ export function Menu(props: MenuProps): React.ReactNode { } ``` +# Solid + +```tsx +import { For } from 'solid-js' + +export interface MenuProps< + TRouter extends RegisteredRouter = RegisteredRouter, + TItems extends ReadonlyArray<unknown> = ReadonlyArray<unknown>, +> { + items: ValidateLinkOptionsArray<TRouter, TItems> +} + +export function Menu< + TRouter extends RegisteredRouter = RegisteredRouter, + TItems extends ReadonlyArray<unknown>, +>(props: MenuProps<TRouter, TItems>): Solid.JSX.Element +export function Menu(props: MenuProps): Solid.JSX.Element { + return ( + <ul> + <For each={props.items}> + {(item) => ( + <li> + <Link {...item} /> + </li> + )} + </For> + </ul> + ) +} +``` + +<!-- ::end:framework --> + This of course allows the following `items` prop to be completely type-safe ```tsx @@ -84,7 +151,11 @@ This of course allows the following `items` prop to be completely type-safe It is also possible to fix `from` for each `Link` options in the array. This would allow all `Menu` items to navigate relative to `from`. Additional type checking of `from` can be provided by the `ValidateFromPath` utility -```tsx +<!-- ::start:framework --> + +# React + +```ts export interface MenuProps< TRouter extends RegisteredRouter = RegisteredRouter, TItems extends ReadonlyArray<unknown> = ReadonlyArray<unknown>, @@ -112,6 +183,42 @@ export function Menu(props: MenuProps): React.ReactNode { } ``` +# Solid + +```ts +import { For } from 'solid-js' + +export interface MenuProps< + TRouter extends RegisteredRouter = RegisteredRouter, + TItems extends ReadonlyArray<unknown> = ReadonlyArray<unknown>, + TFrom extends string = string, +> { + from: ValidateFromPath<TRouter, TFrom> + items: ValidateLinkOptionsArray<TRouter, TItems, TFrom> +} + +export function Menu< + TRouter extends RegisteredRouter = RegisteredRouter, + TItems extends ReadonlyArray<unknown>, + TFrom extends string = string, +>(props: MenuProps<TRouter, TItems, TFrom>): Solid.JSX.Element +export function Menu(props: MenuProps): Solid.JSX.Element { + return ( + <ul> + <For each={props.items}> + {(item) => ( + <li> + <Link {...item} from={props.from} /> + </li> + )} + </For> + </ul> + ) +} +``` + +<!-- ::end:framework --> + `ValidateLinkOptionsArray` allows you to fix `from` by providing an extra type parameter. The result is a type safe array of `Link` options providing navigation relative to `from` ```tsx @@ -157,7 +264,9 @@ fetchOrRedirect('http://example.com/', { to: '/login' }) `ValidateNavigateOptions` type checks object literal types to ensure they conform to navigate options at inference sites. For example, you may want to write a custom hook to enable/disable navigation. -[//]: # 'TypeCheckingNavigateOptionsWithValidateNavigateOptionsImpl' +<!-- ::start:framework --> + +# React ```tsx export interface UseConditionalNavigateResult { @@ -189,7 +298,41 @@ export function useConditionalNavigate( } ``` -[//]: # 'TypeCheckingNavigateOptionsWithValidateNavigateOptionsImpl' +# Solid + +```tsx +import { createSignal } from 'solid-js' + +export interface UseConditionalNavigateResult { + enable: () => void + disable: () => void + navigate: () => void +} + +export function useConditionalNavigate< + TRouter extends RegisteredRouter = RegisteredRouter, + TOptions = unknown, +>( + navigateOptions: ValidateNavigateOptions<TRouter, TOptions>, +): UseConditionalNavigateResult +export function useConditionalNavigate( + navigateOptions: ValidateNavigateOptions, +): UseConditionalNavigateResult { + const [enabled, setEnabled] = createSignal(false) + const navigate = useNavigate() + return { + enable: () => setEnabled(true), + disable: () => setEnabled(false), + navigate: () => { + if (enabled()) { + navigate(navigateOptions) + } + }, + } +} +``` + +<!-- ::end:framework --> The result of this is that `navigateOptions` passed to `useConditionalNavigate` is completely type-safe and we can enable/disable navigation based on react state diff --git a/docs/router/framework/react/guide/url-rewrites.md b/docs/router/guide/url-rewrites.md similarity index 100% rename from docs/router/framework/react/guide/url-rewrites.md rename to docs/router/guide/url-rewrites.md diff --git a/docs/router/framework/react/how-to/README.md b/docs/router/how-to/README.md similarity index 100% rename from docs/router/framework/react/how-to/README.md rename to docs/router/how-to/README.md diff --git a/docs/router/framework/react/how-to/arrays-objects-dates-search-params.md b/docs/router/how-to/arrays-objects-dates-search-params.md similarity index 100% rename from docs/router/framework/react/how-to/arrays-objects-dates-search-params.md rename to docs/router/how-to/arrays-objects-dates-search-params.md diff --git a/docs/router/framework/react/how-to/debug-router-issues.md b/docs/router/how-to/debug-router-issues.md similarity index 100% rename from docs/router/framework/react/how-to/debug-router-issues.md rename to docs/router/how-to/debug-router-issues.md diff --git a/docs/router/framework/react/how-to/deploy-to-production.md b/docs/router/how-to/deploy-to-production.md similarity index 100% rename from docs/router/framework/react/how-to/deploy-to-production.md rename to docs/router/how-to/deploy-to-production.md diff --git a/docs/router/framework/react/how-to/drafts/README.md b/docs/router/how-to/drafts/README.md similarity index 100% rename from docs/router/framework/react/how-to/drafts/README.md rename to docs/router/how-to/drafts/README.md diff --git a/docs/router/framework/react/how-to/drafts/build-search-filtering-systems.draft.md b/docs/router/how-to/drafts/build-search-filtering-systems.draft.md similarity index 100% rename from docs/router/framework/react/how-to/drafts/build-search-filtering-systems.draft.md rename to docs/router/how-to/drafts/build-search-filtering-systems.draft.md diff --git a/docs/router/framework/react/how-to/drafts/optimize-search-param-performance.draft.md b/docs/router/how-to/drafts/optimize-search-param-performance.draft.md similarity index 100% rename from docs/router/framework/react/how-to/drafts/optimize-search-param-performance.draft.md rename to docs/router/how-to/drafts/optimize-search-param-performance.draft.md diff --git a/docs/router/framework/react/how-to/drafts/search-params-in-forms.draft.md b/docs/router/how-to/drafts/search-params-in-forms.draft.md similarity index 100% rename from docs/router/framework/react/how-to/drafts/search-params-in-forms.draft.md rename to docs/router/how-to/drafts/search-params-in-forms.draft.md diff --git a/docs/router/framework/react/how-to/install.md b/docs/router/how-to/install.md similarity index 100% rename from docs/router/framework/react/how-to/install.md rename to docs/router/how-to/install.md diff --git a/docs/router/framework/react/how-to/integrate-chakra-ui.md b/docs/router/how-to/integrate-chakra-ui.md similarity index 100% rename from docs/router/framework/react/how-to/integrate-chakra-ui.md rename to docs/router/how-to/integrate-chakra-ui.md diff --git a/docs/router/framework/react/how-to/integrate-framer-motion.md b/docs/router/how-to/integrate-framer-motion.md similarity index 100% rename from docs/router/framework/react/how-to/integrate-framer-motion.md rename to docs/router/how-to/integrate-framer-motion.md diff --git a/docs/router/framework/react/how-to/integrate-material-ui.md b/docs/router/how-to/integrate-material-ui.md similarity index 100% rename from docs/router/framework/react/how-to/integrate-material-ui.md rename to docs/router/how-to/integrate-material-ui.md diff --git a/docs/router/framework/react/how-to/integrate-shadcn-ui.md b/docs/router/how-to/integrate-shadcn-ui.md similarity index 100% rename from docs/router/framework/react/how-to/integrate-shadcn-ui.md rename to docs/router/how-to/integrate-shadcn-ui.md diff --git a/docs/router/framework/react/how-to/migrate-from-react-router.md b/docs/router/how-to/migrate-from-react-router.md similarity index 100% rename from docs/router/framework/react/how-to/migrate-from-react-router.md rename to docs/router/how-to/migrate-from-react-router.md diff --git a/docs/router/framework/react/how-to/navigate-with-search-params.md b/docs/router/how-to/navigate-with-search-params.md similarity index 100% rename from docs/router/framework/react/how-to/navigate-with-search-params.md rename to docs/router/how-to/navigate-with-search-params.md diff --git a/docs/router/framework/react/how-to/setup-auth-providers.md b/docs/router/how-to/setup-auth-providers.md similarity index 100% rename from docs/router/framework/react/how-to/setup-auth-providers.md rename to docs/router/how-to/setup-auth-providers.md diff --git a/docs/router/framework/react/how-to/setup-authentication.md b/docs/router/how-to/setup-authentication.md similarity index 100% rename from docs/router/framework/react/how-to/setup-authentication.md rename to docs/router/how-to/setup-authentication.md diff --git a/docs/router/framework/react/how-to/setup-basic-search-params.md b/docs/router/how-to/setup-basic-search-params.md similarity index 100% rename from docs/router/framework/react/how-to/setup-basic-search-params.md rename to docs/router/how-to/setup-basic-search-params.md diff --git a/docs/router/framework/react/how-to/setup-rbac.md b/docs/router/how-to/setup-rbac.md similarity index 100% rename from docs/router/framework/react/how-to/setup-rbac.md rename to docs/router/how-to/setup-rbac.md diff --git a/docs/router/framework/react/how-to/setup-ssr.md b/docs/router/how-to/setup-ssr.md similarity index 100% rename from docs/router/framework/react/how-to/setup-ssr.md rename to docs/router/how-to/setup-ssr.md diff --git a/docs/router/framework/react/how-to/setup-testing.md b/docs/router/how-to/setup-testing.md similarity index 100% rename from docs/router/framework/react/how-to/setup-testing.md rename to docs/router/how-to/setup-testing.md diff --git a/docs/router/framework/react/how-to/share-search-params-across-routes.md b/docs/router/how-to/share-search-params-across-routes.md similarity index 100% rename from docs/router/framework/react/how-to/share-search-params-across-routes.md rename to docs/router/how-to/share-search-params-across-routes.md diff --git a/docs/router/framework/react/how-to/test-file-based-routing.md b/docs/router/how-to/test-file-based-routing.md similarity index 100% rename from docs/router/framework/react/how-to/test-file-based-routing.md rename to docs/router/how-to/test-file-based-routing.md diff --git a/docs/router/framework/react/how-to/use-environment-variables.md b/docs/router/how-to/use-environment-variables.md similarity index 100% rename from docs/router/framework/react/how-to/use-environment-variables.md rename to docs/router/how-to/use-environment-variables.md diff --git a/docs/router/framework/react/how-to/validate-search-params.md b/docs/router/how-to/validate-search-params.md similarity index 100% rename from docs/router/framework/react/how-to/validate-search-params.md rename to docs/router/how-to/validate-search-params.md diff --git a/docs/router/framework/react/installation/manual.md b/docs/router/installation/manual.md similarity index 58% rename from docs/router/framework/react/installation/manual.md rename to docs/router/installation/manual.md index 6583abf9083..76d1f1fe6a6 100644 --- a/docs/router/framework/react/installation/manual.md +++ b/docs/router/installation/manual.md @@ -8,21 +8,23 @@ To set up TanStack Router manually in a React project, follow the steps below. T #### 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 -``` +Install the necessary core dependencies: + +<!-- ::start:tabs variant="package-managers" --> + +react: @tanstack/react-router @tanstack/react-router-devtools +solid: @tanstack/solid-router @tanstack/solid-router-devtools + +<!-- ::end:tabs --> + +Install the necessary development dependencies: + +<!-- ::start:tabs variant="package-managers" mode="dev-install" --> + +react: @tanstack/router-plugin +solid: @tanstack/router-plugin + +<!-- ::end:tabs --> #### Configure the Vite Plugin @@ -56,9 +58,13 @@ Create the following files: - `src/routes/about.tsx` - `src/main.tsx` -#### `src/routes/__root.tsx` +<!-- ::start:framework --> -```tsx +# React + +<!-- ::start:tabs variant="files" --> + +```tsx title="src/routes/__root.tsx" import { createRootRoute, Link, Outlet } from '@tanstack/react-router' import { TanStackRouterDevtools } from '@tanstack/react-router-devtools' @@ -81,9 +87,7 @@ const RootLayout = () => ( export const Route = createRootRoute({ component: RootLayout }) ``` -#### `src/routes/index.tsx` - -```tsx +```tsx title="src/routes/index.tsx" import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/')({ @@ -99,9 +103,7 @@ function Index() { } ``` -#### `src/routes/about.tsx` - -```tsx +```tsx title="src/routes/about.tsx" import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/about')({ @@ -113,13 +115,7 @@ function 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 +```tsx title="src/main.tsx" import { StrictMode } from 'react' import ReactDOM from 'react-dom/client' import { RouterProvider, createRouter } from '@tanstack/react-router' @@ -149,6 +145,93 @@ if (!rootElement.innerHTML) { } ``` +<!-- ::end:tabs --> + +# Solid + +<!-- ::start:tabs variant="files" --> + +```tsx title="src/routes/__root.tsx" +import { createRootRoute, Link, Outlet } from '@tanstack/solid-router' +import { TanStackRouterDevtools } from '@tanstack/solid-router-devtools' + +const RootLayout = () => ( + <> + <div class="p-2 flex gap-2"> + <Link to="/" class="[&.active]:font-bold"> + Home + </Link>{' '} + <Link to="/about" class="[&.active]:font-bold"> + About + </Link> + </div> + <hr /> + <Outlet /> + <TanStackRouterDevtools /> + </> +) + +export const Route = createRootRoute({ component: RootLayout }) +``` + +```tsx title="src/routes/index.tsx" +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/')({ + component: Index, +}) + +function Index() { + return ( + <div class="p-2"> + <h3>Welcome Home!</h3> + </div> + ) +} +``` + +```tsx title="src/routes/about.tsx" +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/about')({ + component: About, +}) + +function About() { + return <div class="p-2">Hello from About!</div> +} +``` + +```tsx title="src/main.tsx" +/* @refresh reload */ +import { render } from 'solid-js/web' +import { RouterProvider, createRouter } from '@tanstack/solid-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/solid-router' { + interface Register { + router: typeof router + } +} + +// Render the app +const rootElement = document.getElementById('root')! + +render(() => <RouterProvider router={router} />, rootElement) +``` + +<!-- ::end:tabs --> + +<!-- ::end:framework --> + +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`. + If you are working with this pattern you should change the `id` of the root `<div>` on your `index.html` file to `<div id='root'></div>` ## Using Code-Based Route Configuration @@ -156,6 +239,10 @@ If you are working with this pattern you should change the `id` of the root `<di > [!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. +<!-- ::start:framework --> + +# React + ```tsx import { StrictMode } from 'react' import ReactDOM from 'react-dom/client' @@ -228,4 +315,73 @@ if (!rootElement.innerHTML) { } ``` +# Solid + +```tsx +/* @refresh reload */ +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: () => ( + <> + <div class="p-2 flex gap-2"> + <Link to="/" class="[&.active]:font-bold"> + Home + </Link>{' '} + <Link to="/about" class="[&.active]:font-bold"> + About + </Link> + </div> + <hr /> + <Outlet /> + <TanStackRouterDevtools /> + </> + ), +}) + +const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: function Index() { + return ( + <div class="p-2"> + <h3>Welcome Home!</h3> + </div> + ) + }, +}) + +const aboutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/about', + component: function About() { + return <div class="p-2">Hello from About!</div> + }, +}) + +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')! +render(() => <RouterProvider router={router} />, rootElement) +``` + +<!-- ::end:framework --> + 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/solid/installation/with-esbuild.md b/docs/router/installation/with-esbuild.md similarity index 82% rename from docs/router/framework/solid/installation/with-esbuild.md rename to docs/router/installation/with-esbuild.md index ac8044ee82f..b5cad0622c0 100644 --- a/docs/router/framework/solid/installation/with-esbuild.md +++ b/docs/router/installation/with-esbuild.md @@ -2,18 +2,40 @@ title: Installation with Esbuild --- -[//]: # 'BundlerConfiguration' - To use file-based routing with **Esbuild**, you'll need to install the `@tanstack/router-plugin` package. -```sh -npm install -D @tanstack/router-plugin -``` +<!-- ::start:tabs variant="package-manager" mode="dev-install" --> + +react: @tanstack/router-plugin +solid: @tanstack/router-plugin + +<!-- ::end:tabs --> Once installed, you'll need to add the plugin to your configuration. -```tsx -// build.js +<!-- ::start:framework --> + +# React + +```ts title="esbuild.config.js" +import { tanstackRouter } from '@tanstack/router-plugin/esbuild' + +export default { + // ... + plugins: [ + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + }), + ], +} +``` + +Or, you can clone our [Quickstart Esbuild example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-esbuild-file-based) and get started. + +# Solid + +```ts title="build.js" import * as esbuild from 'esbuild' import { solidPlugin } from 'esbuild-plugin-solid' import { tanstackRouter } from '@tanstack/router-plugin/esbuild' @@ -46,9 +68,9 @@ if (isDev) { Or, you can clone our [Quickstart Esbuild example](https://github.com/TanStack/router/tree/main/examples/solid/quickstart-esbuild-file-based) and get started. -Now that you've added the plugin to your Esbuild configuration, you're all set to start using file-based routing with TanStack Router. +<!-- ::end:framework --> -[//]: # 'BundlerConfiguration' +Now that you've added the plugin to your Esbuild configuration, you're all set to start using file-based routing with TanStack Router. ## Ignoring the generated route tree file @@ -96,4 +118,4 @@ When using the TanStack Router Plugin with Esbuild for File-based routing, it co If these defaults work for your project, you don't need to configure anything at all! However, if you need to customize the configuration, you can do so by editing the configuration object passed into the `tanstackRouter` function. -You can find all the available configuration options in the [File-based Routing API Reference](../../../api/file-based-routing.md). +You can find all the available configuration options in the [File-based Routing API Reference](../api/file-based-routing.md). diff --git a/docs/router/framework/react/installation/with-router-cli.md b/docs/router/installation/with-router-cli.md similarity index 80% rename from docs/router/framework/react/installation/with-router-cli.md rename to docs/router/installation/with-router-cli.md index 3f5e616595b..b0363aaf415 100644 --- a/docs/router/framework/react/installation/with-router-cli.md +++ b/docs/router/installation/with-router-cli.md @@ -7,9 +7,12 @@ title: Installation with Router CLI To use file-based routing with the TanStack Router CLI, you'll need to install the `@tanstack/router-cli` package. -```sh -npm install -D @tanstack/router-cli -``` +<!-- ::start:tabs variant="package-manager" mode="dev-install" --> + +react: @tanstack/router-cli +solid: @tanstack/router-cli + +<!-- ::end:tabs --> Once installed, you'll need to amend your scripts in your `package.json` for the CLI to `watch` and `generate` files. @@ -24,6 +27,25 @@ Once installed, you'll need to amend your scripts in your `package.json` for the } ``` +<!-- ::start:framework --> + +# Solid + +If you are using TypeScript, you should also add the following options to your `tsconfig.json`: + +```json +{ + "compilerOptions": { + "jsx": "preserve", + "jsxImportSource": "solid-js" + } +} +``` + +With that, you're all set to start using file-based routing with TanStack Router. + +<!-- ::end:framework --> + [//]: # 'AfterScripts' [//]: # 'AfterScripts' @@ -86,18 +108,34 @@ You can use those settings either at a user level or only for a single workspace When using the TanStack Router CLI for File-based routing, it comes with some sane defaults that should work for most projects: +<!-- ::start:framework --> + +# React + ```json { "routesDirectory": "./src/routes", "generatedRouteTree": "./src/routeTree.gen.ts", "routeFileIgnorePrefix": "-", - "quoteStyle": "single" + "quoteStyle": "single", + "target": "react" } ``` -If these defaults work for your project, you don't need to configure anything at all! However, if you need to customize the configuration, you can do so by creating a `tsr.config.json` file in the root of your project directory. +# Solid + +```json +{ + "routesDirectory": "./src/routes", + "generatedRouteTree": "./src/routeTree.gen.ts", + "routeFileIgnorePrefix": "-", + "quoteStyle": "single", + "target": "solid" +} +``` + +<!-- ::end:framework --> -[//]: # 'TargetConfiguration' -[//]: # 'TargetConfiguration' +If these defaults work for your project, you don't need to configure anything at all! However, if you need to customize the configuration, you can do so by creating a `tsr.config.json` file in the root of your project directory. -You can find all the available configuration options in the [File-based Routing API Reference](../../../api/file-based-routing.md). +You can find all the available configuration options in the [File-based Routing API Reference](../api/file-based-routing.md). diff --git a/docs/router/framework/solid/installation/with-rspack.md b/docs/router/installation/with-rspack.md similarity index 77% rename from docs/router/framework/solid/installation/with-rspack.md rename to docs/router/installation/with-rspack.md index c592bcd22a5..f384831d5a7 100644 --- a/docs/router/framework/solid/installation/with-rspack.md +++ b/docs/router/installation/with-rspack.md @@ -2,18 +2,46 @@ title: Installation with Rspack --- -[//]: # 'BundlerConfiguration' - To use file-based routing with **Rspack** or **Rsbuild**, you'll need to install the `@tanstack/router-plugin` package. -```sh -npm install -D @tanstack/router-plugin -``` +<!-- ::start:tabs variant="package-manager" mode="dev-install" --> + +react: @tanstack/router-plugin +solid: @tanstack/router-plugin + +<!-- ::end:tabs --> Once installed, you'll need to add the plugin to your configuration. -```tsx -// rsbuild.config.ts +<!-- ::start:framework --> + +# React + +```ts title="rsbuild.config.ts" +import { defineConfig } from '@rsbuild/core' +import { pluginReact } from '@rsbuild/plugin-react' +import { tanstackRouter } from '@tanstack/router-plugin/rspack' + +export default defineConfig({ + plugins: [pluginReact()], + tools: { + rspack: { + plugins: [ + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + }), + ], + }, + }, +}) +``` + +Or, you can clone our [Quickstart Rspack/Rsbuild example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-rspack-file-based) and get started. + +# Solid + +```ts title="rsbuild.config.ts" import { defineConfig } from '@rsbuild/core' import { pluginBabel } from '@rsbuild/plugin-babel' import { pluginSolid } from '@rsbuild/plugin-solid' @@ -36,9 +64,9 @@ export default defineConfig({ Or, you can clone our [Quickstart Rspack/Rsbuild example](https://github.com/TanStack/router/tree/main/examples/solid/quickstart-rspack-file-based) and get started. -Now that you've added the plugin to your Rspack/Rsbuild configuration, you're all set to start using file-based routing with TanStack Router. +<!-- ::end:framework --> -[//]: # 'BundlerConfiguration' +Now that you've added the plugin to your Rspack/Rsbuild configuration, you're all set to start using file-based routing with TanStack Router. ## Ignoring the generated route tree file @@ -86,4 +114,4 @@ When using the TanStack Router Plugin with Rspack (or Rsbuild) for File-based ro If these defaults work for your project, you don't need to configure anything at all! However, if you need to customize the configuration, you can do so by editing the configuration object passed into the `tanstackRouter` function. -You can find all the available configuration options in the [File-based Routing API Reference](../../../api/file-based-routing.md). +You can find all the available configuration options in the [File-based Routing API Reference](../api/file-based-routing.md). diff --git a/docs/router/framework/react/installation/with-vite.md b/docs/router/installation/with-vite.md similarity index 78% rename from docs/router/framework/react/installation/with-vite.md rename to docs/router/installation/with-vite.md index 3288c7759d4..64cf2cb2b74 100644 --- a/docs/router/framework/react/installation/with-vite.md +++ b/docs/router/installation/with-vite.md @@ -2,18 +2,22 @@ title: Installation with Vite --- -[//]: # 'BundlerConfiguration' - To use file-based routing with **Vite**, you'll need to install the `@tanstack/router-plugin` package. -```sh -npm install -D @tanstack/router-plugin -``` +<!-- ::start:tabs variant="package-manager" mode="dev-install" --> + +react: @tanstack/router-plugin +solid: @tanstack/router-plugin + +<!-- ::end:tabs --> Once installed, you'll need to add the plugin to your Vite configuration. -```ts -// vite.config.ts +<!-- ::start:framework --> + +# React + +```ts title="vite.config.ts" import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { tanstackRouter } from '@tanstack/router-plugin/vite' @@ -34,12 +38,31 @@ export default defineConfig({ Or, you can clone our [Quickstart Vite example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-file-based) and get started. -> [!WARNING] -> If you are using the older `@tanstack/router-vite-plugin` package, you can still continue to use it, as it will be aliased to the `@tanstack/router-plugin/vite` package. However, we would recommend using the `@tanstack/router-plugin` package directly. +# Solid -Now that you've added the plugin to your Vite configuration, you're all set to start using file-based routing with TanStack Router. +```ts title="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(), + // ... + ], +}) +``` -[//]: # 'BundlerConfiguration' +Or, you can clone our [Quickstart Vite example](https://github.com/TanStack/router/tree/main/examples/solid/quickstart-file-based) and get started. + +<!-- ::end:framework --> + +Now that you've added the plugin to your Vite configuration, you're all set to start using file-based routing with TanStack Router. ## Ignoring the generated route tree file @@ -87,4 +110,4 @@ When using the TanStack Router Plugin with Vite for File-based routing, it comes If these defaults work for your project, you don't need to configure anything at all! However, if you need to customize the configuration, you can do so by editing the configuration object passed into the `tanstackRouter` function. -You can find all the available configuration options in the [File-based Routing API Reference](../../../api/file-based-routing.md). +You can find all the available configuration options in the [File-based Routing API Reference](../api/file-based-routing.md). diff --git a/docs/router/framework/solid/installation/with-webpack.md b/docs/router/installation/with-webpack.md similarity index 81% rename from docs/router/framework/solid/installation/with-webpack.md rename to docs/router/installation/with-webpack.md index ef884aacbfe..ecee96a5ab6 100644 --- a/docs/router/framework/solid/installation/with-webpack.md +++ b/docs/router/installation/with-webpack.md @@ -2,18 +2,39 @@ title: Installation with Webpack --- -[//]: # 'BundlerConfiguration' - To use file-based routing with **Webpack**, you'll need to install the `@tanstack/router-plugin` package. -```sh -npm install -D @tanstack/router-plugin -``` +<!-- ::start:tabs variant="package-manager" mode="dev-install" --> + +react: @tanstack/router-plugin +solid: @tanstack/router-plugin + +<!-- ::end:tabs --> Once installed, you'll need to add the plugin to your configuration. -```tsx -// webpack.config.ts +<!-- ::start:framework --> + +# React + +```ts title="webpack.config.ts" +import { tanstackRouter } from '@tanstack/router-plugin/webpack' + +export default { + plugins: [ + tanstackRouter({ + target: 'react', + autoCodeSplitting: true, + }), + ], +} +``` + +Or, you can clone our [Quickstart Webpack example](https://github.com/TanStack/router/tree/main/examples/react/quickstart-webpack-file-based) and get started. + +# Solid + +```ts title="webpack.config.ts" import { tanstackRouter } from '@tanstack/router-plugin/webpack' export default { @@ -39,9 +60,9 @@ And in the .babelrc (SWC doesn't support solid-js, see [here](https://www.answer Or, for a full webpack.config.js, you can clone our [Quickstart Webpack example](https://github.com/TanStack/router/tree/main/examples/solid/quickstart-webpack-file-based) and get started. -Now that you've added the plugin to your Webpack configuration, you're all set to start using file-based routing with TanStack Router. +<!-- ::end:framework --> -[//]: # 'BundlerConfiguration' +Now that you've added the plugin to your Webpack configuration, you're all set to start using file-based routing with TanStack Router. ## Ignoring the generated route tree file @@ -89,4 +110,4 @@ When using the TanStack Router Plugin with Webpack for File-based routing, it co If these defaults work for your project, you don't need to configure anything at all! However, if you need to customize the configuration, you can do so by editing the configuration object passed into the `tanstackRouter` function. -You can find all the available configuration options in the [File-based Routing API Reference](../../../api/file-based-routing.md). +You can find all the available configuration options in the [File-based Routing API Reference](../api/file-based-routing.md). diff --git a/docs/router/integrations/query.md b/docs/router/integrations/query.md index ab6687a8703..22fbb927c8f 100644 --- a/docs/router/integrations/query.md +++ b/docs/router/integrations/query.md @@ -4,7 +4,7 @@ title: TanStack Query Integration --- > [!IMPORTANT] -> This integration automates SSR dehydration/hydration and streaming between TanStack Router and TanStack Query. If you haven't read the standard [External Data Loading](../framework/react/guide/external-data-loading.md) guide, start there. +> This integration automates SSR dehydration/hydration and streaming between TanStack Router and TanStack Query. If you haven't read the standard [External Data Loading](../guide/external-data-loading.md) guide, start there. ## What you get @@ -17,22 +17,22 @@ title: TanStack Query Integration The TanStack query integration is a separate package that you need to install: -```sh -npm install @tanstack/react-router-ssr-query -# or -pnpm add @tanstack/react-router-ssr-query -# or -yarn add @tanstack/react-router-ssr-query -# or -bun add @tanstack/react-router-ssr-query -``` +<!-- ::start:tabs variant="package-manager" mode="dev-install" --> + +react: @tanstack/react-router-ssr-query +solid: @tanstack/solid-router-ssr-query + +<!-- ::end:tabs --> ## Setup Create your router and wire up the integration. Ensure a fresh `QueryClient` is created per request in SSR environments. -```tsx -// src/router.tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/router.tsx" import { QueryClient } from '@tanstack/react-query' import { createRouter } from '@tanstack/react-router' import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query' @@ -60,6 +60,8 @@ export function getRouter() { } ``` +<!-- ::end:framework --> + By default, the integration wraps your router with a `QueryClientProvider`. If you already provide your own provider, pass `wrapQueryClient: false` and keep your custom wrapper. ## SSR behavior and streaming @@ -83,12 +85,29 @@ const { data } = useSuspenseQuery(postsQuery) const { data, isLoading } = useQuery(postsQuery) ``` +<!-- ::start:framework --> + +# React + +```tsx +// Suspense: executes on server and streams +const { data } = useSuspenseQuery(postsQuery) + +// Non-suspense: executes only on client +const { data, isLoading } = useQuery(postsQuery) +``` + +<!-- ::end:framework --> + ### Preload with a loader and read with a hook Preload critical data in the route `loader` to avoid waterfalls and loading flashes, then read it in the component. The integration ensures server-fetched data is dehydrated and streamed to the client during SSR. -```tsx -// src/routes/posts.tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/posts.tsx" import { queryOptions, useSuspenseQuery, useQuery } from '@tanstack/react-query' import { createFileRoute } from '@tanstack/react-router' @@ -110,11 +129,17 @@ function PostsPage() { } ``` +<!-- ::end:framework --> + ### Prefetching and streaming You can also prefetch with `fetchQuery` or `ensureQueryData` in a loader without consuming the data in a component. If you return the promise directly from the loader, it will be awaited and thus block the SSR request until the query finishes. If you don't await the promise nor return it, the query will be started on the server and will be streamed to the client without blocking the SSR request. -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/users.$id.tsx" import { createFileRoute } from '@tanstack/react-router' import { queryOptions, useQuery } from '@tanstack/react-query' @@ -132,6 +157,8 @@ export const Route = createFileRoute('/user/$id')({ }) ``` +<!-- ::end:framework --> + ## Redirect handling If a query or mutation throws a `redirect(...)`, the integration intercepts it on the client and performs a router navigation. diff --git a/docs/router/framework/react/llm-support.md b/docs/router/llm-support.md similarity index 100% rename from docs/router/framework/react/llm-support.md rename to docs/router/llm-support.md diff --git a/docs/router/framework/react/overview.md b/docs/router/overview.md similarity index 100% rename from docs/router/framework/react/overview.md rename to docs/router/overview.md diff --git a/docs/router/framework/react/quick-start.md b/docs/router/quick-start.md similarity index 69% rename from docs/router/framework/react/quick-start.md rename to docs/router/quick-start.md index 11f861e7907..69ec50c5381 100644 --- a/docs/router/framework/react/quick-start.md +++ b/docs/router/quick-start.md @@ -7,32 +7,22 @@ title: Quick Start The fastest way to get started with TanStack Router is to scaffold a new project. Just run: -[//]: # 'createAppCommand' +<!-- ::start:tabs variant="package-managers" mode="local-install" --> -```sh -npx create-tsrouter-app@latest -``` +react: create-tsrouter-app@latest +solid: create-tsrouter-app@latest --framework solid -[//]: # 'createAppCommand' +<!-- ::end:tabs --> The CLI will guide you through a short series of prompts to customize your setup, including options for: -[//]: # 'CLIPrompts' - - 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: - -```sh -cd your-project-name -npm run dev -``` +Once complete, a new project will be generated with TanStack Router installed and ready to use. > [!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). @@ -45,32 +35,25 @@ TanStack Router supports both file-based and code-based route configurations. Yo 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. -[//]: # 'createAppCommandFileBased' +<!-- ::start:tabs variant="package-manager" mode="local-install" --> -```sh -npx create-tsrouter-app@latest my-app --template file-router -``` +react: create-tsrouter-app@latest my-app --template file-router +solid: create-tsrouter-app@latest my-app --framework solid --template file-router -[//]: # 'createAppCommandFileBased' +<!-- ::end:tabs --> ### Code-Based Route Configuration If you prefer to define routes programmatically, you can use the code-based route configuration. This approach gives you full control over routing logic. -[//]: # 'createAppCommandCodeBased' +<!-- ::start:tabs variant="package-manager" mode="local-install" --> -```sh -npx create-tsrouter-app@latest my-app -``` - -[//]: # 'createAppCommandCodeBased' +react: create-tsrouter-app@latest my-app +solid: create-tsrouter-app@latest my-app --framework solid -With either approach, navigate to your project directory and start the development server: +<!-- ::end:tabs --> -```sh -cd my-app -npm run dev -``` +With either approach, navigate to your project directory and start the development server. ## Existing Project @@ -80,12 +63,18 @@ If you have an existing React project and want to add TanStack Router to it, you Before installing TanStack Router, please ensure your project meets the following requirements: -[//]: # 'Requirements' +<!-- ::start:framework --> + +# React - `react` v18 or later with `createRoot` support. - `react-dom` v18 or later. -[//]: # 'Requirements' +# Solid + +- `solid-js` v1.x.x + +<!-- ::end:framework --> > [!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. @@ -96,25 +85,18 @@ TanStack Router is currently only compatible with React (with ReactDOM) and Soli To install TanStack Router in your project, run the following command using your preferred package manager: -[//]: # 'installCommand' - -```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 -``` +<!-- ::start:tabs variant="package-managers" --> + +react: @tanstack/react-router +solid: @tanstack/solid-router -[//]: # 'installCommand' +<!-- ::end:tabs --> Once installed, you can verify the installation by checking your `package.json` file for the dependency. -[//]: # 'packageJson' +<!-- ::start:framework --> + +# React ```json { @@ -124,4 +106,14 @@ Once installed, you can verify the installation by checking your `package.json` } ``` -[//]: # 'packageJson' +# Solid + +```json +{ + "dependencies": { + "@tanstack/solid-router": "^x.x.x" + } +} +``` + +<!-- ::end:framework --> diff --git a/docs/router/framework/react/routing/code-based-routing.md b/docs/router/routing/code-based-routing.md similarity index 86% rename from docs/router/framework/react/routing/code-based-routing.md rename to docs/router/routing/code-based-routing.md index 06fa3221621..d3102cafd60 100644 --- a/docs/router/framework/react/routing/code-based-routing.md +++ b/docs/router/routing/code-based-routing.md @@ -40,6 +40,10 @@ routes/ And here is a summarized code-based version: +<!-- ::start:framework --> + +# React + ```tsx import { createRootRoute, createRoute } from '@tanstack/react-router' @@ -111,6 +115,81 @@ const filesRoute = createRoute({ }) ``` +# Solid + +```tsx +import { createRootRoute, createRoute } from '@tanstack/solid-router' + +const rootRoute = createRootRoute() + +const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', +}) + +const aboutRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'about', +}) + +const postsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'posts', +}) + +const postsIndexRoute = createRoute({ + getParentRoute: () => postsRoute, + path: '/', +}) + +const postRoute = createRoute({ + getParentRoute: () => postsRoute, + path: '$postId', +}) + +const postEditorRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'posts/$postId/edit', +}) + +const settingsRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'settings', +}) + +const profileRoute = createRoute({ + getParentRoute: () => settingsRoute, + path: 'profile', +}) + +const notificationsRoute = createRoute({ + getParentRoute: () => settingsRoute, + path: 'notifications', +}) + +const pathlessLayoutRoute = createRoute({ + getParentRoute: () => rootRoute, + id: 'pathlessLayout', +}) + +const pathlessLayoutARoute = createRoute({ + getParentRoute: () => pathlessLayoutRoute, + path: 'route-a', +}) + +const pathlessLayoutBRoute = createRoute({ + getParentRoute: () => pathlessLayoutRoute, + path: 'route-b', +}) + +const filesRoute = createRoute({ + getParentRoute: () => rootRoute, + path: 'files/$', +}) +``` + +<!-- ::end:framework --> + ## Anatomy of a Route All other routes other than the root route are configured using the `createRoute` function: @@ -198,6 +277,10 @@ Creating a root route in code-based routing is thankfully the same as doing so i Unlike file-based routing however, you do not need to export the root route if you don't want to. It's certainly not recommended to build an entire route tree and application in a single file (although you can and we do this in the examples to demonstrate routing concepts in brevity). +<!-- ::start:framework --> + +# React + ```tsx // Standard root route import { createRootRoute } from '@tanstack/react-router' @@ -214,6 +297,26 @@ export interface MyRouterContext { const rootRoute = createRootRouteWithContext<MyRouterContext>() ``` +# Solid + +```tsx +// Standard root route +import { createRootRoute } from '@tanstack/solid-router' + +const rootRoute = createRootRoute() + +// Root route with Context +import { createRootRouteWithContext } from '@tanstack/solid-router' +import type { QueryClient } from '@tanstack/solid-query' + +export interface MyRouterContext { + queryClient: QueryClient +} +const rootRoute = createRootRouteWithContext<MyRouterContext>() +``` + +<!-- ::end:framework --> + To learn more about Context in TanStack Router, see the [Router Context](../guide/router-context.md) guide. ## Basic Routes diff --git a/docs/router/framework/react/routing/file-based-routing.md b/docs/router/routing/file-based-routing.md similarity index 96% rename from docs/router/framework/react/routing/file-based-routing.md rename to docs/router/routing/file-based-routing.md index d20827174ef..6be11e55d64 100644 --- a/docs/router/framework/react/routing/file-based-routing.md +++ b/docs/router/routing/file-based-routing.md @@ -111,14 +111,23 @@ To get started with file-based routing, you'll need to configure your project's To enable file-based routing, you'll need to be using React with a supported bundler. See if your bundler is listed in the configuration guides below. -[//]: # 'SupportedBundlersList' +<!-- ::start:framework --> + +# React + +- [Installation with Vite](../installation/with-vite) +- [Installation with Rspack/Rsbuild](../installation/with-rspack) +- [Installation with Webpack](../installation/with-webpack) +- [Installation with Esbuild](../installation/with-esbuild) + +# Solid - [Installation with Vite](../installation/with-vite) - [Installation with Rspack/Rsbuild](../installation/with-rspack) - [Installation with Webpack](../installation/with-webpack) - [Installation with Esbuild](../installation/with-esbuild) -[//]: # 'SupportedBundlersList' +<!-- ::end:framework --> When using TanStack Router's file-based routing through one of the supported bundlers, our plugin will **automatically generate your route configuration through your bundler's dev and build processes**. It is the easiest way to use TanStack Router's route generation features. diff --git a/docs/router/framework/react/routing/file-naming-conventions.md b/docs/router/routing/file-naming-conventions.md similarity index 86% rename from docs/router/framework/react/routing/file-naming-conventions.md rename to docs/router/routing/file-naming-conventions.md index 2425e694a4e..115310f5b3a 100644 --- a/docs/router/framework/react/routing/file-naming-conventions.md +++ b/docs/router/routing/file-naming-conventions.md @@ -4,20 +4,20 @@ title: File Naming Conventions File-based routing requires that you follow a few simple file naming conventions to ensure that your routes are generated correctly. The concepts these conventions enable are covered in detail in the [Route Trees & Nesting](./route-trees.md) guide. -| Feature | Description | -| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| **`__root.tsx`** | The root route file must be named `__root.tsx` and must be placed in the root of the configured `routesDirectory`. | -| **`.` Separator** | Routes can use the `.` character to denote a nested route. For example, `blog.post` will be generated as a child of `blog`. | -| **`$` Token** | Route segments with the `$` token are parameterized and will extract the value from the URL pathname as a route `param`. | -| **`_` Prefix** | Route segments with the `_` prefix are considered to be pathless layout routes and will not be used when matching its child routes against the URL pathname. | -| **`_` Suffix** | Route segments with the `_` suffix exclude the route from being nested under any parent routes. | -| **`-` Prefix** | Files and folders with the `-` prefix are excluded from the route tree. They will not be added to the `routeTree.gen.ts` file and can be used to colocate logic in route folders. | -| **`(folder)` folder name pattern** | A folder that matches this pattern is treated as a **route group**, preventing the folder from being included in the route's URL path. | -| **`[x]` Escaping** | Square brackets escape special characters in filenames that would otherwise have routing meaning. For example, `script[.]js.tsx` becomes `/script.js` and `api[.]v1.tsx` becomes `/api.v1`. | -| **`index` Token** | Route segments ending with the `index` token (before any file extensions) will match the parent route when the URL pathname matches the parent route exactly. This can be configured via the `indexToken` configuration option (supports both strings and regex patterns), see [options](../../../api/file-based-routing.md#indextoken). | -| **`.route.tsx` File Type** | When using directories to organise routes, the `route` suffix can be used to create a route file at the directory's path. For example, `blog.post.route.tsx` or `blog/post/route.tsx` can be used as the route file for the `/blog/post` route. This can be configured via the `routeToken` configuration option (supports both strings and regex patterns), see [options](../../../api/file-based-routing.md#routetoken). | - -> **πŸ’‘ Remember:** The file-naming conventions for your project could be affected by what [options](../../../api/file-based-routing.md) are configured. +| Feature | Description | +| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **`__root.tsx`** | The root route file must be named `__root.tsx` and must be placed in the root of the configured `routesDirectory`. | +| **`.` Separator** | Routes can use the `.` character to denote a nested route. For example, `blog.post` will be generated as a child of `blog`. | +| **`$` Token** | Route segments with the `$` token are parameterized and will extract the value from the URL pathname as a route `param`. | +| **`_` Prefix** | Route segments with the `_` prefix are considered to be pathless layout routes and will not be used when matching its child routes against the URL pathname. | +| **`_` Suffix** | Route segments with the `_` suffix exclude the route from being nested under any parent routes. | +| **`-` Prefix** | Files and folders with the `-` prefix are excluded from the route tree. They will not be added to the `routeTree.gen.ts` file and can be used to colocate logic in route folders. | +| **`(folder)` folder name pattern** | A folder that matches this pattern is treated as a **route group**, preventing the folder from being included in the route's URL path. | +| **`[x]` Escaping** | Square brackets escape special characters in filenames that would otherwise have routing meaning. For example, `script[.]js.tsx` becomes `/script.js` and `api[.]v1.tsx` becomes `/api.v1`. | +| **`index` Token** | Route segments ending with the `index` token (before any file extensions) will match the parent route when the URL pathname matches the parent route exactly. This can be configured via the `indexToken` configuration option (supports both strings and regex patterns), see [options](../api/file-based-routing.md#indextoken). | +| **`.route.tsx` File Type** | When using directories to organise routes, the `route` suffix can be used to create a route file at the directory's path. For example, `blog.post.route.tsx` or `blog/post/route.tsx` can be used as the route file for the `/blog/post` route. This can be configured via the `routeToken` configuration option (supports both strings and regex patterns), see [options](../api/file-based-routing.md#routetoken). | + +> **πŸ’‘ Remember:** The file-naming conventions for your project could be affected by what [options](../api/file-based-routing.md) are configured. ## Dynamic Path Params diff --git a/docs/router/framework/react/routing/route-matching.md b/docs/router/routing/route-matching.md similarity index 100% rename from docs/router/framework/react/routing/route-matching.md rename to docs/router/routing/route-matching.md diff --git a/docs/router/framework/react/routing/route-trees.md b/docs/router/routing/route-trees.md similarity index 100% rename from docs/router/framework/react/routing/route-trees.md rename to docs/router/routing/route-trees.md diff --git a/docs/router/framework/react/routing/routing-concepts.md b/docs/router/routing/routing-concepts.md similarity index 74% rename from docs/router/framework/react/routing/routing-concepts.md rename to docs/router/routing/routing-concepts.md index dfb6bc91af9..bcd507a45fd 100644 --- a/docs/router/framework/react/routing/routing-concepts.md +++ b/docs/router/routing/routing-concepts.md @@ -10,7 +10,11 @@ Each of these concepts is useful and powerful, and we'll dive into each of them All other routes, other than the [Root Route](#the-root-route), are configured using the `createFileRoute` function, which provides type safety when using file-based routing: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/index.tsx" import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/')({ @@ -18,6 +22,18 @@ export const Route = createFileRoute('/')({ }) ``` +# Solid + +```tsx title="src/routes/index.tsx" +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/')({ + component: PostsComponent, +}) +``` + +<!-- ::end:framework --> + The `createFileRoute` function takes a single argument, the file-route's path as a string. **❓❓❓ "Wait, you're making me pass the path of the route file to `createFileRoute`?"** @@ -43,6 +59,10 @@ Even though it doesn't have a path, the root route has access to all of the same To create a root route, call the `createRootRoute()` function and export it as the `Route` variable in your route file: +<!-- ::start:framework --> + +# React + ```tsx // Standard root route import { createRootRoute } from '@tanstack/react-router' @@ -59,6 +79,26 @@ export interface MyRouterContext { export const Route = createRootRouteWithContext<MyRouterContext>() ``` +# Solid + +```tsx +// Standard root route +import { createRootRoute } from '@tanstack/solid-router' + +export const Route = createRootRoute() + +// Root route with Context +import { createRootRouteWithContext } from '@tanstack/solid-router' +import type { QueryClient } from '@tanstack/solid-query' + +export interface MyRouterContext { + queryClient: QueryClient +} +export const Route = createRootRouteWithContext<MyRouterContext>() +``` + +<!-- ::end:framework --> + To learn more about Context in TanStack Router, see the [Router Context](../guide/router-context.md) guide. ## Basic Routes @@ -67,8 +107,11 @@ Basic routes match a specific path, for example `/about`, `/settings`, `/setting Let's take a look at an `/about` route: -```tsx -// about.tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/about.tsx" import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/about')({ @@ -80,6 +123,22 @@ function AboutComponent() { } ``` +# Solid + +```tsx title="src/routes/about.tsx" +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/about')({ + component: AboutComponent, +}) + +function AboutComponent() { + return <div>About</div> +} +``` + +<!-- ::end:framework --> + Basic routes are simple and straightforward. They match the path exactly and render the provided component. ## Index Routes @@ -88,8 +147,11 @@ Index routes specifically target their parent route when it is **matched exactly Let's take a look at an index route for a `/posts` URL: -```tsx -// posts.index.tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/posts.index.tsx" import { createFileRoute } from '@tanstack/react-router' // Note the trailing slash, which is used to target index routes @@ -102,6 +164,23 @@ function PostsIndexComponent() { } ``` +# Solid + +```tsx title="src/routes/posts.index.tsx" +import { createFileRoute } from '@tanstack/solid-router' + +// Note the trailing slash, which is used to target index routes +export const Route = createFileRoute('/posts/')({ + component: PostsIndexComponent, +}) + +function PostsIndexComponent() { + return <div>Please select a post!</div> +} +``` + +<!-- ::end:framework --> + This route will be matched when the URL is `/posts` exactly. ## Dynamic Route Segments @@ -110,7 +189,11 @@ Route path segments that start with a `$` followed by a label are dynamic and ca These params are then usable in your route's configuration and components! Let's look at a `posts.$postId.tsx` route: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/posts.tsx" import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/posts/$postId')({ @@ -127,6 +210,27 @@ function PostComponent() { } ``` +# Solid + +```tsx title="src/routes/posts.tsx" +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/posts/$postId')({ + // In a loader + loader: ({ params }) => fetchPost(params.postId), + // Or in a component + component: PostComponent, +}) + +function PostComponent() { + // In a component! + const { postId } = Route.useParams() + return <div>Post ID: {postId()}</div> +} +``` + +<!-- ::end:framework --> + > 🧠 Dynamic segments work at **each** segment of the path. For example, you could have a route with the path of `/posts/$postId/$revisionId` and each `$` segment would be captured into the `params` object. ## Splat / Catch-All Routes @@ -149,8 +253,12 @@ For example, a route targeting the `files/$` path is a splat route. If the URL p Optional path parameters allow you to define route segments that may or may not be present in the URL. They use the `{-$paramName}` syntax and provide flexible routing patterns where certain parameters are optional. -```tsx -// posts.{-$category}.tsx - Optional category parameter +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/posts.{-$category}.tsx" +// The `-$category` segment is optional, so this route matches both `/posts` and `/posts/tech` import { createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/posts/{-$category}')({ @@ -164,17 +272,55 @@ function PostsComponent() { } ``` +# Solid + +```tsx title="src/routes/posts.{-$category}.tsx" +// The `-$category` segment is optional, so this route matches both `/posts` and `/posts/tech` +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/posts/{-$category}')({ + component: PostsComponent, +}) + +function PostsComponent() { + const { category } = Route.useParams() + + return <div>{category ? `Posts in ${category()}` : 'All Posts'}</div> +} +``` + +<!-- ::end:framework --> + This route will match both `/posts` (category is `undefined`) and `/posts/tech` (category is `"tech"`). You can also define multiple optional parameters in a single route: -```tsx -// posts.{-$category}.{-$slug}.tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/posts.{-$category}.${-$slug}.tsx" +// The `-$category` segment is optional, so this route matches both `/posts` and `/posts/tech` +import { createFileRoute } from '@tanstack/react-router' + export const Route = createFileRoute('/posts/{-$category}/{-$slug}')({ component: PostsComponent, }) ``` +# Solid + +```tsx title="src/routes/posts.{-$category}.${-$slug}.tsx" +// The `-$category` segment is optional, so this route matches both `/posts` and `/posts/tech` +import { createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/posts/{-$category}/{-$slug}')({ + component: PostsComponent, +}) +``` + +<!-- ::end:framework --> + This route matches `/posts`, `/posts/tech`, and `/posts/tech/hello-world`. > 🧠 Routes with optional parameters are ranked lower in priority than exact matches, ensuring that more specific routes like `/posts/featured` are matched before `/posts/{-$category}`. @@ -203,7 +349,11 @@ In the tree above, `app.tsx` is a layout route that wraps two child routes, `app This tree structure is used to wrap the child routes with a layout component: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/app.tsx" import { Outlet, createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/app')({ @@ -220,6 +370,27 @@ function AppLayoutComponent() { } ``` +# Solid + +```tsx title="src/routes/app.tsx" +import { Outlet, createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/app')({ + component: AppLayoutComponent, +}) + +function AppLayoutComponent() { + return ( + <div> + <h1>App Layout</h1> + <Outlet /> + </div> + ) +} +``` + +<!-- ::end:framework --> + The following table shows which component(s) will be rendered based on the URL: | URL Path | Component | @@ -274,7 +445,11 @@ In the tree above, `_pathlessLayout.tsx` is a pathless layout route that wraps t The `_pathlessLayout.tsx` route is used to wrap the child routes with a Pathless layout component: -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/_pathlessLayout.tsx" import { Outlet, createFileRoute } from '@tanstack/react-router' export const Route = createFileRoute('/_pathlessLayout')({ @@ -291,6 +466,27 @@ function PathlessLayoutComponent() { } ``` +# Solid + +```tsx title="src/routes/_pathlessLayout.tsx" +import { Outlet, createFileRoute } from '@tanstack/solid-router' + +export const Route = createFileRoute('/_pathlessLayout')({ + component: PathlessLayoutComponent, +}) + +function PathlessLayoutComponent() { + return ( + <div> + <h1>Pathless layout</h1> + <Outlet /> + </div> + ) +} +``` + +<!-- ::end:framework --> + The following table shows which component will be rendered based on the URL: | URL Path | Component | @@ -370,7 +566,11 @@ routes/ We can import from the excluded files into our posts route -```tsx +<!-- ::start:framework --> + +# React + +```tsx title="src/routes/posts.tsx" import { createFileRoute } from '@tanstack/react-router' import { PostsTable } from './-posts-table' import { PostsHeader } from './-components/header' @@ -394,6 +594,34 @@ function PostComponent() { } ``` +# Solid + +```tsx title="src/routes/posts.tsx" +import { createFileRoute } from '@tanstack/solid-router' +import { PostsTable } from './-posts-table' +import { PostsHeader } from './-components/header' +import { PostsFooter } from './-components/footer' + +export const Route = createFileRoute('/posts')({ + loader: () => fetchPosts(), + component: PostComponent, +}) + +function PostComponent() { + const posts = Route.useLoaderData() + + return ( + <div> + <PostsHeader /> + <PostsTable posts={posts} /> + <PostsFooter /> + </div> + ) +} +``` + +<!-- ::end:framework --> + The excluded files will not be added to `routeTree.gen.ts`. ## Pathless Route Group Directories diff --git a/docs/router/framework/react/routing/virtual-file-routes.md b/docs/router/routing/virtual-file-routes.md similarity index 90% rename from docs/router/framework/react/routing/virtual-file-routes.md rename to docs/router/routing/virtual-file-routes.md index a285a49223a..79754685e17 100644 --- a/docs/router/framework/react/routing/virtual-file-routes.md +++ b/docs/router/routing/virtual-file-routes.md @@ -49,8 +49,11 @@ Virtual file routes can be configured either via: If you're using the `TanStackRouter` plugin for Vite/Rspack/Webpack, you can configure virtual file routes by passing the path of your routes file to the `virtualRoutesConfig` option when setting up the plugin: -```tsx -// vite.config.ts +<!-- ::start:framework --> + +# React + +```tsx title="vite.config.ts" import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' import { tanstackRouter } from '@tanstack/router-plugin/vite' @@ -66,6 +69,26 @@ export default defineConfig({ }) ``` +# Solid + +```tsx title="vite.config.ts" +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' +import { tanstackRouter } from '@tanstack/router-plugin/vite' + +export default defineConfig({ + plugins: [ + tanstackRouter({ + target: 'solid', + virtualRouteConfig: './routes.ts', + }), + solid(), + ], +}) +``` + +<!-- ::end:framework --> + Or, you choose to define the virtual routes directly in the configuration: ```tsx @@ -84,6 +107,48 @@ export default defineConfig({ }) ``` +<!-- ::start:framework --> + +# React + +```tsx title="vite.config.ts" +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' +import { tanstackRouter } from '@tanstack/router-plugin/vite' + +const routes = rootRoute('root.tsx', [ + // ... the rest of your virtual route tree +]) + +export default defineConfig({ + plugins: [ + tanstackRouter({ virtualRouteConfig: routes, target: 'react' }), + react(), + ], +}) +``` + +# Solid + +```tsx title="vite.config.ts" +import { defineConfig } from 'vite' +import solid from 'vite-plugin-solid' +import { tanstackRouter } from '@tanstack/router-plugin/vite' + +const routes = rootRoute('root.tsx', [ + // ... the rest of your virtual route tree +]) + +export default defineConfig({ + plugins: [ + tanstackRouter({ virtualRouteConfig: routes, target: 'solid' }), + solid(), + ], +}) +``` + +<!-- ::end:framework --> + ## Creating Virtual File Routes To create virtual file routes, you'll need to import the `@tanstack/virtual-file-routes` package. This package provides a set of functions that allow you to create virtual routes that reference real files in your project. A few utility functions are exported from the package: diff --git a/docs/start/framework/react/guide/isr.md b/docs/start/framework/react/guide/isr.md index a2a68008dea..ad1e3221305 100644 --- a/docs/start/framework/react/guide/isr.md +++ b/docs/start/framework/react/guide/isr.md @@ -442,5 +442,5 @@ Track key metrics: - [Static Prerendering](./static-prerendering.md) - Build-time page generation - [Hosting](./hosting.md) - CDN deployment configurations - [Server Functions](./server-functions.md) - Creating dynamic data endpoints -- [Data Loading](../../../../router/framework/react/guide/data-loading.md) - Client-side cache control +- [Data Loading](../../../../router/guide/data-loading.md) - Client-side cache control - [Middleware](./middleware.md) - Request/response customization diff --git a/scripts/llms-generate.mjs b/scripts/llms-generate.mjs index 16129a8a4c5..a6debd16d0c 100755 --- a/scripts/llms-generate.mjs +++ b/scripts/llms-generate.mjs @@ -20,36 +20,36 @@ const RULES_DIR = './llms/rules' const packages = { 'react-router': [ { - paths: [`${DOCS_DIR}/router/framework/react/api/router`], + paths: [`${DOCS_DIR}/router/api/router`], description: 'TanStack Router: API', name: 'api', globs: ['src/**/*.ts', 'src/**/*.tsx'], }, { - paths: [`${DOCS_DIR}/router/framework/react/guide`], + paths: [`${DOCS_DIR}/router/guide`], description: 'TanStack Router: Guide', name: 'guide', globs: ['src/**/*.ts', 'src/**/*.tsx'], }, { - paths: [`${DOCS_DIR}/router/framework/react/routing`], + paths: [`${DOCS_DIR}/router/routing`], description: 'TanStack Router: Routing', name: 'routing', globs: ['src/**/*.ts', 'src/**/*.tsx'], }, { - paths: [`${DOCS_DIR}/router/framework/react/installation`], + paths: [`${DOCS_DIR}/router/installation`], description: 'TanStack Router: Installation', name: 'installation', globs: ['src/**/*.ts', 'src/**/*.tsx'], }, { paths: [ - `${DOCS_DIR}/router/framework/react/overview.md`, - `${DOCS_DIR}/router/framework/react/quick-start.md`, - `${DOCS_DIR}/router/framework/react/decisions-on-dx.md`, - `${DOCS_DIR}/router/framework/react/devtools.md`, - `${DOCS_DIR}/router/framework/react/faq.md`, + `${DOCS_DIR}/router/overview.md`, + `${DOCS_DIR}/router/quick-start.md`, + `${DOCS_DIR}/router/decisions-on-dx.md`, + `${DOCS_DIR}/router/devtools.md`, + `${DOCS_DIR}/router/faq.md`, ], description: 'TanStack Router: Setup and Architecture', name: 'setup-and-architecture',