From frontend-skills
Auto-generates TanStack Router route trees, enforces router patterns via PostToolUse hooks, and bans react-router-dom, window.location, and untyped hooks.
How this skill is triggered — by the user, by Claude, or both
Slash command
/frontend-skills:setup-tanstack-router**/routes/**/*.tsx**/routes/**/*.tsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
- Ban `react-router-dom` imports
react-router-dom importswindow.location navigation (block) + reads (warn)window.location.reload() -- suggest router.invalidate()strict: false in router hooksuseParams()/useSearch()/useLoaderData()/useRouteContext() missing { from }URLSearchParams -- suggest nuqsvalidateSearch when useSearch in route filesuseLoaderData instead of useQuery/useSuspenseQueryqueryClient context without defaultPreloadStaleTime: 0queryClient context without createRootRouteWithContextAuto-regen route tree on route file change.
When a route needs server data, prefer this ownership split:
useQuery() or useSuspenseQuery() so Query has an active observer.Do not enforce suspense globally. Choose per field/page:
useSuspenseQuery() for blocking, page-critical data that should use route pending/error boundaries.useQuery() for deferred or secondary data with inline loading/empty/error states.export const Route = createFileRoute('/dashboards/$dashboardId')({
loader: ({ context, params }) => {
context.queryClient.prefetchQuery(dashboardQueryOptions(params.dashboardId))
},
component: Dashboard,
})
function Dashboard() {
const params = Route.useParams()
const dashboard = useSuspenseQuery(dashboardQueryOptions(params.dashboardId))
const widgetCount = useQuery(widgetCountQueryOptions(params.dashboardId))
return <DashboardView dashboard={dashboard.data} widgetCount={widgetCount.data} />
}
Router setup when Query owns cache:
const rootRoute = createRootRouteWithContext<{ queryClient: QueryClient }>()({
component: RootLayout,
})
const router = createRouter({
routeTree,
context: { queryClient },
defaultPreloadStaleTime: 0,
defaultPendingComponent: DefaultLoader,
defaultErrorComponent: DefaultError,
})
Avoid Route.useLoaderData() for Query-loaded data. It bypasses Query observers, so focus refetch, invalidation refetch, and cache retention can behave surprisingly.
Routes dir pattern default /routes/. Update grep pattern in hook scripts if project use different convention:
if ! echo "$file_path" | grep -qE '/routes/'; then # default
if ! echo "$file_path" | grep -qE '/pages/'; then # pages-based
if ! echo "$file_path" | grep -qE '/app/routes/'; then # nested
import { useQueryState, parseAsInteger, parseAsString } from 'nuqs'
function UsersPage() {
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1))
const [filter, setFilter] = useQueryState('filter', parseAsString)
}
Setup (install, config, verify): see SETUP.md.
npx claudepluginhub redpanda-data/ui-harness --plugin frontend-skillsGuides TanStack Router setup in React: file-based routing, type-safe search/path params, data loaders, auth protection, code splitting, SSR, error handling, testing, and bundler config.
TanStack Router - 100% type-safe routing, file-based routes, loaders, search params. Use when implementing routing in React apps (NOT Next.js).
Provides type-safe routing for React and Solid apps with file-based routes, search params, data loading, code splitting, and Vite plugin support.