
Url State Patterns
- 51 installs
- 18 repo stars
- Updated June 8, 2026
- andrelandgraf/fullstackrecipes
Url-state-patterns is a Claude skill that syncs React state to URL query params with nuqs for shareable filters, search, and deep-linkable dialogs.
About
Url-state-patterns shows how to sync React state to URL query params using the nuqs library. It covers the required Suspense wrapper, parsers with defaults, clearing params, and driving deep-linkable dialogs from the URL. A developer uses it when building shareable filters, search, or URL-driven dialogs.
- Wraps nuqs in a Suspense boundary since it reads useSearchParams
- Uses parsers (parseAsString/Boolean/ArrayOf) with .withDefault() to keep URLs clean
- Drives deep-linkable dialogs from a URL param for shareable back/forward state
Url State Patterns by the numbers
- 51 all-time installs (skills.sh)
- Ranked #1,297 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
url-state-patterns capabilities & compatibility
- Capabilities
- frontend
- Use cases
- frontend
What url-state-patterns says it does
nuqs reads `useSearchParams`, so it needs a Suspense boundary.
Set a value to `null` to remove it from the URL.
npx skills add https://github.com/andrelandgraf/fullstackrecipes --skill url-state-patternsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 51 |
|---|---|
| repo stars | ★ 18 |
| Last updated | June 8, 2026 |
| Repository | andrelandgraf/fullstackrecipes ↗ |
What it does
Shows how to sync React state to URL query params with nuqs for shareable filters, search, and deep-linkable dialogs.
Who is it for?
React/Next.js developers building shareable, URL-driven UI state with nuqs.
When should I use this skill?
Building shareable filters, search, or URL-driven dialogs.
What you get
- URL-synced React components using useQueryState
- Deep-linkable dialogs driven by URL params
Files
URL State Patterns
Sync React state to URL query params with nuqs.
Prerequisites
Complete these setup recipes first:
- URL State with nuqs
Suspense Wrapper
nuqs reads useSearchParams, so it needs a Suspense boundary. Colocate it by exporting a public wrapper that suspends an internal client component — consumers then use the component without adding Suspense themselves.
import { Suspense } from "react";
type SearchInputProps = { placeholder?: string };
export function SearchInput(props: SearchInputProps) {
return (
<Suspense fallback={<input placeholder={props.placeholder} disabled />}>
<SearchInputClient {...props} />
</Suspense>
);
}"use client";
import { useQueryState, parseAsString } from "nuqs";
function SearchInputClient({ placeholder = "Search..." }: SearchInputProps) {
const [search, setSearch] = useQueryState("q", parseAsString.withDefault(""));
return (
<input
value={search}
onChange={(e) => setSearch(e.target.value || null)}
placeholder={placeholder}
/>
);
}Parsers
Replace useState with useQueryState plus a parser. Use .withDefault() to read a fallback while keeping the URL clean.
"use client";
import {
useQueryState,
parseAsString,
parseAsBoolean,
parseAsArrayOf,
} from "nuqs";
const [search, setSearch] = useQueryState("q", parseAsString.withDefault(""));
const [showArchived, setShowArchived] = useQueryState(
"archived",
parseAsBoolean.withDefault(false),
);
const [tags, setTags] = useQueryState(
"tags",
parseAsArrayOf(parseAsString).withDefault([]),
);Clearing
Set a value to null to remove it from the URL. With .withDefault(), the param clears but reads return the default.
setSearch(null);
function clearFilters() {
setSearch(null);
setTags(null);
setShowArchived(null);
}Deep-Linkable Dialogs
Drive dialog visibility from a URL param so it's shareable and survives back/forward. Wrap in the same Suspense pattern.
import { Suspense } from "react";
type DeleteDialogProps = { onDelete: (id: string) => Promise<void> };
export function DeleteDialog(props: DeleteDialogProps) {
return (
<Suspense fallback={null}>
<DeleteDialogClient {...props} />
</Suspense>
);
}"use client";
import { useQueryState, parseAsString } from "nuqs";
import { AlertDialog, AlertDialogContent } from "@/components/ui/alert-dialog";
function DeleteDialogClient({ onDelete }: DeleteDialogProps) {
const [deleteId, setDeleteId] = useQueryState("delete", parseAsString);
async function handleDelete() {
if (!deleteId) return;
await onDelete(deleteId);
setDeleteId(null);
}
return (
<AlertDialog open={!!deleteId} onOpenChange={(open) => !open && setDeleteId(null)}>
<AlertDialogContent>
<Button onClick={handleDelete}>Delete</Button>
</AlertDialogContent>
</AlertDialog>
);
}Open it from anywhere by setting the param — setDeleteId("item-123") yields the deep link /items?delete=item-123.
function ItemRow({ item }: { item: Item }) {
const [, setDeleteId] = useQueryState("delete", parseAsString);
return (
<Button variant="ghost" onClick={() => setDeleteId(item.id)}>
Delete
</Button>
);
}---
References
Related skills
FAQ
Why does nuqs need a Suspense boundary?
nuqs reads useSearchParams, which requires a Suspense boundary; the pattern colocates it in a public wrapper.
How do you clear a query param?
Set its value to null; with .withDefault() the param clears while reads return the default.