Shareable URLs reproduce identical result set in a new tab
Why it matters
Search results that can't be shared via URL are functionally private — users cannot bookmark a filtered view, send a result set to a colleague, or return to the same context after closing a tab. This violates ISO 25010:2011 functional-suitability and is a first-order UX failure for any directory product where sharing and referral are core growth mechanics. Non-shareable URLs also break SEO: search engines cannot index filtered result pages, eliminating long-tail keyword traffic for category, location, and attribute combinations that represent the directory's core value.
Severity rationale
High because non-shareable URLs eliminate bookmark, referral, and SEO surface area — all of which are foundational to a directory's discovery and growth model.
Remediation
Serialize all search state (query, filters, sort, page) into URL parameters and read them back on load. In your listings API route:
// /api/listings
const q = req.nextUrl.searchParams.get('q')
const category = req.nextUrl.searchParams.get('category')
const page = parseInt(req.nextUrl.searchParams.get('page') || '1')
const listings = await db.listings.findMany({
where: { name: { contains: q }, category },
orderBy: [{ rating: 'desc' }, { createdAt: 'desc' }],
skip: (page - 1) * 20,
take: 20
})
On the client, use router.push or useSearchParams to keep the URL in sync with every filter change. Verify by copying the URL, opening a new tab, and confirming identical results.
Detection
-
ID:
shareable-urls -
Severity:
high -
What to look for: Enumerate all relevant files and Perform a search with multiple filters and navigate to a specific results page. Copy the current URL. Open a new tab, paste the URL, and load the page. Verify that the results, filters, and page number are identical to what you saw in the original tab.
-
Pass criteria: At least 1 conforming pattern must exist. When the same URL is loaded, the identical search state, filters, and results are displayed. No randomness or variance in the results.
-
Fail criteria: URL is copied but loading it in a new tab shows different results, missing filters, or wrong page.
-
Skip (N/A) when: Search/filtering not implemented. Signal: no search input or filters found.
-
Detail on fail:
"URL preserves filters and page, but results are randomized or ordered differently on page load."or"URL lost query parameters when copied." -
Remediation: Ensure your API endpoint is deterministic for the same parameters:
// /api/listings (API route) export async function GET(req) { const q = req.nextUrl.searchParams.get('q') const category = req.nextUrl.searchParams.get('category') const page = parseInt(req.nextUrl.searchParams.get('page') || '1') const pageSize = 20 // Fetch with consistent ordering const listings = await db.listings.findMany({ where: { name: { contains: q }, category }, orderBy: [{ rating: 'desc' }, { createdAt: 'desc' }], skip: (page - 1) * pageSize, take: pageSize }) return Response.json({ listings }) }Ensure the database query is deterministic (use
orderByclauses consistently). Test by loading the same URL multiple times and verifying identical results.
External references
- iso-25010:2011 · functional-suitability — Functional Correctness — deterministic reproducibility
Taxons
History
- 2026-04-18·v1.0.0·Initial import from directory-search-discovery·automated