mcpField
S
solid-router

solid-router

github.com/tanstack開発15.3k58 回インストール4 日前に更新
リポジトリ

solid-router について

`@tanstack/solid-router` を使った開発向けのスキルです。ファイルベースルーティング、Solid 固有のフックやコンポーネント、型とリアクティビティに関する注意点を解説します。

主な機能

  1. 01Vite によるファイルベースルーティング設定
  2. 02ルーターフックとコンポーネントの利用
  3. 03ナビゲーションとルートデータの処理
  4. 04Solid のリアクティブなアクセサーパターン
  5. 05一般的な設定・型の誤りの回避

活用シーン

  1. 01Solid アプリへの型安全なルート追加
  2. 02ナビゲーションとルート単位のデータ読み込み
  3. 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 はクライアントファーストです。ローダーは既定でクライアント上で実行され、サーバー上では実行されません。 CRITICAL:ほとんどのフックは Accessor<T> を返します。リアクティブな値を読み取るには、アクセサー(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. ルーターインスタンスを作成し、型を登録する
// 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')!,
)

フックのリファレンス

すべてのフックは @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> を返します

状態全体を公開するため、パフォーマンスコストが発生します。マッチやロケーションには、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>
}
その他のフック

すべて @tanstack/solid-router からインポートします。

  • useMatches() — Accessor<Array<Match>>。アクティブなルートマッチすべてを返します
  • useParentMatches() — Accessor<Array<Match>>。親ルートのマッチを返します
  • useChildMatches() — Accessor<Array<Match>>。子ルートのマッチを返します
  • useRouteContext({ from }) — Accessor<T>。beforeLoad からのコンテキストを返します
  • useLoaderDeps({ from }) — Accessor<T>。ローダーの依存値を返します
  • 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 — 宣言的なナビゲーションブロッカーです。カスタム UI には shouldBlockFn と withResolver を使用します
  • ScrollRestoration — 非推奨です。代わりに createRouter の scrollRestoration: true オプションを使用してください
  • ClientOnly — ハイドレーション後にのみ子要素をレンダリングし、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

ハイドレーション後にのみ子要素をレンダリングします。

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 を使ったカスタムリンクコンポーネント
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>
  )
}
ルーターの状態で 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>
  )
}
ルーターコンテキストを使った認証
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. 重要:Accessor の呼び出し忘れ

フックは 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など)

コメント