Search page
How an agent adds a search page to a Meridian site with the special search route and search.html, what browse mode returns without a search service, how to connect one, and how to verify the page renders and is accessible.
Search is a special route that renders search.html with a results object. With no search service configured, the page answers in browse mode over published content; connect Meilisearch, Elasticsearch or OpenSearch for ranked, matched results. The template is the same either way.
Route it
# config/sites/main/routes.yaml
special:
search: /search Per locale: search: { en: /search, cy: /chwilio }. check refuses a site that routes search without a search.html, and warns while no backend is configured.
Write the template
{# templates/_shared/search.html #}
{% extends "base.html" %}
{% block body %}
<h1>{{ t('search.title') }}</h1>
<form method="get" action="{{ request.path|url }}" role="search">
<label for="q">{{ t('search.field') }}</label>
<input id="q" type="search" name="q" value="{{ results.query }}">
<button type="submit">{{ t('search.go') }}</button>
</form>
{% if results.degraded %}<p role="status">{{ t('search.browse_notice') }}</p>{% endif %}
{% if results.items %}
<p>{{ t('search.count', results.total) }}</p>
<ol>
{% for item in results.items %}{% if item.title %}
<li>
<h2><a href="{% if item.clickUrl %}{{ item.clickUrl|url }}{% else %}{{ item.uri|url }}{% endif %}">{{ item.title }}</a></h2>
{% if item.highlight %}<p>{{ item.highlight }}</p>{% elif item.summary %}<p>{{ item.summary }}</p>{% endif %}
</li>
{% endif %}{% endfor %}
</ol>
{% if results.pagination.next %}<a rel="next" href="{{ results.pagination.next|url }}">{{ t('pager.next') }}</a>{% endif %}
{% elif results.query %}
<p>{{ t('search.none') }}</p>
{% endif %}
{% endblock %} # config/strings/en.yaml
strings:
search.title: Search
search.field: What are you looking for?
search.go: Search
search.browse_notice: Results are filtered, not ranked.
search.count:
one: "{count} result"
other: "{count} results"
search.none: Nothing matched. Try a shorter phrase. - The form is a plain
GET, so search works without JavaScript and every result page has an address. {% if item.title %}leaves out results with no title (a singleton home page, for example), which would otherwise print an empty link that fails the accessibility audit.- If you print the type facet (
results.facets.type), label each value withf.label|default:f.value: in browse mode the labels can be empty. searchForm()prints a search box for the header: aGETform to this route, and nothing on a site that routes no search.
Connect a search service
Browse mode lists published documents in the locale and does not rank or match words, so tell the person it is a fallback. For real search, run a backend and point the engine at it:
engine-dmo dev config set engine.yaml search.driver meilisearch The connection settings and keys (always ${VAR}) are in Search. Then engine-dmo search rebuild indexes every published document and engine-dmo search test "towpath" shows what a query returns.
Verify
Search pages are not in the sitemap, so name them:
engine-dmo render '/search?q=towpath' --json
engine-dmo a11y audit --path /search --path '/search?q=towpath' Both must answer 200 with no findings.
Multilingual site
How an agent adds a second locale to a Meridian site with dev locale add, gives routes per-locale paths, translates interface strings and labels, seeds translated documents, adds a language switcher and verifies hreflang and the translated pages.
DMO directory
How an agent builds a destination directory on engine-dmo, extending the core listing and area types, loading the tag vocabulary, routing areas beside pages at /{slug}, filtering by place facets, printing structured data, seeding complete listings with hours, address and phone, and verifying it all.