Cecil logo Cecil
What's on this page

Add client-side search

A static site has no server-side search, but Cecil can generate a JSON index of your pages at build time. A few lines of JavaScript are then enough to search it in the browser.

Create the index page

Create a page whose only output format is json, with a dedicated layout:

pages/search.md

---
title: Search index
layout: search
output: json
excluded: true
---

excluded: true keeps this technical page out of list pages and the sitemap.

Write the JSON template

Following the lookup rules, Cecil renders this page with layouts/search.json.twig. Build an array of the showable pages, then encode it:

{%- set index = [] -%}
{%- for p in site.pages.showable|filter(p => p.type == 'page') -%}
  {%- set index = index|merge([{
    title: p.title,
    url: url(p),
    description: p.description|default(''),
    content: p.content|striptags|excerpt(300)
  }]) -%}
{%- endfor -%}
{{ index|json_encode(constant('JSON_UNESCAPED_UNICODE') b-or constant('JSON_UNESCAPED_SLASHES'))|raw }}

After php cecil.phar build, the index is available at /search.json.

Search with Fuse.js

Add a search field to a template and load Fuse.js from a CDN to query the index:

<input type="search" id="search" placeholder="Search…">
<ul id="results"></ul>

<script type="module">
  import Fuse from 'https://cdn.jsdelivr.net/npm/fuse.js@7/dist/fuse.mjs';

  const index = await fetch('{{ url('search', {format: 'json'}) }}').then(r => r.json());
  const fuse = new Fuse(index, { keys: ['title', 'description', 'content'], threshold: 0.3 });
  const results = document.getElementById('results');

  document.getElementById('search').addEventListener('input', (e) => {
    results.replaceChildren(...fuse.search(e.target.value, { limit: 10 }).map(({ item }) => {
      const li = document.createElement('li');
      const a = document.createElement('a');
      a.href = item.url;
      a.textContent = item.title;
      li.append(a);
      return li;
    }));
  });
</script>

Alternative: Pagefind

Pagefind indexes the generated HTML files instead: no JSON template is needed. Run it after the build, on the output directory (_site by default):

php cecil.phar build
npx pagefind --site _site

Then load its UI in a template:

<link href="/pagefind/pagefind-ui.css" rel="stylesheet">
<script src="/pagefind/pagefind-ui.js"></script>
<div id="search"></div>
<script>
  window.addEventListener('DOMContentLoaded', () => new PagefindUI({ element: '#search' }));
</script>