SuperAPI AI Directory

setup-tanstack-router

Frontend & websites

View repository

Updated 10-16 07:27

About this skill

TanStack Router Enforcement

What This Catches

  • Ban react-router-dom imports
  • Ban window.location navigation (block) + reads (warn)
  • Warn window.location.reload() -- suggest router.invalidate()
  • Ban strict: false in router hooks
  • Ban untyped useParams()/useSearch()/useLoaderData()/useRouteContext() missing { from }
  • Ban URLSearchParams -- suggest nuqs
  • Warn exported components from route files (break code splitting)
  • Require validateSearch when useSearch in route files
  • Warn when Query-primed loaders are consumed via useLoaderData instead of useQuery/useSuspenseQuery
  • Warn when router uses queryClient context without defaultPreloadStaleTime: 0
  • Warn when router uses queryClient context without createRootRouteWithContext

Auto-regen route tree on route file change.

TanStack Router + Query

When a route needs server data, prefer this ownership split:

  • Router loader: start fetching early after navigation intent.
  • TanStack Query: own cache, refetch, invalidation, and garbage collection.
  • Component: read via 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.

Customization

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

Type-Safe Search Params with nuqs

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.