mcpField
T
tanstack-vue-router-skilld

tanstack-vue-router-skilld

github.com/skilld-devDevelopment18499 installsUpdated 3w ago
Repository

About tanstack-vue-router-skilld

tanstack-vue-router-skilld is a reference skill for developers routing Vue applications with @tanstack/vue-router. It covers type-safe routes, navigation, data loading, search parameters, and SSR.

Key features

  1. 01Type-safe file-based and code-based routing
  2. 02Vue composables and route components
  3. 03Loaders, context, and redirects
  4. 04Search parameter validation
  5. 05SSR entry points and components

Use cases

  1. 01Building typed Vue navigation
  2. 02Loading route data with authentication checks
  3. 03Validating and transforming search parameters
  4. 04Setting up server-side rendering

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/skilld-dev/vue-ecosystem-skills --skill tanstack-vue-router-skilld

After installing, type/tanstack-vue-router-skilldin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

references/
api-surface.md
composables.md
data-loading.md
file-based-routing.md
navigation-links.md
routes.md
search-params.md
ssr.md

Details

From the project README

@tanstack/vue-router

Type-safe router for Vue. Prepared source: version 1.170.35 (package.json:3).

  • Requires vue >= 3.3.0 (peer), Node >= 20.19 (package.json:59-88)
  • Runtime deps: @tanstack/[email protected], @tanstack/[email protected], @tanstack/vue-store@^0.11.0
  • ESM-only ("type": "module"), no main/CJS entry
  • Entry points: @tanstack/vue-router, @tanstack/vue-router/ssr/server, @tanstack/vue-router/ssr/client (package.json:29-50)

Docs: https://tanstack.com/router (framework pages verified at /router/latest/docs/framework/vue/...)

Hard rules

  1. Most composables return Ref<T> — use .value in script; templates auto-unwrap. useRouter(), useNavigate(), useLinkProps(), useAwaited() do NOT return refs. Full table in references/composables.md.
  2. Never cast or annotate inferred route/router types. Types come from the route tree and the Register declaration.
  3. Register the router for type safety — without this, Link/useNavigate/useSearch accept any string:
declare module '@tanstack/vue-router' {
  interface Register {
    router: typeof router
  }
}
  1. Not vue-router. Never import useRoute/useRouter from vue-router, never use <router-view>/<router-link>.
  2. beforeLoad/loader are plain async functions. Vue composables (ref, computed, lifecycle hooks) cannot run inside them. Pass state through router context.
  3. Route options accept Vue SFCs: component, errorComponent, notFoundComponent, pendingComponent and the default*Component router options take .vue files or h()-style function components (src/route.ts:54-58, src/router.ts:26-65).

Setup with Vite (file-based routing)

npm install @tanstack/vue-router
npm install -D @tanstack/router-plugin @vitejs/plugin-vue @vitejs/plugin-vue-jsx
// vite.config.ts
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import { tanstackRouter } from '@tanstack/router-plugin/vite'

export default defineConfig({
  plugins: [
    tanstackRouter({ target: 'vue', autoCodeSplitting: true }), // before vue()
    vue(),
    vueJsx(), // needed for .tsx route files
  ],
})

Source: official Vue example, https://github.com/TanStack/router/blob/main/examples/vue/basic-file-based-sfc/vite.config.ts

// src/main.ts
import { createApp, h } from 'vue'
import { RouterProvider, createRouter } from '@tanstack/vue-router'
import { routeTree } from './routeTree.gen'

const router = createRouter({
  routeTree,
  defaultPreload: 'intent',
  scrollRestoration: true,
})

declare module '@tanstack/vue-router' {
  interface Register {
    router: typeof router
  }
}

createApp({ setup: () => () => h(RouterProvider, { router }) }).mount('#app')

File naming, split-file conventions (.route.ts, .component.vue, .lazy.ts), and the generated routeTree.gen.ts: references/file-based-routing.md.

Code-based routing

import { h } from 'vue'
import { createRootRouteWithContext, createRoute, createRouter, Outlet } from '@tanstack/vue-router'

interface RouterContext { auth: { isAuthenticated: boolean } }

const rootRoute = createRootRouteWithContext<RouterContext>()({ component: () => h(Outlet) })

const postsRoute = createRoute({
  getParentRoute: () => rootRoute,
  path: 'posts',
  loader: ({ context }) => fetchPosts(context.auth),
})

const routeTree = rootRoute.addChildren([postsRoute])
const router = createRouter({
  routeTree,
  context: { auth: { isAuthenticated: false } },
})

Route options, hooks on route objects, and property-order rules: references/routes.md.

Composables

ComposableReturnsNotes
useRouter()router instancenot a ref
useRouterState({ select })Ref<T>pass select to avoid re-renders
useNavigate({ from? })functionnot a ref
useSearch({ from })Ref<T>throws unless from matches
useParams({ from })Ref<T>
useMatch({ from })Ref<T>
useLoaderData({ from })Ref<T>
useLoaderDeps({ from })Ref<T>
useRouteContext({ from })Ref<T>
useLocation()Ref<ParsedLocation>
useMatches() / useParentMatches() / useChildMatches()Ref<Array<Match>>accepts select
useMatchRoute()functioncalling it returns Ref<false | params> (src/Matches.tsx:148-172)
useLinkProps(options)link props objectfor custom anchors
useBlocker({ shouldBlockFn })void or Ref<BlockerResolver>resolver only with withResolver: true
useCanGoBack()Ref<boolean>location.state.__TSR_index !== 0 (src/useCanGoBack.ts:4-10)
useAwaited({ promise })[data, promise] tuplefor deferred data

Route objects and getRouteApi('/path') expose the same hooks pre-scoped: Route.useSearch(), Route.useLoaderData(), Route.useNavigate(), Route.Link (src/route.ts:72-80).

Details and select/strict semantics: references/composables.md.

Components

ComponentPurpose
<RouterProvider :router="router" />mounts the router; extra attrs update router options (src/RouterProvider.tsx:60-95)
<Link to="..." :params="..." :search="...">type-safe anchor; active state sets data-status="active" + aria-current="page" (src/link.tsx:522-525); default slot receives { isActive } (src/link.tsx:918)
<Outlet />renders matched child route (src/Match.tsx:275)
<Navigate to="..." />declarative redirect, fires in onMounted (src/useNavigate.tsx:24-40)
<MatchRoute to="..." :fuzzy="true">renders slot when matched; scoped slot receives params
<Await :promise="p">deferred data with <Suspense> (src/awaited.tsx:26-43)
<Block :should-block-fn="fn">navigation blocking; scoped slot receives resolver (src/useBlocker.tsx:470-491)
<CatchBoundary> / <ErrorComponent>error boundary via onErrorCaptured
<ClientOnly>renders children only after mount
<HeadContent>, <Scripts>, <Html>, <Body>, <Asset>, <ScriptOnce>SSR document shell

Link options, activeProps (defaults to { class: 'active' }, src/link.tsx:477), preloading (intent/viewport/render), linkOptions(), and createLink(): references/navigation-links.md.

Data loading and redirects

const route = createFileRoute('/posts/$postId')({
  beforeLoad: async ({ context, params }) => {
    if (!context.auth.isAuthenticated) {
      throw redirect({ to: '/login', search: { from: params.postId } })
    }
  },
  loaderDeps: ({ search: { page } }) => ({ page }),
  loader: ({ deps, params }) => fetchPost(params.postId, deps.page),
  pendingComponent: PendingSpinner, // .vue file works too
})

throw redirect(), isRedirect(), deferred streaming with defer(), and context typing: references/data-loading.md.

Search params

import { z } from 'zod' // or plain validators
const route = createFileRoute('/posts')({
  validateSearch: z.object({ page: z.number().default(1) }),
  search: {
    middlewares: [stripSearchParams({ page: 1 })],
  },
})

Validators, input/output schemas, retainSearchParams/stripSearchParams, and custom serialization: references/search-params.md.

Deprecations in 1.170.x (use instead)

DeprecatedReplacementProof
Route, RootRoute, FileRoute, RouteApi classescreateRoute, createRootRoute, createFileRoute, getRouteApisrc/route.ts:95, src/route.ts:220, src/route.ts:460, src/fileRoute.ts:57
rootRouteWithContextcreateRootRouteWithContextsrc/route.ts:414
NotFoundRoute / routerOptions.notFoundRoutenotFoundComponent route option / defaultNotFoundComponent router optionhttps://tanstack.com/router/latest/docs/framework/vue/guide/not-found-errors
<ScrollRestoration />scrollRestoration: true in createRoutersrc/ScrollRestoration.tsx:18
opts.navigate in beforeLoad/loaderthrow redirect({ to })https://tanstack.com/router/latest/docs/framework/vue/api/router/RouteOptionsType
parseParams/stringifyParamsparams.parse/params.stringifysame RouteOptionsType doc
preSearchFilters/postSearchFilterssearch.middlewaressame RouteOptionsType doc
FileRouteLoader / separate .lazy.ts loader fileskeep the loader in the route filesrc/fileRoute.ts:153
useBlocker(fn, condition) legacy signatures{ shouldBlockFn } objectsrc/useBlocker.tsx:139-151

Best practices

References

Comments

Markdown supported