perf: client-side athlete index for instant search#188
Merged
Conversation
Adds a copy step that mirrors data/athlete-index.tsv.gz into app/public/ on dev and build, plus a Cache-Control header allowing returning visitors to instantly reuse the cached index while the browser revalidates in the background. Sets up the static-asset path that client-side search will consume in the following commits.
Splits the prefix / rotated-token / substring search algorithm out of search-index.ts (which depends on fs/zlib) into a Node-free search-core module. Server-side search-index.ts re-exports the pure pieces and keeps the disk loader; client-side code added in the next commit will import directly from search-core. Existing search-index tests still pass against the re-exported API.
Adds parseIndexTsv (pure, unit-tested) and loadSearchIndex (browser-only, memoized): fetches /athlete-index.tsv.gz, decompresses with DecompressionStream, parses into IndexEntry[], and builds the sorted search keys. searchAthletesInClientIndex wraps the shared core algorithm over the in-memory data. Not wired into the UI yet.
useAthleteSearch now consults a module-level memoized index loader. Once the index resolves, every keystroke runs locally (binary search over the sorted name array + rotated-token keys) with no network roundtrip. While the index is in flight, the hook falls back to /api/search unchanged. GlobalSearchBar triggers the fetch on mount; CommandPalette triggers it on open (renamed prefetchSearch → prefetchSearchIndex). Emits search_latency, search_index_load, and search_index_load_error analytics events so we can verify the win in Vercel Analytics.
Runs 10 representative queries against the full ~800K-entry index. Not a CI gate — informational, run with \`vitest bench\`. Establishes a baseline number so future algorithm changes can be compared.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/api/searchround-trip per keystroke to a client-side index served as a static asset./athlete-index.tsv.gzonce per session (CDN-cached withstale-while-revalidate=604800), decompresses withDecompressionStream, then runs the same prefix / rotated-token / substring search locally —~56µsper query on the full 808K-athlete index.DecompressionStream.search_latency(source: "client" | "api"),search_index_load, andsearch_index_load_errorlet us verify the win in production.Test plan
/api/search(visible in Network); subsequent keystrokes show no further network requests./api/searchfallback populates results while the index downloads in the background./races) still works.cd app && npm run buildsucceeds with the new copy step in the build chain.cd app && npm run test— 35/35 passing,npx tsc --noEmitclean,npm run lintclean.🤖 Generated with Claude Code