본문으로 건너뛰기
cccskills
무료GitHub에 공개

router-core-search-params

validateSearch, search param validation with Zod/Valibot/ArkType adapters, fallback(), search middlewares (retainSearchParams, stripSearchParams), custom serialization (parseSearch, stringifySearch), search param inheritance, loaderDeps for cache keys, reading and writing search params.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(2)

  • SKILL.md9.0 KB
  • references/validation-patterns.md9.0 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Search Params

TanStack Router treats search params as JSON-first application state. They are automatically parsed from the URL into structured objects (numbers, booleans, arrays, nested objects) and validated via validateSearch on each route.

CRITICAL: When using zodValidator() and Zod v3, use fallback() from @tanstack/zod-adapter, NOT zod's .catch(). Using .catch() with the zod adapter makes the output type unknown, destroying type safety. This does not apply to Valibot or ArkType (which use their own fallback mechanisms). It also does not apply to Zod v4, which should use .catch() and not use the zodValidator(). CRITICAL: Types are fully inferred. Never annotate the return of useSearch().

Setup: Zod Adapter (Recommended)

npm install zod @tanstack/zod-adapter
// src/routes/products.tsx
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'

const productSearchSchema = z.object({
  page: z.number().default(1).catch(1),
  filter: z.string().default(''),
  sort: z.enum(['newest', 'oldest', 'price']).default('newest').catch('newest'),
})

export const Route = createFileRoute('/products')({
  validateSearch: productSearchSchema,
  component: ProductsPage,
})

function ProductsPage() {
  // page: number, filter: string, sort: 'newest' | 'oldest' | 'price'
  // ALL INFERRED — do not annotate
  const { page, filter, sort } = Route.useSearch()

  return (
    <div>
      <p>
        Page {page}, filter: {filter}, sort: {sort}
      </p>
    </div>
  )
}

Reading Search Params

In Route Components: Route.useSearch()

function ProductsPage() {
  const { page, sort } = Route.useSearch()
  return <div>Page {page}</div>
}

In Code-Split Components: getRouteApi()

import { getRouteApi } from '@tanstack/react-router'

const routeApi = getRouteApi('/products')

function ProductFilters() {
  const { sort } = routeApi.useSearch()
  return <select value={sort}>{/* options */}</select>
}

From Any Component: useSearch({ from })

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

function SortIndicator() {
  const { sort } = useSearch({ from: '/products' })
  return <span>Sorted by: {sort}</span>
}

Loose Access: useSearch({ strict: false })

function GenericPaginator() {
  const search = useSearch({ strict: false })
  // search.page is number | undefined (union of all routes)
  return <span>Page: {search.page ?? 1}</span>
}

Writing Search Params

Link with Function Form (Preserves Existing Params)

import { Link } from '@tanstack/react-router'

function Pagination() {
  return (
    <Link
      from="/products"
      search={(prev) => ({ ...prev, page: prev.page + 1 })}
    >
      Next Page
    </Link>
  )
}

Link with Object Form (Replaces All Params)

<Link to="/products" search={{ page: 1, filter: '', sort: 'newest' }}>
  Reset
</Link>

Programmatic: useNavigate()

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

function SortDropdown() {
  const navigate = useNavigate({ from: '/products' })

  return (
    <select
      onChange={(e) => {
        navigate({
          search: (prev) => ({ ...prev, sort: e.target.value, page: 1 }),
        })
      }}
    >
      <option value="newest">Newest</option>
      <option value="price">Price</option>
    </select>
  )
}

Search Param Inheritance

Parent route search params are automatically merged into child routes:

// src/routes/shop.tsx — parent defines shared params
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'

const shopSearchSchema = z.object({
  currency: z.enum(['USD', 'EUR']).default('USD').catch('USD'),
})

export const Route = createFileRoute('/shop')({
  validateSearch: shopSearchSchema,
})
// src/routes/shop/products.tsx — child inherits currency
import { createFileRoute } from '@tanstack/react-router'

export const Route = createFileRoute('/shop/products')({
  component: ShopProducts,
})

function ShopProducts() {
  // currency is available here from parent — fully typed
  const { currency } = Route.useSearch()
  return <div>Currency: {currency}</div>
}

Search Middlewares

retainSearchParams — Keep Params Across Navigation

import { createRootRoute, retainSearchParams } from '@tanstack/react-router'
import { z } from 'zod'

const rootSearchSchema = z.object({
  debug: z.boolean().optional(),
})

export const Route = createRootRoute({
  validateSearch: rootSearchSchema,
  search: {
    middlewares: [retainSearchParams(['debug'])],
  },
})

stripSearchParams — Remove Default Values from URL

import { createFileRoute, stripSearchParams } from '@tanstack/react-router'
import { z } from 'zod'

const defaults = { sort: 'newest', page: 1 }

const searchSchema = z.object({
  sort: z.string().default(defaults.sort),
  page: z.number().default(defaults.page),
})

export const Route = createFileRoute('/items')({
  validateSearch: searchSchema,
  search: {
    middlewares: [stripSearchParams(defaults)],
  },
})

Chaining Middlewares

export const Route = createFileRoute('/search')({
  validateSearch: z.object({
    retainMe: z.string().optional(),
    arrayWithDefaults: z.string().array().default(['foo', 'bar']),
    required: z.string(),
  }),
  search: {
    middlewares: [
      retainSearchParams(['retainMe']),
      stripSearchParams({ arrayWithDefaults: ['foo', 'bar'] }),
    ],
  },
})

Custom Serialization

Override the default JSON serialization at the router level:

import {
  createRouter,
  parseSearchWith,
  stringifySearchWith,
} from '@tanstack/react-router'

const router = createRouter({
  routeTree,
  // Example: use JSURL2 for compact, human-readable URLs
  parseSearch: parseSearchWith(parse),
  stringifySearch: stringifySearchWith(stringify),
})

Using Search Params in Loaders via loaderDeps

export const Route = createFileRoute('/products')({
  validateSearch: productSearchSchema,
  // Pick ONLY the params the loader needs — not the entire search object
  loaderDeps: ({ search }) => ({ page: search.page }),
  loader: async ({ deps }) => {
    return fetchProducts({ page: deps.page })
  },
})

Common Mistakes

1. HIGH: Using zod v3's .catch() with zodValidator() instead of adapter fallback()

// WRONG — .catch() with zodValidator makes the type unknown
const schema = z.object({ page: z.number().catch(1) })
validateSearch: zodValidator(schema) // page is typed as unknown!

// CORRECT — fallback() preserves the inferred type
import { fallback } from '@tanstack/zod-adapter'
const schema = z.object({ page: fallback(z.number(), 1) })

Important: This only applies when using Zod v3, not when using Zod v4. For v4, using .catch() is correct.

2. HIGH: Returning entire search object from loaderDeps

// WRONG — loader re-runs on ANY search param change
loaderDeps: ({ search }) => search

// CORRECT — loader only re-runs when page changes
loaderDeps: ({ search }) => ({ page: search.page })

3. HIGH: Passing Date objects in search params

// WRONG — Date does not serialize correctly to JSON in URLs
<Link search={{ startDate: new Date() }}>

// CORRECT — convert to ISO string
<Link search={{ startDate: new Date().toISOString() }}>

4. MEDIUM: Parent route missing validateSearch blocks inheritance

// WRONG — child cannot access shared params
export const Route = createRootRoute({
  component: RootComponent,
  // no validateSearch!
})

// CORRECT — parent must define validateSearch for children to inherit
export const Route = createRootRoute({
  validateSearch: globalSearchSchema,
  component: RootComponent,
})

5. HIGH (cross-skill): Using search as object instead of function loses params

// WRONG — replaces ALL search params, losing any existing ones
<Link to="." search={{ page: 2 }}>Page 2</Link>

// CORRECT — preserves existing params, updates only page
<Link to="." search={(prev) => ({ ...prev, page: 2 })}>Page 2</Link>

References

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Build AI chat interfaces using ai-elements components — conversations, messages, tool displays, prompt inputs, and more. Use when the user wants to build a chatbot, AI assistant UI, or any AI-powered chat interface.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

hashintel/brunch82026년 10월 17일 업데이트

Search the live web via Perplexity Search API. Use when you need current documentation, release notes, vendor pages, news, domain-constrained web search, or date/recency filtering. Not for local codebase search or stable docs already in context.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

hashintel/brunch82026년 10월 17일 업데이트

cli-cdp

무료

Chrome DevTools CLI for browser automation via shell commands. Use when interacting with web pages from the command line — navigating, clicking, filling forms, inspecting console/network, taking screenshots, or extracting page content. Triggers on: browse a page, automate Chrome, inspect console, check network requests, take a screenshot, fill a form, click a button.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

hashintel/brunch82026년 10월 17일 업데이트

Uses the chrome-devtools-axi CLI for browser automation, accessibility-tree snapshots, console and network inspection, screenshots, Lighthouse audits, and performance traces. Use when interacting with Chrome from the shell, especially when the user mentions chrome-devtools-axi, AX snapshots, browser debugging, or DevTools automation from the command line.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

hashintel/brunch82026년 10월 17일 업데이트

cli-cmux

무료

Deep expertise in cmux — the terminal multiplexer with native browser views. Use when managing panes, reading terminal output, sending keystrokes, opening browser views, or manually testing web UIs and TUIs inside cmux. Triggers on: cmux, open a browser pane, split terminal, read screen, send keys, test this UI in cmux, preview in cmux.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

hashintel/brunch82026년 10월 17일 업데이트

Uses the gh-axi CLI for GitHub shell operations: issue, pull request, workflow run, release, repo, search, and API tasks. Prefer this over regular `gh` for GitHub reads and simple mutations when an agent needs compact, structured, suggestion-rich output. Triggers on: gh, GitHub CLI, github issue, github pr, pull request, workflow run, github release, gh api, repo inspection, list PRs, view issue, check workflow runs, inspect repo, GitHub shell operations.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

hashintel/brunch82026년 10월 17일 업데이트

hashintel의 스킬 모두 보기

이 스킬의 문제 신고