Analyze and fix URL/query-param state anti-patterns — manual useSearchParams reads, hand-built query mutations, view-state trapped in useState, and objects in the URL
64
77%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
Fix and improve this skill with Tessl
tessl review fix ./.agents/skills/you-might-not-need-url-state/SKILL.mdArguments:
User arguments: $ARGUMENTS
Shareable client view-state (active tab/panel, filters, search query, sort, pagination, selected-entity id, an open "view" modal/drawer that is a destination) lives in the URL via nuqs — driven by a co-located search-params.ts, never read via useSearchParams().get(...) and never mutated by hand-built query strings. Remote data stays in React Query; high-frequency / large / ephemeral / socket-synced state stays in Zustand; purely local UI stays in useState.
.claude/rules/sim-url-state.md is the source of truth — read it first.
Read these before analyzing:
.claude/rules/sim-url-state.md — the decision framework, conventions, debounced-input pattern, sort convention, selected-entity deep-link pattern, and the workflow-editor carve-outparseAsString/parseAsInteger/parseAsBoolean/parseAsStringLiteral/parseAsArrayOf/createParser)withDefault, history, shallow, clearOnDefaultcreateSearchParamsCache for server readsuseSearchParams().get(...) or new URLSearchParams(window.location.search) used to read view-state. Replace with useQueryState/useQueryStates bound to a search-params.ts. (Read-once auth/invite/redirect signals are NOT view-state — the list and the per-surface new caveat are in .claude/rules/sim-url-state.md "Read-once auth / redirect signals"; leave those on useSearchParams.)router.replace/router.push to change a param on the current path. Use a nuqs setter. (A router.push that changes the route path is fine; an outbound new URLSearchParams building an href/window.open/download/API URL is fine.)window.history.replaceState/pushState to mutate a param.popstate listener). The URL is the single source of truth; derive from it, don't mirror it.TableDefinition/SkillDefinition/etc. Store the id and derive the object from the loaded list (items.find(i => i.id === id)).useDebouncedSearchSetter (never a local useState mirror + reconcile effect, and never inline limitUrlUpdates wiring); keep canvas/presence/resize state in Zustand.useState: a tab/filter/sort/pagination/selected-entity that should be a link but lives in local state. Migrate it to the URL.useQueryState/useQueryStates whose page entry has no <Suspense> wrapper (Next.js requires it for useSearchParams). Add one with a real-chrome fallback.import { z } for param validation in client code: use nuqs parsers instead.SORT_DIRECTIONS/default-sort constants/activeSort derivation instead of createSortParams + useUrlSort, or an inline debounced-search setter instead of useDebouncedSearchSetter/useSettingsSearch..claude/rules/sim-url-state.md and the nuqs docs above to understand the guidelinessearch-params.ts, wire useQueryState(s) — sort via createSortParams + useUrlSort, search via useDebouncedSearchSetter — add the Suspense boundary, delete the replaced state + sync effects). If fix=false, propose the fixes without applying.7945b29
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.