When search state lives only in client memory, the back button becomes useless, sharing a result page over Slack or email delivers the unfiltered homepage, and refreshing the tab wipes every refinement. Users expect URLs to be durable addresses — breaking that contract means every deep link from Google, Reddit, or a friend lands on the wrong state. URL-synced state is also the only reliable way to give users back/forward history through their filter exploration, which doubles as an undo stack.
Critical because URL-less state breaks sharability, bookmarks, back/forward navigation, and SEO indexing of filtered views.
Drive all filter, sort, and pagination state through URLSearchParams and router.push. Read initial values from useSearchParams on mount so the URL is the single source of truth. Edit app/listings/search/page.tsx:
const updateFilters = (next) => {
const params = new URLSearchParams(searchParams)
Object.entries(next).forEach(([k, v]) => v ? params.set(k, v) : params.delete(k))
router.push(`?${params}`)
}
ID: directory-search-discovery.pagination-url.url-state-sync
Severity: critical
What to look for: Enumerate all relevant files and Perform a search, apply filters, and navigate to page 2. Check the URL — it should contain query parameters for the search term (q=), active filters (category=, price=, etc.), and page number (page=). Use the browser back button and verify the previous state is restored (including filters and page). Use forward and verify the state advances correctly.
Pass criteria: No more than 0 violations are acceptable. All search state (search term, all active filters, current page) is encoded in the URL query parameters. Browser back/forward buttons correctly restore and advance through the browsing history.
Fail criteria: URL does not update with filter/page changes, or back/forward buttons do not restore state, or search state is stored in local storage/client-side state without URL sync.
Skip (N/A) when: Search/filtering is not implemented. Signal: no search input or filters found.
Detail on fail: "URL does not update when filters are applied. Filters are stored in client state only. Back button does not work to undo filter changes." or "Page parameter is in URL but filter parameters are missing."
Remediation: Sync all state to URL query parameters:
// pages/listings/search.tsx
import { useSearchParams } from 'next/navigation'
import { useRouter } from 'next/navigation'
export default function SearchPage() {
const searchParams = useSearchParams()
const router = useRouter()
const [filters, setFilters] = useState({
q: searchParams.get('q') || '',
category: searchParams.get('category') || '',
page: parseInt(searchParams.get('page') || '1')
})
const updateFilters = (newFilters) => {
const params = new URLSearchParams(searchParams)
Object.entries(newFilters).forEach(([k, v]) => {
if (v) params.set(k, v)
else params.delete(k)
})
router.push(`?${params}`)
}
return (
<div>
<SearchInput value={filters.q} onChange={q => updateFilters({ ...filters, q })} />
<FilterPanel filters={filters} onChange={f => updateFilters(f)} />
<Pagination page={filters.page} onChange={p => updateFilters({ ...filters, page: p })} />
</div>
)
}