Optimizing OpenSearch & Algolia Faceted Search in Next.js
Managing disjunctive facets, debouncing rapid keystrokes, and eliminating redundant API roundtrips across high-traffic candidate and healthcare job listings.
The Problem: Rapid Keystrokes & Out-of-Order Responses
Healthcare hiring portals require faceted searching over complex dimensions: clinical specialization, medical license status, hospital network, years of clinical experience, and shift preferences.
When candidates or recruiters typed quickly in the search bar (e.g. typing "Cardiothoracic Surgeon"), each keystroke fired an immediate network request. This caused two severe issues:
1. API Rate Limiting & Billing Spikes: 25 rapid keystrokes produced 25 instant queries to Algolia and OpenSearch.
2. Race Conditions: An earlier query (e.g. "Cardio") returning in 400ms would resolve after a later query (e.g. "Cardiothoracic") that returned in 150ms, leaving the screen with stale, incorrect search results.
// Custom search hook with debouncing and abort controller
export function useDebouncedSearch(query: string, delay = 300) {
const [debouncedQuery, setDebouncedQuery] = useState(query);
const abortControllerRef = useRef<AbortController | null>(null);
useEffect(() => {
const handler = setTimeout(() => {
// Abort previous in-flight search request to prevent race conditions
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
abortControllerRef.current = new AbortController();
setDebouncedQuery(query);
}, delay);
return () => clearTimeout(handler);
}, [query, delay]);
return { debouncedQuery, signal: abortControllerRef.current?.signal };
}The Fix: Keystroke Collapsing and Disjunctive Facet Caching
We implemented a 300ms leading-edge debouncing architecture coupled with an AbortController signal on all fetch calls. This collapsed 27 keystrokes into 2 or 3 precise requests.
Additionally, for facet counts (e.g. showing "Cardiology (42)", "Neurology (18)"), we decoupled facet aggregations from the main results query. Facets are cached in client memory and only refreshed when core filters change, cutting redundant API calls by over 65%.
Search state was also two-way bound to URL query parameters via shallow routing, enabling recruiters to share filtered search URLs directly with hiring managers without losing filter state.
Performance Impact
The optimization dramatically reduced search latency, eliminated out-of-order rendering glitches, and reduced backend search compute costs while delivering instantaneous filter feedback.