AACsearch
Analytics

No-Results Queries

The recovery loop — detecting zero-result queries, closing the gap with synonyms, curations, or content, and measuring the improvement.

A no-result query is a search that returned zero hits. It is the single highest-signal event in your search analytics: the user told you exactly what they wanted, and you told them you have nothing. Every no-result query is either a relevance miss (you have the answer but didn't find it) or a content gap (you don't have it).

This page is the playbook for triaging that data.

How no-results are detected

The public search handler records type: "zero_results" on SearchUsageEvent when found === 0. The Top Queries panel surfaces these in a dedicated cut, and the dashboard exposes a no-results-rate KPI tile.

The events also carry:

  • The raw query string.
  • The filterBy that was active (often the cause of the zero).
  • The indexSlug.
  • The locale.
  • A searchTimeMs so you can detect timeouts vs true empties.

Reading the no-results panel

const zr = await orpc.search.topQueries.call({
  organizationId,
  period: "30d",
  filter: { zeroResults: true },
  limit: 50,
});

Sort by search count. The top 10–20 are the queries to act on this week; the long tail is for content-team backlog.

Three columns to read together:

ColumnInsight
queryThe literal user phrase.
searchesHow many distinct sessions hit this empty.
withFilterWhether the query had a filterBy active — a "zero with filter" is often filter-induced, not catalog-missing.

A query like iphone 15 with 200 zero-result searches and 0 filters is a content gap. The same query with 200 zero-results and filterBy: "brand:=Samsung" is a filter UX problem — the user picked an incompatible filter.

Triage flow

For each top no-result query, in order:

Step 1 — Is it a typo or a known synonym?

Run the query in the Search Preview. If a fuzzy match would have worked but didn't (typo tolerance off, or the query is too long for fuzz):

Step 2 — Is it a real product/article with a vocabulary mismatch?

Search your catalog directly (without the user's query). If you find the right document, the answer was in the index but the user's words didn't reach it:

  • Add a synonym mapping (e.g. "airpods" → "wireless earbuds").
  • Add the alternate term to the document's tags or description (if you control the catalog).
  • Boost relevant fields in queryBy.

Step 3 — Is it a filter-induced zero?

If withFilter is true and the underlying query has matches without the filter:

  • This is a UX issue, not a content issue. Detect filter-induced zeros in the client and show "Clear filters to see X more results".
  • Long-term: in the widget, disable filter values that would produce zero, given the current query (see "graceful filter pruning" in the widget docs).

Step 4 — Is it a content gap?

If neither the synonym fix nor the filter fix applies, you genuinely don't have the answer. This is content team work:

  • Add the missing product / article.
  • Re-ingest.
  • Mark the query as "watched" in the dashboard so you measure the recovery.

Measuring recovery

After applying a fix, watch the same query over the next two periods:

const before = await orpc.search.topQueries.call({
  organizationId,
  period: "30d",
  offsetDays: 30,
  filter: { zeroResults: true },
});
const after = await orpc.search.topQueries.call({
  organizationId,
  period: "30d",
  filter: { zeroResults: true },
});

A successful fix shows the query dropping out of the zero-result list in the "after" window. If it's still there, the fix didn't take — typically because the reindex hasn't run yet, or the synonym was added to the wrong index.

The no-results-rate KPI

The dashboard KPI tile is:

no-results rate = zero-result searches / total searches

In a healthy storefront, target under 5 %. Above 10 % usually means a serious catalog / vocabulary mismatch. Above 20 % almost always means a routing bug — the wrong index is being queried.

Track this weekly. A sudden jump usually correlates with a recent deploy (new schema field, new locale, new connector) and bisects easily against the Activity feed.

Bulk patterns

For very high-cardinality catalogs, scanning no-result queries one by one is unreasonable. Two bulk patterns:

Cluster by token

Take the top 500 no-result queries, lowercase / tokenise, and find the most common shared tokens. A cluster of zero-result queries that all contain a single token — for example "airpods" showing up in "airpods", "airpods pro", "new airpods" — is one fix (add the term) that wipes out the whole cluster.

Cluster by brand / category

Same idea but along your facet dimensions. If 200 zero-result queries all parse to "Brand: Acme, Category: phones", you're missing an entire product line.

The Export flow returns the raw rows; both clusters are 20-line scripts away.

Recovery via AI fallback

When all else fails, the no-result UX is the natural surface for a fallback:

  • Run a semantic-search hybrid pass when keyword returns zero.
  • Or fire an AI answer over the closest related products and let the user pivot.

Don't make this the default — both add latency and cost — but on the no-result branch, the cost is justified because the alternative is showing the user nothing.

Telemetry on the recovery itself

If you enable an AI fallback, instrument it:

trackEvent({
  type: "search_query",
  query,
  metadata: { fallback: "ai_answer", originalFound: 0 },
});

You can then compute "AI fallback effectiveness" — how often a zero-result session that triggered a fallback ended in a click — and decide whether the fallback is paying for itself.

On this page