MCPInstall

solid-router

Solid bindings for TanStack Router: RouterProvider, useRouter, useRouterState, useMatch, useMatches, useLocation, useSearch, useParams, useNavigate, useLoaderData, useLoaderDeps, useRouteContext, useBlocker, useCanGoBack, Link, Navigate, Outlet, CatchBoundary, ErrorComponent. Solid-specific patterns with Accessor returns, createSignal/createMemo/createEffect, Show/Switch/Match/Dynamic, and @solidjs/meta for head management.

Installs
58
Last 24h
—
Trending rank
—
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://github.com/tanstack/router --skill solid-router
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads solid-router automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Solid Router (@tanstack/solid-router)

This skill builds on router-core. Read @tanstack/router-core#router-core first for foundational concepts.

CRITICAL: TanStack Router types are FULLY INFERRED. Never cast, never annotate inferred values. CRITICAL: TanStack Router is CLIENT-FIRST. Loaders run on the client by default, not on the server. CRITICAL: Most hooks return Accessor<T> — you MUST call the accessor (value()) to read the reactive value. This is the #1 difference from the React version. CRITICAL: Do not confuse @tanstack/solid-router with @solidjs/router. They are completely different libraries with different APIs.

Full Setup: File-Based Routing with Vite

1. Install Dependencies

npm install @tanstack/solid-router
npm install -D @tanstack/router-plugin @tanstack/solid-router-devtools

2. Configure Vite Plugin

// vite.config.ts
import { defineConfig } from 'vite'
import solidPlugin from 'vite-plugin-solid'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    // MUST come before solid plugin
    tanstackRouter({
      target: 'solid',
      autoCodeSplitting: true,
    }),
    solidPlugin(),
  ],
})

3. Create Root Route

// src/routes/__root.tsx
import { createRootRoute, Link, Outlet } from '@tanstack/solid-router'

export const Route = createRootRoute({
  component: RootLayout,
})

function RootLayout() {
  return (
    <>
      <nav>
        <Link to="/" activeClass="font-bold">
          Home
        </Link>
        <Link to="/about" activeClass="font-bold">
          About
        </Link>
      </nav>
      <hr />
      <Outlet />
    </>
  )
}

4. Create Route Files

// src/routes/index.tsx
import { createFileRoute } from '@tanstack/solid-router'

export const Route = createFileRoute('/')({
  component: HomePage,
})

function HomePage() {
  return <h1>Welcome Home</h1>
}

5. Create Router Instance and Register Types

// src/main.tsx
import { render } from 'solid-js/web'
import { RouterProvider, createRouter } from '@tanstack/solid-router'
import { routeTree } from './routeTree.gen'

const router = createRouter({ routeTree })

// REQUIRED — without this, Link/useNavigate/useSearch have no type safety
declare module '@tanstack/solid-router' {
  interface Register {
    router: typeof router
  }
}

render(
  () => <RouterProvider router={router} />,
  document.getElementById('root')!,
)

Hooks Reference

All hooks imported from @tanstack/solid-router. Most return Accessor<T> — call the result to read the value.

useRouter() — returns TRouter (NOT an Accessor)

import { useRouter } from '@tanstack/solid-router'

function InvalidateButton() {
  const router = useRouter()
  return <button onClick={() => router.invalidate()}>Refresh data</button>
}

useRouterState() — returns Accessor<T>

Exposes the entire state and thus incurs a performance cost. For matches or location favor useMatches and useLocation.

import { useRouterState } from '@tanstack/solid-router'

function LoadingIndicator() {
  const isLoading = useRouterState({ select: (s) => s.isLoading })
  return (
    <Show when={isLoading()}>
      <div>Loading...</div>
    </Show>
  )
}

useNavigate() — returns a function (NOT an Accessor)

import { useNavigate } from '@tanstack/solid-router'

function AfterSubmit() {
  const navigate = useNavigate()

  const handleSubmit = async () => {
    await saveData()
    navigate({ to: '/posts/$postId', params: { postId: '123' } })
  }

  return <button onClick={handleSubmit}>Save</button>
}

useSearch({ from }) — returns Accessor<T>

import { useSearch } from '@tanstack/solid-router'

function Pagination() {
  const search = useSearch({ from: '/products' })
  return <span>Page {search().page}</span>
}

useParams({ from }) — returns Accessor<T>

import { useParams } from '@tanstack/solid-router'

function PostHeader() {
  const params = useParams({ from: '/posts/$postId' })
  return <h2>Post {params().postId}</h2>
}

useLoaderData({ from }) — returns Accessor<T>

import { useLoaderData } from '@tanstack/solid-router'

function PostContent() {
  const data = useLoaderData({ from: '/posts/$postId' })
  return <article>{data().post.content}</article>
}

useMatch({ from }) — returns Accessor<T>

import { useMatch } from '@tanstack/solid-router'

function PostDetails() {
  const match = useMatch({ from: '/posts/$postId' })
  return <div>{match().loaderData.post.title}</div>
}

Other Hooks

All imported from @tanstack/solid-router:

  • useMatches() — Accessor<Array<Match>>, all active route matches
  • useParentMatches() — Accessor<Array<Match>>, parent route matches
  • useChildMatches() — Accessor<Array<Match>>, child route matches
  • useRouteContext({ from }) — Accessor<T>, context from beforeLoad
  • useLoaderDeps({ from }) — Accessor<T>, loader dependency values
  • useBlocker({ shouldBlockFn }) — blocks navigation for unsaved changes
  • useCanGoBack() — Accessor<boolean>
  • useLocation() — Accessor<ParsedLocation>
  • useLinkProps({ to, params?, search? }) — returns ComponentProps<'a'> (NOT an Accessor)
  • useMatchRoute() — returns a function; calling it returns Accessor<false | Params>
  • useHydrated() — Accessor<boolean>

Components Reference

RouterProvider

<RouterProvider router={router} />

Link

Type-safe navigation link. Children can be a function for active state:

;<Link to="/posts/$postId" params={{ postId: '42' }}>
  View Post
</Link>

{
  /* Function children for active state */
}
;<Link to="/about">
  {(state) => <span classList={{ active: state.isActive }}>About</span>}
</Link>

Outlet

Renders the matched child route component:

function Layout() {
  return (
    <div>
      <Sidebar />
      <main>
        <Outlet />
      </main>
    </div>
  )
}

Navigate

Declarative redirect (triggers navigation in onMount):

import { Navigate } from '@tanstack/solid-router'

function OldPage() {
  return <Navigate to="/new-page" />
}

Await

Renders deferred data with Solid's Suspense:

import { Await } from '@tanstack/solid-router'
import { Suspense } from 'solid-js'

function PostWithComments() {
  const data = Route.useLoaderData()
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Await promise={data().deferredComments}>
        {(comments) => <For each={comments}>{(c) => <li>{c.text}</li>}</For>}
      </Await>
    </Suspense>
  )
}

CatchBoundary

Error boundary wrapping Solid.ErrorBoundary:

import { CatchBoundary } from '@tanstack/solid-router'
;<CatchBoundary
  getResetKey={() => 'widget'}
  errorComponent={({ error }) => <div>Error: {error.message}</div>}
>
  <RiskyWidget />
</CatchBoundary>

Other Components

  • CatchNotFound — catches notFound() errors in children; fallback receives the error data
  • Block — declarative navigation blocker; use shouldBlockFn and withResolver for custom UI
  • ScrollRestoration — deprecated; use createRouter's scrollRestoration: true option instead
  • ClientOnly — renders children only after hydration; accepts fallback prop

Block

Declarative navigation blocker component:

import { Block } from '@tanstack/solid-router'
;<Block shouldBlockFn={() => formIsDirty()} withResolver>
  {({ status, proceed, reset }) => (
    <Show when={status === 'blocked'}>
      <div>
        <p>Are you sure?</p>
        <button onClick={proceed}>Yes</button>
        <button onClick={reset}>No</button>
      </div>
    </Show>
  )}
</Block>

ScrollRestoration

Restores scroll position on navigation:

import { ScrollRestoration } from '@tanstack/solid-router'
// In root route component
;<ScrollRestoration />

ClientOnly

Renders children only after hydration:

import { ClientOnly } from '@tanstack/solid-router'
;<ClientOnly fallback={<div>Loading...</div>}>
  <BrowserOnlyWidget />
</ClientOnly>

Head Management

Uses @solidjs/meta under the hood:

import { HeadContent, Scripts } from '@tanstack/solid-router'

function RootDocument(props) {
  return (
    <html>
      <head>
        <HeadContent />
      </head>
      <body>
        {props.children}
        <Scripts />
      </body>
    </html>
  )
}

Solid-Specific Patterns

Custom Link Component with createLink

import { createLink } from '@tanstack/solid-router'

const StyledLinkComponent = (props) => (
  <a {...props} class={`styled-link ${props.class ?? ''}`} />
)

const StyledLink = createLink(StyledLinkComponent)

function Nav() {
  return (
    <StyledLink to="/posts/$postId" params={{ postId: '42' }}>
      Post
    </StyledLink>
  )
}

Using Solid Primitives with Router State

import { createMemo, Show, For } from 'solid-js'
import { useRouterState } from '@tanstack/solid-router'

function Breadcrumbs() {
  const matches = useRouterState({ select: (s) => s.matches })
  const crumbs = createMemo(() =>
    matches().filter((m) => m.context?.breadcrumb),
  )

  return (
    <nav>
      <For each={crumbs()}>
        {(match) => <span>{match.context.breadcrumb}</span>}
      </For>
    </nav>
  )
}

Auth with Router Context

import { createRootRouteWithContext } from '@tanstack/solid-router'

const rootRoute = createRootRouteWithContext<{ auth: AuthState }>()({
  component: RootComponent,
})

// In main.tsx — provide context at router creation
const router = createRouter({
  routeTree,
  context: { auth: authState },
})

// In a route — access via beforeLoad (NOT hooks)
beforeLoad: ({ context }) => {
  if (!context.auth.isAuthenticated) {
    throw redirect({ to: '/login' })
  }
}

Common Mistakes

1. CRITICAL: Forgetting to call Accessor

Hooks return Accessor<T> — you must call them to read the value. This is the #1 migration issue from React.

// WRONG — comparing the accessor function, not its value
const params = useParams({ from: '/posts/$postId' })
if (params.postId === '42') { ... } // params is a function!

// CORRECT — call the accessor
const params = useParams({ from: '/posts/$postId' })
if (params().postId === '42') { ... }

2. HIGH: Destructuring reactive values

Destructuring breaks Solid's reactivity tracking.

// WRONG — loses reactivity
const { page } = useSearch({ from: '/products' })()

// CORRECT — access through accessor
const search = useSearch({ from: '/products' })
<span>Page {search().page}</span>

3. HIGH: Using React hooks in beforeLoad or loader

beforeLoad and loader are NOT components — they are plain async functions. No hooks (React or Solid) can be used in them. Pass state via router context instead.

4. MEDIUM: Wrong plugin target

Must set target: 'solid' in the router plugin config. Default is 'react'.

Cross-References

  • @tanstack/router-core#router-core — all sub-skills for domain-specific patterns (search params, data loading, navigation, auth, SSR, etc.)

FAQ

What does the solid-router skill do?

Solid bindings for TanStack Router: RouterProvider, useRouter, useRouterState, useMatch, useMatches, useLocation, useSearch, useParams, useNavigate, useLoaderData, useLoaderDeps, useRouteContext, useBlocker, useCanGoBack, Link, Navigate, Outlet, CatchBoundary, ErrorComponent. Solid-specific patterns with Accessor returns, createSignal/createMemo/createEffect, Show/Switch/Match/Dynamic, and @solidjs/meta for head management.

How do I install solid-router?

Run npx skills add https://github.com/tanstack/router --skill solid-router in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/solid-router/ (personal) or .claude/skills/solid-router/ (project).

Which AI agents can use solid-router?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is solid-router safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains solid-router?

It is published by tanstack in tanstack/router. It has 58 recorded installs.

More from tanstack/router

View all →

router-core

tanstack/router

Framework-agnostic core concepts for TanStack Router: route trees, createRouter, createRoute, createRootRoute, createRootRouteWithContext, addChildren, Register type declaration, route matching, route sorting, file naming conventions. Entry point for all router skills.

602 installs

react-router

tanstack/router

React bindings for TanStack Router: RouterProvider, useRouter, useRouterState, useMatch, useMatches, useLocation, useSearch, useParams, useNavigate, useLoaderData, useLoaderDeps, useRouteContext, useBlocker, useCanGoBack, Link, Navigate, Outlet, CatchBoundary, ErrorComponent. React-specific patterns for hooks, providers, SSR hydration, and createLink with forwardRef.

Frontend & Design559 installs

react-start

tanstack/router

React bindings for TanStack Start: createStart, StartClient, StartServer, React-specific imports, re-exports from @tanstack/react-router, full project setup with React, useServerFn hook.

Frontend & Design465 installs

start-core

tanstack/router

Core overview for TanStack Start: tanstackStart() Vite plugin, getRouter() factory, root route document shell (HeadContent, Scripts, Outlet), client/server entry points, routeTree.gen.ts, tsconfig configuration. Entry point for all Start skills.

Backend & APIs457 installs

router-query

tanstack/router

Integrating TanStack Router with TanStack Query: queryClient in router context, static queries and fire-and-forget queries in loaders, useSuspenseQuery in components, defaultPreloadStaleTime: 0, setupRouterSsrQueryIntegration for SSR dehydration/hydration and streaming, per-request QueryClient isolation.

375 installs

start-server-core

tanstack/router

Server-side runtime for TanStack Start: createStartHandler, request/response utilities (getRequest, setResponseHeader, setCookie, getCookie, appendResponseHeader), three-phase request handling, AsyncLocalStorage context.

Backend & APIs363 installs

Popular other skills

View category →

hyperframes-cli

heygen-com/hyperframes

The HyperFrames CLI by job - scaffold, check, look at frames, edit the timeline, preview, render locally or in the cloud, publish, project history - plus reading its JSON and recovering from build or render failures.

1.3M installs

triage

mattpocock/skills

Move issues and external PRs through a state machine of triage roles, categorise, verify, grill if needed, and write agent-ready briefs.

1M installs

ask-matt

mattpocock/skills

Ask which skill or flow fits your situation. A router over the skills in this repo.

778.4K installs

to-questionnaire

mattpocock/skills

Turn a decision you can't fully answer into a questionnaire for someone else to fill in.

508.9K installs

wait-what

mattpocock/skills

Stop. That last message did not land: re-pitch it.

459.7K installs

loop-me

mattpocock/skills

Grill me about specs for the workflows I want to build, within this workspace.

447.9K installs