← Back to blog

URL State with searchParams

URL search parameters provide shareable, bookmarkable state that persists across refreshes.

Reading searchParams

From app/dashboard/_components/PostList.tsx:

const filterSchema = z.enum(['all', 'published', 'drafts', 'archived']).catch('all'); const sortSchema = z.enum(['newest', 'oldest', 'title']).catch('newest'); export async function PostList({ searchParams }) { const { filter, sort } = await searchParams; const validFilter = filterSchema.parse(filter); const validSort = sortSchema.parse(sort); const posts = await getPosts(validFilter, validSort); // ... }

Updating URL State

From app/dashboard/_components/PostTabs.tsx:

'use client'; export function PostTabs() { const searchParams = useSearchParams(); const router = useRouter(); const currentTab = searchParams.get('filter') ?? 'all'; const currentSort = searchParams.get('sort') ?? 'newest'; function tabAction(value: string) { // Preserve other params when updating one router.push(`/dashboard?filter=${value}&sort=${currentSort}`); } return <TabList activeTab={currentTab} changeAction={tabAction} />; }

Cycle Button with Optimistic State

From app/dashboard/_components/SortButton.tsx—a button that cycles through options:

'use client'; const sortOptions = [ { icon: ArrowUpDown, label: 'Newest', value: 'newest' }, { icon: ArrowDownUp, label: 'Oldest', value: 'oldest' }, { icon: ArrowDownAZ, label: 'Title', value: 'title' }, ]; export function SortButton() { const searchParams = useSearchParams(); const router = useRouter(); const currentSort = searchParams.get('sort') ?? 'newest'; const currentFilter = searchParams.get('filter') ?? 'all'; const [optimisticSort, setOptimisticSort] = useOptimistic(currentSort); const [isPending, startTransition] = useTransition(); const currentIndex = sortOptions.findIndex(opt => opt.value === optimisticSort); const nextIndex = (currentIndex + 1) % sortOptions.length; const nextSort = sortOptions[nextIndex].value; function sortAction() { startTransition(() => { setOptimisticSort(nextSort); router.push(`/dashboard?filter=${currentFilter}&sort=${nextSort}`); }); } return ( <Button onClick={sortAction} disabled={isPending}> {sortOptions[currentIndex].label} </Button> ); }

URL state works with browser history and makes pages shareable—/dashboard?filter=drafts&sort=title shows exactly that view.

March 5, 2026254 words