In 2024, I migrated my notes from [[Roam]] to [[Obsidian]] and merged my Obsidian vault with my personal website/blog repository. I had to adjust my Jekyll setup (using Jekyll plugins, layouts and includes and Liquid code snippets) to accommodate the notes in addition to blog posts and to publish everything the way I want it - or, as close to it as I figured out how ;)

In 2026, my Jekyll setup broke down, which motivated me to write my own [[Site Publisher]]. I no longer use Jekyll on my personal website; my previous setup is described here for historical purposes ;)

File Names and Titles

Unlike Obsidian, which uses document's file name as its title, many Jekyll plugins and code snippets that I need to make Jekyll do what I want require the pages they process to have titles; my notes, as imported from [[Roam]], do not, so I need to do something about it.

Jekyll Collections

One possibility is to make notes and days into Jekyll Collections, where the member documents have their title, if it is not set explicitly, automatically populated based on the file name - but:

  • Jekyll collection directories have their names start with _, so the notes will have to reside in the directory _notes both in the GitHub repository and on the published website;
  • sub-directories of Jekyll collections get ignored, but I want the ability to have sub-directories in my notes;
  • when Jekyll populates the title of the document from its file name, it capitalizes the first letter of each dash-separated segment, and I am not sure that this is what I want.

Liquid Code

Another possibility is to adjust the code that requires titles to use filename instead (when the title is not set explicitly) - but:

  • this works for Liquid and Ruby code snippets, but not for Jekyll plugins - and I probably need those;
  • without the title being set explicitly, my notes as they are rendered by Jekyll do not have titles - and I want the titles!

Linter Obsidian Plugin

So, I need to make sure that my notes always have titles, just like the blog posts. Obviously, adding the title manually to every little note created by just referencing it from another note is tedious and error-prone; I need to automate setting the title for the notes.

Linter Obsidian plugin can set the title property in each note to its file name; in Obsidian Settings | Community plugins | Linter | Settings:

  • General | Lint on save: enabled
  • YAML | YAML Title | Insert the title of the file into the YAML frontmatter: enabled

Sometimes I need the title to be different from the file name, for instance, for the manually created index.md files that I want to add to Jekyll's list header_pages in the _config.yml; in such cases, appropriate Linter rule can be disabled by adding a property to the file's YAML frontmatter:

---
disabled rules: [yaml-title]
---

MathJax

I freshened up my Jekyll MathJax setup by upgrading to MathJax 3; I followed the instructions of Bodun Hu - a rare find that addresses MathJax 3, as opposed to the more wide-spread instructions for MathJax 2 integration.

To enable MathJax on a page, add math: true to its front matter:

---  
...  
math: true  
---
...

In _includes/head.html: {% raw %}

<head>
  ...
  {%- if page.math == true -%}  
    {% include mathjax.html -%}  
  {%- endif -%}
</head>

{% endraw %}

In _includes/mathjax.html:

<script>  
  MathJax = {  
    tex: {inlineMath: [ ['$', '$'], ['\\(', '\\)'] ]},  
    svg: {fontCache: 'global'}  
  };  
</script>  
<script  
  type="text/javascript" id="MathJax-script" async  
  src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js"  
>  
</script>

Directory Listing

With the notes published on my website, I want a list of them to be published too - otherwise, nobody can find the individual notes. I found a solution; thank you, Michael Currin!

I did not make sub-folders appear in the lists automatically yet, not just files, so I add them manually, as content of the index files.

  •   [[TODO]] add sub-folders to the folder listing automatically;
  •   [[TODO]] generate the index files themselves automatically.
  •   [[TODO]] paginate the page list.

In _layouts/page.html: {% raw %}

<div class="post-content">
  {{ content }}
  {% if page.name == 'index.md' %}  
    {% include page-list.html %}  
  {% endif %}  
</div>

{% endraw %}

In _includes/page-list.html: {% raw %}

<ul class="page-list">  
  {% assign pages = site.pages | sort_natural: 'title' %}  
  {%- for item in pages %}  
  {%- if item.dir == page.dir and item.path != page.path %}  
  <li>
    <a href="{{ item.url | relative_url }}">
      <span>{{ item.title }}</span>
    </a>
  </li>  
  {% endif -%}  
  {% endfor -%}  
</ul>

{% endraw %}

In _sass/custom.scss: {% raw %}

.page-list {  
  font-size: 18px;  
  
  & i {  
    font-size: 14px;  
    // Based on blockquote.  
    color: #828282;  
  }  
}

{% endraw %}

Unlike [[Obsidian]], Jekyll does not understand wiki-links. There is a Jekyll plugin for that: Jekyll Wikirefs, but its 0.0.14 release did not work with current Jekyll; I asked for an update - and release 0.0.15 relaxed the dependency requirements! Thank you, manunamz!

In the interim, I used (after tweaking it a bit) a piece of Liquid code that does what needs to be done with basic wiki-links: brackettest (listed on the plugin's website :)).

Plugin does not respect the boundaries of the code blocks (but brackettest does), so to stop it from processing wiki-links inside such blocks (for instance, in this post :)), I insert a zero-width space between the two opening brackets: [​[. This is sub-optimal, since the invisible zero-width space ends up in the rendered code, which, as a result, can not be re-used by copying it: it is not what it looks like (in addition, the use of ZWSP throws off the code colorizer and requires an editor that shows invisible characters).

Another way - back-slash-escaping the opening bracket (\[) does not work within the code block. I asked for the plugin to not process links within the code blocks, but this seems to be unfeasible in the "legacy system" Jekyll; awaiting manunamz's suggestion for a non-legacy system.

Plugin down-cases page titles; I asked for this down-casing to be configurable.

Plugin gathers and puts into the front-matter of each page list of back-links to it. To show a list of back-links on a page, in _layouts/default.html add (note the use of the concat filter to make sure that links from both pages and posts are resolved): {% raw %}

<main ...>  
  <div class="wrapper">  
    {{ content }}  
  
    <div class="backlinks">  
    {% if page.backlinks != blank %}
    <hr/>  
    <h4>Backlinks</h4>  
    {% for backlink in page.backlinks %}  
    {% assign linked_doc = site.pages | concat: site.posts | where: "url", backlink.url | first %}  
    • <a class="backlink" href="{{ linked_doc.url }}">{{ linked_doc.title }}</a>  
    {% endfor %}  
    {% endif %}  
    </div>  
  </div></main>

{% endraw %}

Links can be styled in _sass/custom.scss:

.wiki-link::before {  content: "[[";  }  
.wiki-link::after {  content: "]]";  }  
.invalid-wiki-link {  background: red;  }  
  
.backlink {  font-style: italic; }

Pages by Tag

Now that I have my blog and my notes in the same repository and in the same Obsidian vault, I can cross-link blog posts and notes; both can have tags in their YAML frontmatter, and any overall list of tags, tag cloud or whatever I end up publishing on the site needs to include both posts and notes.

There are many code snippets floating around that add listing of tags and documents tagged with them to a site generated by [[Jekyll]], for example: "an easy way to support tags in a jekyll blog", "listing jekyll posts by tag", "https://www.maggie98choy.com/Add Tags in Jekyll/", jekyll-tagging [[Jekyll]] plugin.

All of the samples I saw use site.tags, which maps tags to lists of posts that have the tags, but only posts are included, not pages. To gather tags and associated files across the posts and pages, I copied a chunk of Jekyll code, tweaked it to include all posts and pages, tweaked the sorting, packaged it as a Liquid filter and put it into _plugins/all-tags.rb:

module Jekyll  
  module AllTagsFilter  
    def all_tags(site)  
      @all_tags ||= begin  
       hash = Hash.new { |h, key| h[key] = [] }  
       (site.documents + site.pages).each do |p|  
         p.data["tags"]&.each { |t| hash[t] << p }  
       end  
       hash.each_value { |pages| pages.sort_by! { |page| page.data["title"].downcase } }  
       hash.sort_by { |word| word[0].downcase }
     end  
    end  endend  
  
Liquid::Template.register_filter(Jekyll::AllTagsFilter)

List of tags and pages for each is generated using the all_tags filter in the file tags.html (which I list under header_pages in _config.yml):

{% raw %}

---  
layout: page  
title: "Tags"  
disabled rules: [yaml-title]  
description: "Pages by tags"  
permalink: /tags/  
---  
  
<div id="tags">  
  {% assign tags = site | all_tags %}  
  <h2>All tags</h2>  
  <p>
    {% for tag in tags %}  
    <a class="page-tag" href="{{ site.baseurl }}/tags/#{{ tag[0] | slugify }}">{{ tag[0] }}</a>  
    {% endfor %}  
  </p>  
  <h2>Pages by tags</h2>  
  {% for tag in tags %}  
  <div id="{{ tag[0] | slugify }}">  
    <h3>{{ tag[0] }}</h3>  
    {% for post in tag[1] %}  
    <ul>  
      <p>
        <a class="post-link" href="{{ post.url | relative_url }}">{{ post.title | escape }}</a>  
      </p>
    </ul>
    {% endfor %}  
  </div>  
  {% endfor %}  
</div>

{% endraw %}

  •   [[TODO]] Look into tag pages aliased to tags (with the help of the Tag Wrangler Obsidian plugin?) to bring tags like #computer closer to classifier pages like [​[buy]] and [​[learn]] (while in [[Roam]] they were equivalent) - or!, just link the tag header in the by-tag list to a page with the same name if it exists :) (possibly ignoring case).

Page Tags

Tags for posts and for pages like notes are listed on the page, linking back to the appropriate place by-tag list.

In _includes/tags.html: {% raw %}

{% if page.tags %} |  
{% for tag in page.tags %}  
<a class="post-tag" href="{{ site.baseurl }}/tags/#{{ tag | slugify }}">{{ tag }}</a>  
{% endfor %}  
{% endif %}

{% endraw %}

In _layouts/post.html, between the date and the author: {% raw %}

{%- include tags.html -%}

{% endraw %}

And in _layouts/page.html, after the title: {% raw %}

<p class="post-meta">  
{%- include tags.html -%}  
</p>

{% endraw %}

Tags are styled in _sass/custom.scss:

.page-tag {  
  display: inline-block;  
  background: $grey-color-light;  
  padding: 0 .5rem;  
  margin-right: .5rem;  
  border-radius: 4px;  
  color: $text-color;  
  font-size: 90%;  
  &:before {  
    content: "\f02b";  
    font-family: FontAwesome;  
    padding-right: .5em;  
  }  
  &:hover {  
    text-decoration: none;  
    background: $grey-color;  
    color: $background-color;  
  }  
}

To use the fancy tag character, make fancy font available in _includes/head.html:

<head>
  <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
</head>
  •   [[TODO]] update the font - or get rid of it if possible

The End

I did some re-organizing of my notes March 2026, and started getting:

/usr/lib/ruby/gems/3.4.0/gems/jekyll-wikirefs-0.0.16/lib/jekyll-wikirefs/util/wikiref.rb:224:in
 'Jekyll::WikiRefs::WikiLinkInline#context_fm_data': undefined method 'url' for nil (NoMethodError)
'url' => self.context_doc.url,
                     ^^^^
from /usr/lib/ruby/gems/3.4.0/gems/jekyll-wikirefs-0.0.16/lib/jekyll-wikirefs/util/link_index.rb:73:in 'block in Jekyll::WikiRefs::LinkIndex#populate'
from /usr/lib/ruby/gems/3.4.0/gems/jekyll-wikirefs-0.0.16/lib/jekyll-wikirefs/util/link_index.rb:67:in 'Array#each'
from /usr/lib/ruby/gems/3.4.0/gems/jekyll-wikirefs-0.0.16/lib/jekyll-wikirefs/util/link_index.rb:67:in 'Jekyll::WikiRefs::LinkIndex#populate'
from /usr/lib/ruby/gems/3.4.0/gems/jekyll-wikirefs-0.0.16/lib/jekyll-wikirefs/plugins/generator.rb:28:in 'block in Jekyll::WikiRefs::Generator#generate'
from /usr/lib/ruby/gems/3.4.0/gems/jekyll-wikirefs-0.0.16/lib/jekyll-wikirefs/plugins/generator.rb:25:in 'Array#each'
from /usr/lib/ruby/gems/3.4.0/gems/jekyll-wikirefs-0.0.16/lib/jekyll-wikirefs/plugins/generator.rb:25:in 'Jekyll::WikiRefs::Generator#generate'

I had to look for the cause by deleting bunch of my notes until I found which one causes the problem. Turned out, use of [[TODO]] in any of the notes/XXX/index.md files causes this...

Although this is not an intolerable restriction, it pushed me over the edge: I wrote my own [[Site Publisher]] and switched to it ;)