After doing some research online, I decided to implement breadcrumbs by combining a hook and a data file.

My reasoning behind this decision is:

  • I want to have more logic living in the plugins and not in liquid code.
  • I want for my permalinks to not contain data related to the categories, because of what I mentioned in this old post.

Today I implemented the breadcrumbs for pages only. I might add them to the posts too in the future. Here is how I did it:

  • Each page has a front matter variable that indicates if the breadcrumbs are shown or not:
# bungee-jumping-tutorial.html
---
#...
breadcrumbs_visible : true
#...
---
  • Then in the page layout, an include is loaded with the breadcrumbs code if the front matter variable is true:
<!-- layouts/page.html -->
{%- if page.breadcrumbs_visible -%}
	{%- include breadcrumbs.html -%}
{%- endif -%}  
  • The include file looks like this:
<!-- includes/breadcrumbs.html -->
<div id="breadcrumbs" style="margin-bottom: 1rem;">
<a href="/">Home</a>
    /
    {% for crumb in breadcrumbs %}
    <a href="{{ crumb.path | prepend:site.baseurl }}">
        {{ crumb.text }}
    </a>
    /
    {% endfor %}
<p href="#">{{page.title}}</p>
</div>

Important to notice: The home link at the beggining, and the current page text at the end are always present, independently from the data file content (the breadcrumbs variable).

  • Finally, as mentioned before, the heavy lifting is done by the hook and the data file:
    • The data file contains for each page a list of the link and text of each breadcrumb (excluding the first and last that are pre-established).
    • The hook finds the correct data in the data file based on the file name of the page (excluding its extension), and then makes the breadcrumbs accessible through Jekyll.
Jekyll::Hooks.register :pages, :pre_render do |page, payload|
    # get the file name of the page without extension 
    file_name = File.basename(page.path)
    file_name_without_extension = File.basename(file_name, File.extname(file_name))
    if page.data["title"]
      puts "Pre render of page (file name "+ file_name_without_extension +"): " + page.data["title"]
    else
      puts "pre render of a page without a title"
    end
    
    # get the breadcrumbs data file
    require 'json'
    breadcrumbs_data_path = File.join(Dir.pwd, '_data', 'breadcrumbs.json')
    breadcrumbs_data_text = File.read(breadcrumbs_data_path)
    breadcrumbs_data = JSON.parse(breadcrumbs_data_text)  

    # find the breadcrumbs for the current page in the data file 
    breadcrumbs_found = false
    breadcrumbs_data.each do |member|
      if member["page"] == file_name_without_extension
        puts "    Found breadcrumb data: #{member}"
        payload["breadcrumbs"] = member["crumbs"] # this will be accessible with liquid as {"path"=>"/journals/", "text"=>"Journals"}{"path"=>"/site-updates/", "text"=>"Site Updates"}
        breadcrumbs_found = true
        break # Exit the loop after finding the member
      end
    end

    if !breadcrumbs_found
      payload["breadcrumbs"] = nil # if not found it should be empty 
    end
  end
  • This is the exact data file (_data/breadcrumbs.json) I am using as of the date of writing this post. Each crumbs list right now has only one breadcrumb, but there could be more if necessary.
[
    {
        "page": "git",
        "crumbs": [
            {
                "path": "/tutorials/",
                "text": "Tutorials" 
            }
        ]
    },
    {
        "page": "journal-other",
        "crumbs": [
            {
                "path": "/journals/",
                "text": "Journals" 
            }
        ]
    }, 
    {
        "page": "journal-site-updates",
        "crumbs": [
            {
                "path": "/journals/",
                "text": "Journals" 
            }
        ]
    },     
    {
        "page": "journal-revit-api",
        "crumbs": [
            {
                "path": "/journals/",
                "text": "Journals" 
            }
        ]
    },        
    {
        "page": "journal-cs",
        "crumbs": [
            {
                "path": "/journals/",
                "text": "Journals" 
            }
        ]
    }    
]
  

Important to notice: as of today the site has very few pages and many more posts - that’s why this approach works fine in my case. If someday I want to implement post breadcrumbs as well, I’ll probably make sure it requires fewer manual inputs.

And that’s how it is working as of today. I know it is not perfect, but I am happy with this as a first iteration. I’ll probably be improving this system in the future.