关于 solid-router
用于开发 `@tanstack/solid-router` 应用的技能,介绍文件路由、Solid 专用钩子与组件,以及常见的类型和响应式陷阱。
主要功能
- 01使用 Vite 配置文件路由
- 02使用路由钩子和组件
- 03处理导航与路由数据
- 04遵循 Solid 响应式访问器模式
- 05避免常见配置和类型错误
适用场景
- 01为 Solid 应用添加类型安全路由
- 02构建导航和基于路由的数据加载
- 03排查访问器与插件配置问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tanstack/ router --skill solid-router安装后在 Claude Code 中输入/solid-router确认已加载。
详细介绍
Solid Router(@tanstack/solid-router)
此技能基于 router-core。请先阅读 @tanstack/router-core#router-core,了解基础概念。
CRITICAL:TanStack Router 的类型完全通过推断得出。绝不进行类型转换,也不要为推断出的值添加类型注解。 CRITICAL:TanStack Router 以客户端为先。Loader 默认在客户端运行,而不是服务器上。 CRITICAL:大多数 Hook 都会返回
Accessor<T>——必须调用 Accessor(value())才能读取响应式值。这是与 React 版本最大的区别。 CRITICAL:不要将@tanstack/solid-router与@solidjs/router混淆。它们是完全不同的库,API 也各不相同。
完整设置:使用 Vite 进行基于文件的路由
1. 安装依赖
npm install @tanstack/solid-router
npm install -D @tanstack/router-plugin @tanstack/solid-router-devtools
2. 配置 Vite 插件
// 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. 创建根路由
// 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. 创建路由文件
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/solid-router'
export const Route = createFileRoute('/')({
component: HomePage,
})
function HomePage() {
return <h1>Welcome Home</h1>
}
5. 创建 Router 实例并注册类型
// 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')!,
)
Hook 参考
所有 Hook 均从 @tanstack/solid-router 导入。大多数会返回 Accessor<T>——调用返回值即可读取其值。
useRouter() — 返回 TRouter(不是 Accessor)
import { useRouter } from '@tanstack/solid-router'
function InvalidateButton() {
const router = useRouter()
return <button onClick={() => router.invalidate()}>Refresh data</button>
}
useRouterState() — 返回 Accessor<T>
公开整个状态,因此会带来性能开销。需要获取匹配项或 location 时,优先使用 useMatches 和 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() — 返回一个函数(不是 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 }) — 返回 Accessor<T>
import { useSearch } from '@tanstack/solid-router'
function Pagination() {
const search = useSearch({ from: '/products' })
return <span>Page {search().page}</span>
}
useParams({ from }) — 返回 Accessor<T>
import { useParams } from '@tanstack/solid-router'
function PostHeader() {
const params = useParams({ from: '/posts/$postId' })
return <h2>Post {params().postId}</h2>
}
useLoaderData({ from }) — 返回 Accessor<T>
import { useLoaderData } from '@tanstack/solid-router'
function PostContent() {
const data = useLoaderData({ from: '/posts/$postId' })
return <article>{data().post.content}</article>
}
useMatch({ from }) — 返回 Accessor<T>
import { useMatch } from '@tanstack/solid-router'
function PostDetails() {
const match = useMatch({ from: '/posts/$postId' })
return <div>{match().loaderData.post.title}</div>
}
其他 Hook
均从 @tanstack/solid-router 导入:
useMatches()—Accessor<Array<Match>>,所有处于激活状态的路由匹配项useParentMatches()—Accessor<Array<Match>>,父路由匹配项useChildMatches()—Accessor<Array<Match>>,子路由匹配项useRouteContext({ from })—Accessor<T>,来自beforeLoad的上下文useLoaderDeps({ from })—Accessor<T>,Loader 依赖项的值useBlocker({ shouldBlockFn })— 当存在未保存的更改时阻止导航useCanGoBack()—Accessor<boolean>useLocation()—Accessor<ParsedLocation>useLinkProps({ to, params?, search? })— 返回ComponentProps<'a'>(不是 Accessor)useMatchRoute()— 返回一个函数;调用该函数会返回Accessor<false | Params>useHydrated()—Accessor<boolean>
组件参考
RouterProvider
<RouterProvider router={router} />
Link
类型安全的导航链接。子项可以是用于表示激活状态的函数:
;<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
渲染匹配到的子路由组件:
function Layout() {
return (
<div>
<Sidebar />
<main>
<Outlet />
</main>
</div>
)
}
Navigate
声明式重定向(在 onMount 中触发导航):
import { Navigate } from '@tanstack/solid-router'
function OldPage() {
return <Navigate to="/new-page" />
}
Await
使用 Solid 的 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
包装 Solid.ErrorBoundary 的错误边界:
import { CatchBoundary } from '@tanstack/solid-router'
;<CatchBoundary
getResetKey={() => 'widget'}
errorComponent={({ error }) => <div>Error: {error.message}</div>}
>
<RiskyWidget />
</CatchBoundary>
其他组件
CatchNotFound— 捕获子项中的notFound()错误;fallback接收错误数据Block— 声明式导航拦截器;使用shouldBlockFn和withResolver实现自定义 UIScrollRestoration— 已弃用;请改用createRouter的scrollRestoration: true选项ClientOnly— 仅在 hydration 后渲染子项;接受fallback属性
Block
声明式导航拦截器组件:
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
在导航时恢复滚动位置:
import { ScrollRestoration } from '@tanstack/solid-router'
// In root route component
;<ScrollRestoration />
ClientOnly
仅在 hydration 后渲染子项:
import { ClientOnly } from '@tanstack/solid-router'
;<ClientOnly fallback={<div>Loading...</div>}>
<BrowserOnlyWidget />
</ClientOnly>
Head 管理
底层使用 @solidjs/meta:
import { HeadContent, Scripts } from '@tanstack/solid-router'
function RootDocument(props) {
return (
<html>
<head>
<HeadContent />
</head>
<body>
{props.children}
<Scripts />
</body>
</html>
)
}
Solid 专属模式
使用 createLink 创建自定义 Link 组件
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>
)
}
在 Router 状态中使用 Solid Primitives
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>
)
}
使用 Router 上下文实现身份验证
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' })
}
}
常见错误
1. CRITICAL:忘记调用 Accessor
Hook 会返回 Accessor<T>——必须调用它才能读取值。这是从 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. 高优先级:解构响应式值
解构会破坏 Solid 的响应式跟踪。
// WRONG — loses reactivity
const { page } = useSearch({ from: '/products' })()
// CORRECT — access through accessor
const search = useSearch({ from: '/products' })
<span>Page {search().page}</span>
3. 高优先级:在 beforeLoad 或 loader 中使用 React 钩子
beforeLoad 和 loader 不是组件,而是普通的异步函数。不能在其中使用任何钩子(React 或 Solid)。请改用路由器上下文传递状态。
4. 中优先级:插件目标设置错误
必须在路由器插件配置中设置 target: 'solid'。默认值为 'react'。
交叉引用
@tanstack/router-core#router-core— 查看所有针对特定领域模式的子技能(搜索参数、数据加载、导航、身份验证、SSR 等)