# Shareable URLs reproduce identical result set in a new tab

- **Pattern:** `ab-001068` (`directory-search-discovery.pagination-url.shareable-urls`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001068
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001068)

## 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:

```tsx
// /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:

  ```tsx
  // /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 `orderBy` clauses consistently). Test by loading the same URL multiple times and verifying identical results.

## External references

- iso-25010 functional-suitability

Taxons: data-integrity, user-experience

HTML version: https://auditbuffet.com/patterns/ab-001068
