Site search on Core dna is an Elasticsearch index that the platform fills and queries, and a page you build. What reaches the index, search views, the search page, product facets, filtered listings that need no index, and when to go beyond the built-in index.
L2
Unit 02 · The search page
Empty and zero-result states
The search pagePage 9 of 20Updated
The situations that look like an empty search, and what a search page should show for each: a starting state, a zero-result state that offers a way forward, and a failed search, which looks exactly like no results.
Who this is for. The theme developer finishing a search page, and the reviewer who tries it with nonsense. Afterwards the page has a distinct state for each way a search comes back empty, and a check that tells you when search has stopped working.
Situations that look alike
A search page is judged by what it shows when it has nothing to show. Most themes print "0 results" and stop, which reads as a dead end. The platform produces several different situations that all look empty, or look like a long list when they should not:
Situation
What the view returns
What to show
No query: /search/site-search
Everything the view covers, all with the same score
A starting state, not a list
An empty query, ?q=, or one made only of stop words such as the
Nothing
The same starting state
A real query with no matches
Nothing
A zero-result state with a way forward
The search failed: Elasticsearch unreachable, Match phrase with fuzzy search on, a page past the 10,000th result
Nothing, rendered with HTTP 200
The zero-result state; the failure is in the log
The first two are a visitor arriving and a visitor submitting an empty box, and should look the same. A page that tests only the result count shows the whole site for the first and "no results for ''" for the second. Test for a query instead, as the minimal search page does with $hasQuery.
The starting state
When there is no query, show the search box with focus on it, and a short set of places to go: the sections people search for most, or the five most common searches as links. Keep that list in a menu or in site data, not in the template, so it can change without a deploy. A product finder is the exception: its starting state can be the whole browsable range, sorted and faceted, because browsing is what the visitor came for.
A zero-result state that helps
Say what was searched for, escaped, and keep it in the box so it can be edited. Then offer the next step most likely to work:
Drop the filters. If the visitor narrowed the search by type, category or facet, run the same query without them and offer the result as a link. A $_search call runs the view with only the parameters you give it, so the view's own fixed filters still apply but the visitor's do not.
Suggest fewer or broader words. Typing errors are already handled while fuzzy search is on, and the platform has no "did you mean"; the usual remaining cause is a query that is too specific.
Offer somewhere to go: the same short list as the starting state, and a way to reach a person.
Template codesearch/search.html
{if $hasQuery && $response->getTotalCount() == 0}
{$broader = $_search->search($view->getSlug(), ['q' => $query])}
<h2>Nothing found for “{$query|escape}”</h2>
<ul class="search-suggestions">
{if $broader->getTotalCount() > 0}
<li><a href="/search/{$view->getSlug()|escape:'url'}?q={$query|escape:'url'}">See {$broader->getTotalCount()} results without your filters</a></li>
{/if}
<li>Try fewer words, or a more general one.</li>
<li><a href="/contact">Ask us</a> and we will point you to it.</li>
</ul>
{/if}
{% if hasQuery and response.getTotalCount() == 0 %}
{% set broader = _search.search(view.getSlug(), {'q': query}) %}
<h2>Nothing found for “{{ query|e }}”</h2>
<ul class="search-suggestions">
{% if broader.getTotalCount() > 0 %}
<li><a href="/search/{{ view.getSlug()|e('url') }}?q={{ query|e('url') }}">See {{ broader.getTotalCount() }} results without your filters</a></li>
{% endif %}
<li>Try fewer words, or a more general one.</li>
<li><a href="/contact">Ask us</a> and we will point you to it.</li>
</ul>
{% endif %}
$query and $hasQuery are set as on the minimal search page. The second search costs one more request to Elasticsearch, only on a page that found nothing, and finds nothing more when the visitor applied no filters, in which case the link does not appear. Put the result count and this message inside the element with role="status", so that screen readers announce the outcome when a script updates the results in place. A product finder whose filters exclude everything needs one more thing, a way back from each active filter (When a selection excludes every result).
A failed search
When Elasticsearch cannot be reached, the view's settings make Elasticsearch refuse the query, or the page asked for is past the 10,000th result, the search page renders the zero-result state above, with HTTP 200 and caching prevented. The $_search helper does the same and logs the error, marked for administrators to see in the admin. Only the API answers with an error status.
Monitor for a failed search independently of the page. Two habits catch it:
A synthetic check. Have the site's uptime monitoring request the search page with a word that is certainly on the site, such as the client's name, and alert when the page contains your zero-result message. A check on the home page will not notice.
Ask for the log when a client reports that search "stopped finding things". The search page writes its failures to the platform's log, which Core dna can read for you; errors from the $_search helper also appear in the admin. An error from the Search module means a failure, not missing content.
When search comes back empty for everything on one environment only, check that environment's index first: a new environment, or one whose database was replaced, needs a reindex, and one whose indexing workers are not running stops taking in new records (Causes, in the order to check them).