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...

versioned documentation with jekyll collections

Why Documentation Versioning Matters

As software evolves, so does its documentation. Managing different versions of your docs is essential, especially when older versions remain in use. A versioning system allows users to view instructions that apply specifically to the version they’re using, reducing confusion and improving support quality. Fortunately, Jekyll provides tools like collections and Liquid templating that make versioned documentation easy to manage—even in static environments like GitHub Pages.

Common Approaches to Versioning

Before jumping into implementation, consider the main ways documentation is versioned:

  • Directory-based versioning: /v1/, /v2/
  • Subdomain-based versioning: v1.example.com
  • Query parameter-based versioning: ?version=2.0 (not recommended for static sites)

We’ll focus on the first approach—directory-based versioning—since it fits seamlessly into Jekyll and GitHub Pages.

Setting Up Collections Per Version

Step 1: Define Collections in _config.yml

collections:
  v1:
    output: true
    permalink: /v1/:path/
  v2:
    output: true
    permalink: /v2/:path/

This declares two collections: v1 and v2. Each will have its own directory and output HTML pages with URLs prefixed by the version name.

Step 2: Create Collection Folders

Create the following folders in your root directory:

_v1/
_v2/

Each collection will hold version-specific content. You might duplicate files from one version to another and make only necessary changes, or maintain a completely separate structure depending on the level of difference.

Step 3: Add Versioned Pages

Create Markdown files inside each versioned folder, like:

_v1/getting-started.md
---
title: Getting Started (v1)
version: v1
---

Welcome to version 1 of our documentation.
_v2/getting-started.md
---
title: Getting Started (v2)
version: v2
---

Welcome to version 2 with new features.

Each file can now be rendered at /v1/getting-started/ and /v2/getting-started/ respectively.

Creating a Version Switcher

Step 1: Store Page Metadata

To enable cross-version switching, make sure each page includes a common identifier like slug:

slug: getting-started

Step 2: Generate Links Across Versions

In your layout, use Liquid to dynamically build links to other versions:

{% assign current_slug = page.slug %}
{% assign versions = site.collections | map: "label" %}

<ul class="version-switcher">
  {% for version in versions %}
    {% assign docs = site[version] | where: "slug", current_slug %}
    {% if docs.size > 0 %}
      <li><a href="{{ docs[0].url }}">{{ version }}</a></li>
    {% endif %}
  {% endfor %}
</ul>

This code loops through all collections, finds the same slug across versions, and generates links. This is especially useful for header dropdowns or sidebars.

Default Version Routing

To make the newest version your default, create a redirect:

Step 1: Add an Index Page

In your root, add an index.md with this front matter:

---
layout: redirect
redirect_to: /v2/
---

Step 2: Create a Redirect Layout

<!DOCTYPE html>
<html>
  <head>
    <meta http-equiv="refresh" content="0; url={{ page.redirect_to }}" />
  </head>
</html>

This makes your root path redirect to the latest version.

Dynamic Side Navigation per Version

Because each version is a separate collection, you can generate sidebars dynamically. Example:

{% assign docs = site[page.version] %}
<ul class="sidebar">
  {% for doc in docs %}
    <li><a href="{{ doc.url }}">{{ doc.title }}</a></li>
  {% endfor %}
</ul>

This ensures each version of your documentation shows only relevant links.

Maintaining and Updating Versions

Minimize Duplication

If only a few files differ between versions, consider using shared includes or layouts. You can define common chunks in _includes/ and use conditional logic:

{% if page.version == 'v1' %}
  {% include old-api-warning.html %}
{% endif %}

Automate Cloning with Scripts

When releasing a new version, you can duplicate an entire collection folder using a build script:

cp -R _v2 _v3

Then update _config.yml to add v3 as a new collection.

Search Compatibility Across Versions

If you use Lunr.js or similar, you can index all versions or one version at a time. Add a version field to each indexed document and let users filter results by version.

Real-World Example

Many open-source projects use this pattern. Jekyll’s own documentation uses version folders to host multiple docs side-by-side. Popular frameworks like Vue, Angular, and React also use directory-based versioning to manage major releases.

Summary and Next Steps

By using Jekyll collections and Liquid templates, you can build a scalable, maintainable system for versioned documentation—completely static and GitHub Pages friendly. It’s an ideal solution for open-source projects, SaaS products, and APIs with multiple iterations.

In the next article, we’ll explore how to automate navigation menus from data files to reduce manual updates and support nested documentation structures across versions.


Archives / All Content


© MintTagReach🕒😃😃😃 . All rights reserved.