# 13-Jun-2026 — How Site Search Works

_____

Site search lets anyone find pages, walks and events from one box in the navbar, or from the dedicated search page. This article explains what it searches, how it decides what you are allowed to see, how the index is kept up to date, and how to tell what it is doing when something looks slow.

## What gets searched

Search covers two kinds of content:

- **CMS pages** - every page in the content editor, including page titles, body text, action button labels, and the text inside nested rows and columns. The whole `how-to` documentation section, release notes, committee pages and ordinary content pages are all included.
- **Walks and group events** - the title, description, additional details and start location of each walk or event.

Results are grouped into **Pages**, **Walks** and **Events** so it is clear what you are looking at.

## What you are allowed to see

Search respects exactly the same access rules as the rest of the site, so it never shows someone content they could not otherwise reach.

- A page's access follows the navbar section it sits under. If the top-level section is public, the page is public; if the section is committee-only, the page is committee-only.
- Admin pages are only ever returned to administrators.
- Individual content columns can carry their own access level, and those are honoured too.
- A logged-out visitor sees only public content. A committee member sees committee content. An administrator sees everything.

This filtering happens on the server for every search, using the login token sent with the request, so it is authoritative rather than something the browser could bypass.

## How the index works

Running a full-text query directly against the database on every keystroke would be slow and expensive, especially on our shared database tier. Instead the server keeps an in-memory **index** (we call it the corpus): a single bulk read of all pages and all walks, pre-processed into a form that is fast to search.

Once that index is in memory, each query is just a scan through it, which takes a few milliseconds.

```mermaid
flowchart TD
    USER["Visitor types a query"]
    DEBOUNCE["Browser waits 250ms before asking"]
    API["GET /api/database/search"]
    CACHE{"Index ready and fresh?"}
    SERVE["Scan the in-memory index"]
    FILTER["Filter by the visitor's access level"]
    RANK["Rank, group and de-duplicate"]
    RESULTS["Results grouped as Pages, Walks, Events"]
    WARMING["Return the building message"]
    REBUILD["Rebuild in the background"]
    MONGO@{ icon: "ngx:mongodb", label: "MongoDB Atlas", pos: "b", h: 48 }
    FRESH["New index replaces the old one"]

    USER --> DEBOUNCE --> API --> CACHE
    CACHE -->|fresh| SERVE
    CACHE -->|stale, serve old copy| SERVE
    CACHE -->|stale| REBUILD
    CACHE -->|empty on first use| WARMING
    SERVE --> FILTER --> RANK --> RESULTS
    REBUILD --> MONGO --> FRESH

    style RESULTS fill:#E8F5EE,stroke:#9BC8AB,stroke-width:2px,rx:12,ry:12,color:#404143
    style WARMING fill:#FDEFD2,stroke:#F9B104,stroke-width:2px,rx:12,ry:12,color:#404143
```

## How often it updates

The index is cached for **30 minutes**. After that it is considered stale.

When a stale index is next used, the server does two things at once: it serves the existing index straight away, and it rebuilds a fresh one in the background. This is called stale-while-revalidate. The practical effect is that searches stay instant, and new or edited content appears in search within about half an hour without anyone ever waiting for a rebuild.

A fresh build also happens once when the server starts up, which means shortly after each deployment.

## Knowing when it is building or ready

The very first search after a server start (before the first index exists) cannot return results yet. Instead of making you wait, it returns immediately with a short message: **"Building the search index for the first time. Your results will appear here automatically when it's ready…"** — and the results then appear by themselves once the index is built, with no need to retype the search.

For a precise picture there is a status endpoint:

```
GET /api/database/search/status
```

It returns:

- `indexed` - whether an index is in memory yet
- `building` - whether a rebuild is in progress
- `pages` and `events` - how many of each are indexed
- `ageMinutes` - how long ago the current index was built
- `ttlMinutes` - the refresh interval (30)

The search results page shows a plain-English summary of this at the bottom, for example: "Search index: 523 pages and 4263 walks and events, updated 4 minutes ago. It refreshes every 30 minutes."

## Why the first search after a deploy can be slow

The cold build reads roughly 60MB from the database and takes around a minute on our shared M0 cluster, because that tier has limited bandwidth. That is why the warming message exists. Once the index is built, queries are fast (typically 10 to 50 milliseconds) until the next 30-minute refresh, which happens quietly in the background.

## How results are ranked

Not every match is equally useful, so results are ordered by relevance:

- A match in the **title** scores higher than a match in the **body text**.
- Each result shows a relevance label: **Strong match**, **Good match** or **Mention**.
- Each result also shows where the term was found, under "Matched in" (Title, Page content, Description or Location).

Walks that recur every year often share the same title and wording. Rather than showing the same walk many times, repeated walks are collapsed to a single entry by title, so the results stay readable.

## The three ways to search

- **Navbar dropdown** - the magnifying-glass icon opens a search box with a live dropdown. It waits 250 milliseconds after you stop typing before searching, supports arrow-key navigation and Enter to open a result, and remembers your recent searches. The clear (X) button empties the box; press it again on an empty box, press Escape, or click away to close.
- **Full results page** at `/search` - a roomier view with type badges, the page's location in the site, the matching excerpt, relevance and dates. Reached from the dropdown's "View all results" link.
- **Site map** at `/site-map` - a complete hierarchical list of every page you can access, useful when you would rather browse the structure than search.

## For developers

Server-side logging is on for search. The server console shows the index lifecycle with timings, for example the time taken to load page content and events during a build, and a line per query with the result count and how long it took. Look for the `database:site-search` namespace.

The relevant code lives in `server/lib/mongo/controllers/site-search.ts` (index build, access filtering, ranking) and `projects/ngx-ramblers/src/app/modules/common/site-search/` (the navbar widget). Access levels are resolved the same way as `PageAccessGuard`, so search and navigation always agree on who can see what.
