Tracking Zero-Result Searches
A search box that returns nothing is a reader telling you exactly what is missing. "sso", "rate limit 429", "delete account", "terraform provider" — each zero-result query is a question the documentation could not answer, phrased in the reader's own words. Static sites often have search without any analytics on it, because the search runs entirely in the browser with Pagefind or Lunr and nothing reaches a server. That leaves the most useful content-planning data a site produces on the floor.
This guide records zero-result and low-engagement searches on a static site — with Pagefind in the browser and with a search server — without collecting personal data, and turns the data into a weekly report that drives synonyms, content fixes and new pages. It is part of Search for Static Sites. The numbers come from a 2,300-page product documentation site with about 38,000 searches a week.
Prerequisites
- A search UI you control (Pagefind UI or a custom component, Typesense, Algolia or similar).
- An analytics tool with custom events (Plausible, Umami, PostHog, GA4) or a small edge function with somewhere to write (Cloudflare Workers Analytics Engine, D1 or a log drain).
- A privacy review of what will be stored.
What to Record
Three events answer most questions:
- search — the normalized query, number of results, and the section the reader was in.
- search_click — the query and the position of the clicked result.
- search_abandon — a query with results where the reader closed search without clicking within 10 seconds.
Zero-result queries are search events with results = 0. Abandoned searches with results are the quieter signal: the results existed but did not look right.
Normalizing and Protecting Queries
Readers type personal data into search boxes: email addresses, customer IDs, API keys pasted by mistake. Normalize and filter before anything leaves the browser:
function cleanQuery(q) {
const s = q.normalize('NFKC').toLowerCase().trim().replace(/\s+/g, ' ').slice(0, 80);
if (/@/.test(s)) return '[email]';
if (/\b[0-9a-f]{24,}\b|\b\d{7,}\b|sk_[a-z0-9]+|ghp_[a-z0-9]+/i.test(s)) return '[redacted]';
return s;
}
Also debounce: log a query only when the reader pauses for 1.5 seconds or presses Enter, not on every keystroke, or "t", "te", "ter" and "terr" fill the report. No user identifiers are attached; the analytics tool already aggregates by page and date.
Recording With Pagefind
Pagefind UI does not emit events itself, but the search result is available when using the JavaScript API directly, and the UI's input can be observed. With a small custom UI built on pagefind.search():
const pagefind = await import('/pagefind/pagefind.js');
let timer;
input.addEventListener('input', () => {
clearTimeout(timer);
timer = setTimeout(async () => {
const q = input.value;
if (q.trim().length < 2) return;
const search = await pagefind.debouncedSearch(q);
if (!search) return;
render(search.results);
track('search', { q: cleanQuery(q), results: search.results.length, section: location.pathname.split('/')[1] || 'home' });
}, 1500);
});
track sends a Plausible custom event here (plausible('search', { props })); any analytics tool with custom properties works. Clicks call track('search_click', { q, position }) from the result link handler.
Recording With a Search Server
With Typesense, Algolia or Meilisearch, the server already sees every query. Typesense's analytics rules can aggregate popular queries and queries with no hits into collections automatically; Algolia's analytics dashboard reports "no results" queries directly. Server-side logs are complete but mix in bots and monitoring probes, so filter by user agent and exclude queries from the uptime checker. The client-side event approach above still helps for abandonment, which the server cannot see.
Turning Data Into Fixes
The report groups zero-result queries by normalized text, shows the count for the last seven days and the previous seven, and links to the search page with the query pre-filled. Each week, a writer triages the top 20 into one of five fixes:
| Fix | Example query | Action |
|---|---|---|
| Synonym | "sso" | add synonym set: sso, single sign-on, saml |
| Naming mismatch | "workspace" | product renamed "workspace" to "project"; add the old name to the page |
| Missing content | "terraform provider" | write a new guide |
| Typo not tolerated | "webhok" | enable or tune typo tolerance |
| Out of scope | "pricing" | add a pointer to the pricing page in search empty state |
Most weekly fixes took minutes: adding a synonym, or a sentence mentioning an old name on an existing page. New content went into the docs backlog with the search volume as evidence.
Improving the Empty State
Some zero-result searches will always happen. The empty state decides what readers do next. On this site, it now shows the query back, suggests one or two corrections from a small edit-distance check against page titles, links to the three most-visited guides in the current section, and offers a "search the community forum" link with the query pre-filled. Readers who hit the empty state clicked something on the page 44% of the time, up from 12% with the previous plain "No results" message.
Reading the Report Well
A few patterns in the data are easy to misread. Seasonal spikes — a query for a feature announced in a blog post, searched heavily for a week — look urgent and fade on their own; the week-over-week trend column separates them from steady demand. Queries in other languages on an English-only site are a signal for localization rather than content, and on this site they justified translating the twenty most-read pages into German first, as described in Internationalization for Static Sites. Very short queries ("api", "sdk") often return many results rather than none, but show high abandonment: the fix there is better ranking or a curated landing page, not more content.
Abandonment deserves its own weekly look. A query with results that readers consistently abandon usually means the right page exists but its title or excerpt does not look like the answer. Rewriting titles and the first sentence of the most-abandoned targets moved click-through on those queries from 31% to 57% on this site — often a faster win than writing anything new.
Finally, share the report beyond the docs team. Product managers found the zero-result list useful as evidence of features readers expected; support used it to spot confusion before tickets arrived. A two-line summary in the team channel each Monday — the top three new zero-result queries and what was done about last week's — kept the habit going.
Measured Impact
| Measure | Before tracking | After 12 weeks of weekly fixes |
|---|---|---|
| Zero-result rate | 11.8% | 5.3% |
| Searches ending in a result click | 57% | 68% |
| Clicks from the empty state | 12% | 44% |
| New guides written from search demand | — | 29 |
| Support tickets tagged "couldn't find in docs" per month | 140 | 96 |
Pitfalls & Rollback
- Logging every keystroke. Debounce, or the report fills with prefixes.
- Storing raw queries. Redact emails, IDs and tokens in the browser.
- Counting bots. Server-side logs include crawlers and probes; filter them.
- Treating the report as a dashboard. It only helps if someone triages it weekly.
- Rollback: the tracking is a few lines in the search UI; removing the
trackcalls stops collection immediately, and stored aggregates can be deleted.
Conclusion
Zero-result searches are the cheapest content research a documentation team can get. Record them from the search UI with privacy filtering, review the top queries weekly, and fix them with synonyms, renamed terms, new pages and a helpful empty state. On this site twelve weeks of that halved the zero-result rate and measurably reduced "couldn't find it" support tickets.
FAQ
Why track zero-result searches?
They are direct evidence of what readers expected to find and could not. Each recurring zero-result query points at a missing page, a missing synonym, a naming mismatch between the product and the docs, or a search configuration problem, and fixing the top few usually improves search success noticeably.
How can a static site record searches without a backend?
Send an event from the search UI to an analytics tool that supports custom events, or to a small edge function that writes to a log or database. Only the query text, result count and page context are needed.
What about privacy?
Search queries can contain personal data such as email addresses or account IDs that readers paste. Normalize and truncate queries, drop anything matching email or long number patterns, never attach user identifiers, aggregate before storing where possible, and mention search logging in the privacy notice.
How often should the report be reviewed?
Weekly worked well on the site measured here. The top 20 zero-result queries by count, with their trend, fit in a short review, and most fixes, such as adding a synonym or a sentence to an existing page, take minutes.
Related
- Parent: Search for Static Sites — search options and trade-offs.
- Adding Pagefind to an Astro Site — the search the tracking wraps.
- Self-Hosting Typesense for a Static Site — synonyms and server-side analytics.
- Accessible Search UI for Static Sites — the empty state and results markup.
- Self-Hosting Analytics to Cut Third-Party Requests — where the events go.