ESC

Type to search the knowledge base.

Search Results Page Design

Frontend system design for search results — query UX, filters, ranking display, perf, SEO, and empty states.

intermediate4 min read
  • system-design
  • interview
  • architecture
  • search

Scope the problem

In scope: search results page (SRP) frontend — query box, results list, facets/filters, pagination/infinite scroll, performance, SEO for public search.

Out of scope: inverted index internals / ML ranker training.

Critical journey

Type query → intent suggestions → submit → results + facets → refine → open item

Architecture

SearchPage
├── SearchHeader (query, suggestions)
├── FacetSidebar / chips
├── ResultsMeta (count, sort, latency)
├── ResultsList (virtualized)
└── Pagination or infinite sentinel

URL is source of truth:

/search?q=running+shoes&brand=nike&page=2&sort=price_asc

Shareable, refresh-safe, back-button friendly.

Data contracts

type SearchRequest = {
  q: string;
  filters: Record<string, string[]>;
  sort: string;
  cursor?: string;
  page?: number;
};

type SearchHit = {
  id: string;
  title: string;
  snippetHtml: string; // pre-escaped highlights from server
  imageUrl?: string;
  url: string;
};

type SearchResponse = {
  hits: SearchHit[];
  total: number;
  facets: { key: string; values: { value: string; count: number }[] }[];
  nextCursor?: string | null;
  correctedQuery?: string; // did-you-mean
};

Query UX

  • Debounced typeahead suggestions (autocomplete) separate from full search submit
  • Enter submits full SRP navigation
  • Cancel in-flight searches on query change (AbortController)
  • Preserve filters when query changes? Product decision — usually reset facets

Filters & facets

  • Multi-select facets with counts
  • Apply: update URL → fetch
  • Mobile: filters in drawer
  • Show active chips with remove

Facet counts should reflect other selected filters (server responsibility); client just renders.

Rendering results

  • Highlight snippets: server-sanitized HTML only
  • Image lazy load; reserve aspect
  • Skeleton on first load; keep previous results with opacity on refine (placeholderData)
  • Empty: suggestions, clear filters, popular queries
  • Zero-result after typo: show correctedQuery banner

Pagination vs infinite scroll

Mode Pros Cons
Page numbers SEO, jump, stable analytics extra clicks
Infinite engagement hard to deep-link item #80; SEO weaker

Public web search often pages; app search may infinite. Hybrid: infinite with ?page= updates.

Performance

  • Critical CSS for header + first results
  • Virtualize long lists
  • Prefetch result detail on hover
  • Cache recent queries in React Query (staleTime short)
  • Edge cache anonymous popular queries carefully (personalization none)

SEO (public SRP)

  • Server-render first page of results
  • Unique titles Search results for "…"
  • Rel next/prev if paginated
  • Avoid infinite-only crawl traps
  • Noindex ultra-thin query pages if policy requires

Analytics

  • search_submit, search_result_click (position, id)
  • Zero-result rate
  • Filter usage

A11y

  • Results count announced politely on update
  • Landmarks; skip to results
  • Facets as fieldsets
  • Keyboard operable chips

Tradeoffs

  1. Client SPA transitions vs full document for SEO
  2. Facet-heavy UI vs simple search
  3. Offset vs cursor pagination (cursor preferred under mutability)
  4. Personalized rank vs cacheability

Interview close

URL-driven q/filters → abortable search API → facets + chips → results with skeletons and empty/did-you-mean → pagination choice for SEO → perf virtualize + cache. Mention snippet sanitization.

Latency UX

Search feels slow not only from the engine but from serial client work. Parallelize:

  • Fire results + facet query together (or one combined response)
  • Render header chrome immediately
  • Stream or skeleton the list

Show a subtle “Updated” state when refining filters so users know the UI heard them even if results take 400ms.

Ranking transparency (light)

For some products, explain sort (“Best match”, “Newest”, “Price”). Avoid fake precision (“99% match”) unless real. Sponsored results must be labeled for trust and compliance.

Client cache policy

// short staleTime — inventory/prices shift
staleTime: 30_000,
// keep previous while fetching next query
placeholderData: keepPreviousData,

Invalidate on login if results are personalized.

Accessibility extras

Move focus to results summary on submit (aria-live polite count). Ensure infinite scroll has a keyboard “Load more” alternative.

Further reading