automated navigation menus from data files

Why Manual Navigation Is a Problem As your documentation grows in scale and complexity, manually updating navigation menus becomes error-prone and time-consuming. Every time you add a new page or rename a section, you must update multiple navigation structures, often across several files or versions. This redundancy is a breeding ground for inconsistency. To solve this, Jekyll allows the use of YAML data files to drive content generation dynamically. This enables you to define navigation structures once and reuse them across multiple versions, templates, or layouts. Setting Up Data-Driven Navigation Step 1: Create a Data File Create a new file called _data/navigation.yml in your Jekyll root. Here's a basic structure: v1: - title: Getting Started url: /v1/getting-started/ - title: API Reference url: /v1/api/ v2: - title: Introduction url: /v2/introduction/ - title: Usage url: /v2/usage/ - title: API Reference url: /v2/api/ This organize...

enable full-site search in jekyll using lunr

The Importance of Search in Documentation Sites

As your documentation grows, even the best structured navigation can’t replace a powerful search feature. A search bar provides users with a direct path to what they’re looking for—especially in technical documentation where concepts are spread across pages. In static sites like Jekyll on GitHub Pages, adding search requires a client-side solution since there’s no server to run queries. That’s where Lunr.js comes in.

What Is Lunr.js?

Lunr.js is a small, full-text search library written in JavaScript. It allows you to create an index of your site's content and search through it directly in the browser. Unlike third-party solutions like Algolia or Google Custom Search, Lunr is entirely static and privacy-respecting—ideal for documentation hosted on GitHub Pages.

Benefits of Using Lunr.js

  • No server or backend required
  • Lightweight and open-source
  • Works entirely on the client side
  • Supports filtering, partial matches, and ranking

How Site Search Works in Jekyll with Lunr

The core idea is to generate a JSON index of your site’s content during the build process. This JSON file is then loaded by a JavaScript-powered search UI that uses Lunr.js to run queries. Here’s the step-by-step plan:

  1. Create a JSON index of your site using a Jekyll layout.
  2. Load that index with JavaScript on a search page.
  3. Use Lunr to match queries and display the results.

Step 1: Generate a Search Index File

Create a Jekyll Index Page

Create a file named search.json in your root directory with the following front matter:

---
layout: null
sitemap: false
permalink: /search.json
---
[ 
  {% raw %}{% for page in site.pages | where_exp:"page","page.search != false" %}{% endraw %}
    {
      "title": "{{ page.title | escape }}",
      "url": "{{ site.baseurl }}{{ page.url }}",
      "content": {{ page.content | strip_html | jsonify }}
    }{% unless forloop.last %},{% endunless %}
  {% raw %}{% endfor %}{% endraw %}
]

This code loops through your site’s pages (excluding those with search: false in their front matter) and extracts titles, URLs, and content stripped of HTML to create a lightweight searchable index.

Step 2: Add the Search UI

Create a Search Page

In your pages or docs folder, create a new file named search.html:

---
layout: default
title: Search
permalink: /search/
---
<h2>Search the Docs</h2>

<input type="text" id="search-box" placeholder="Search..." />
<ul id="search-results"></ul>

<script src="https://unpkg.com/lunr/lunr.min.js"></script>
<script>
// Fetch the index
fetch('/search.json')
  .then(response => response.json())
  .then(data => {
    const idx = lunr(function () {
      this.field('title')
      this.field('content')
      this.ref('url')

      data.forEach(doc => this.add(doc))
    });

    const searchBox = document.getElementById('search-box');
    const resultsList = document.getElementById('search-results');

    searchBox.addEventListener('input', function () {
      const query = this.value.trim();
      resultsList.innerHTML = '';

      if (query.length < 2) return;

      const results = idx.search(query);

      results.forEach(result => {
        const match = data.find(d => d.url === result.ref);
        const li = document.createElement('li');
        const link = document.createElement('a');
        link.href = match.url;
        link.textContent = match.title;
        li.appendChild(link);
        resultsList.appendChild(li);
      });
    });
  });
</script>

Optional Styling

Add basic styles in your main stylesheet:

#search-box {
  width: 100%;
  padding: 0.5em;
  margin-bottom: 1em;
}

#search-results {
  list-style: none;
  padding: 0;
}

Step 3: Filter Searchable Content

Exclude Pages from Search

If you have pages that shouldn't be indexed (like landing pages or custom redirects), add this to their front matter:

search: false

This flag ensures your search.json generation loop skips them.

Limit Index Size

To avoid bloating your JSON, limit {{ page.content | strip_html }} to a certain length:

"content": {{ page.content | strip_html | truncate: 300 | jsonify }}

This keeps your index lightweight and fast.

Improving Search Relevance

Weighting Title vs Content

By default, Lunr gives equal weight to fields. You can give titles more influence with boosts:

this.field('title', { boost: 10 })

Tokenization

Lunr supports fuzzy search and wildcard matches. You can refine search queries like so:

idx.search(query + '*')

Highlighting Matches

Highlighting keywords in results improves UX. After finding results, wrap keywords with <mark> using regex or a small highlighting library.

Deploying to GitHub Pages

No special setup is needed for GitHub Pages. The JSON file is generated statically by Jekyll and served like any other asset. Just push your changes, and your search is live.

Advanced: Category-Based Search

Want to let users filter by documentation section? Extend your search.json to include categories:

{
  "title": "{{ page.title | escape }}",
  "url": "{{ site.baseurl }}{{ page.url }}",
  "category": "{{ page.categories | join: ',' }}",
  "content": {{ page.content | strip_html | jsonify }}
}

Then add UI controls to filter results by category.

Use Case: Search Across Versions

If you have multiple documentation versions, create a separate index file for each version. Load the correct index dynamically based on user selection or current version path.

Conclusion

Adding Lunr-based search to your Jekyll documentation site offers a powerful, fast, and self-hosted solution to navigate large amounts of content. With no dependencies beyond static assets and no external services required, it fits perfectly into a GitHub Pages workflow. Next, we’ll explore how to version your documentation intelligently using URL paths and collections.


Archives / All Content


© MintTagReach🕒😃😃😃 . All rights reserved.