New Year, New Website
Happy 2026! It's been a long time since I've made a post here; but fear not. I have brought back my New Year's resolution of making one post per week for the year of 2026. You might notice that things are looking different around here. That's because I have moved off of mkdocs and set up a new static site generator with the goal of a simple, clean UI.
Why Leave Mkdocs?
In late 2023, I transitioned from a very old Jekyll-generated website over to a Python-based, trendy static site generator (SSG) called mkdocs, which has several community-created and maintained themes. One of the most popular themes is called mkdocs-material, a very responsive and customizable theme so advanced, it even had its own plugins to further customize it and extend its functionality. Needless to say, I was a huge fan, even contributing to it, and used it up until today when I am launching my new website. But why have I suddenly decided to stop using it?
The mkdocs-material maintainers have made a blog post and official GitHub issue detailing that the project has now entered maintenance mode due to the team pursuing a new project called Zensical, another static site generator. Zensical is relatively new and untested, and the team may be releasing breaking changes as they continue to develop its features.
Because mkdocs-material is now in maintenance mode, the team will no longer be adding new features to it and will only be providing security updates for about a year from November 2025. Eventually it will be discontinued altogether unless another team decides to make a fork of the project and continue maintaining it. This made me reflect on my website stack and realize just how much 3rd party software I am dependent on. Every feature that was embedded on my website was either part of mkdocs-material or better yet a 3rd party plugin that depended on mkdocs-material, such as:
- Navigation sidebar
- The search bar
- Dark/light themes
- Blog structure, posting, and tags
- Rendering math equations
- Rendering flowcharts (I only used this once)
- Code syntax highlighting
- "Copy" button in code blocks
- Google Analytics
- Cookie consent
The Search for a New SSG
I debated internally for a long time how to structure a new version of my website that would depend less on 3rd party software. I considered migrating my blog over to a website like Medium.com similar to how I migrated all my photos to Instagram. Then my domain could redirect to a simple HTML page that has my name and social links, so I would have as little as possible on my actual website.
Another idea I had was to actually just write all the HTML from scratch. and I would not depend on any 3rd party software, but I quickly ruled this out because that would be a huge pain to maintain myself, and I prefer the simplicity of Markdown.
My final idea was to search for another static site generator with good community support and minimal 3rd party plugins and dependencies. I did my research on Google Gemini (AI chatbot) and by reading several blog posts by others in my situation. I played around with, or at least considered, all of the following:
- mkdocs I could switch to a different theme, but I may eventually run into the same problems.
- Jekyll I've used it before and is well integrated with GitHub pages, and GitHub officially supports several themes.
- Hugo Dependent on Go, which I am not proficient in (a.k.a. never used before.) Also, I would need to install a community-built theme, similar to
mkdocs. - Astro Seems well-supported, has paid tiers, but even the starter project has so much bloat.
- Hexo Runs on NodeJs (which I love) but all themes are maintained by the community.
- Eleventy Also runs on NodeJs, absolutely no bloat, but you have to develop the theme and plugins yourself.
Choosing Eleventy
Eventually I decided to use a SSG called Eleventy (11ty). As a NodeJS developer, it was the easiest for me to get started, and in fact I didn't even need to install any dependencies since I can run it with the Node package executor. However, it produced the most bare-bones output by itself. For example, starting with this markdown file...
# My Post
Here is a paragraph.
...and running this command...
npx @11ty/eleventy
...produces this result:
<h1>My Post</h1>
<p>Here is a paragraph.</p>
As the developer, you can customize this a lot. Eleventy uses the Liquid templating language (as well as others) within the inputted Markdown/HTML files. You can create HTML Layouts in which the content is rendered, for example to add headers, footers, and metadata. I know, I know - a dependency. While the amount of these can be minimized, unfortunately some are unavoidable. For example, mkdocs depended on a similar templating language called Jinja2.
Now, it's time to rebuild some of the features that I used in mkdocs.
Navigation
If I wanted my website to be usable at all, I would need to add some sort of navigation for all my pages. Luckily, Eleventy does provide some built-in functionality for collections of pages and a pagination feature. I was able to group all my posts into collections.post by adding the "post" tag in front matter (basically page metadata), like this:
---
tags:
- post
---
# My Post
Here is a paragraph.
Since all of my posts will have the same front matter, I am able to set defaults within a directory data file called posts.json:
{
"tags": "post",
"permalink": "{{ page.date | date:'%Y-%m-%d-' }}{{ page.fileSlug | slugify }}/"
}
Here, I also use Liquid to set the permalink for each post to YYYY-MM-DD-slug which guarantees a unique and easily identifiable permalink for each of my posts. I could have also added title: My Post within the front matter and used {{ title | slugify }} instead of {{ page.fileSlug | slugify }}, it's purely a design choice. I figured that my file names change less frequently than my post titles. Also by adding the post date into the file path, I didn't need to add date: to my front matter, either. A hidden benefit of this was that I could avoid front matter altogether.
Page Metadata
For each post, I wanted 3 pieces of metadata:
- Title of post
- Description
- Date posted
Since I didn't have the page title or description within front matter, I needed to create a custom filter to generate the page title and description based on the page content. I achieved this by reading the template content, which is the Eleventy output for each page - which is raw HTML code, shown above. My methodology was to generate the page title using the first header and the description using the first paragraph. After a lot of trial and error (thanks Regex101), I achieved this using a regular expression to extract the text content and strip HTML tags for these elements.
Title Filter
const firstHeader = post.match(/<(h[1-6])[^>]*>(.+?)<\/\1>/s); // Match first <h1> through <h6>
if (firstHeader) {
return firstHeader[2].replaceAll(/<[^>]+>/g, ''); // Strip HTML tags
} else {
return 'Untitled';
}
Description Filter
const firstPara = post.match(/<p[^>]*>(.+?)<\/p>/s); // Match first <p>
if (firstPara) {
return firstPara[1].replaceAll(/<[^>]+>/g, ''); // Strip HTML tags
} else {
return '';
}
Page Date
As for the page date, normally this is achieved through front matter, and accessed through the date or data.date variable, depending on where you are accessing it from.
date: 2025-12-31
If the date doesn't exist, Eleventy then checks the file path, and finally the file creation date as fallbacks. Because I included the date within the file path itself, I can access that value using the page.date variable.
Pagination
One way to easily navigate through the website is to paginate my posts. Eventually (hopefully?), I may have hundreds of posts on here, and it doesn't make sense to put them all on one page. I used Eleventy to paginate collections.post in groups of 10 posts on my index page. This was incredibly easy with front matter and I was able to customize it so that "page 1" is my index page (/) and page 2 and onwards show the page number, for example /p2/, /p3/, etc. At first, I used complex logic to determine the "page back" and "page forward" hyperlinks, but in the end I discovered that Eleventy supplies automatic variables into your page for previousPageHref and nextPageHref, which made it a lot easier to create these hyperlinks.
Anchors
Out-of-box, Eleventy doesn't add anchors in your post. That's the part after the # in the URL and it jumps you to a specific part of the page. For example, click this to jump to this section. I had to write some custom JavaScript to post-process my output to add these anchors to each header element. By working through this, I learned how to slugify a string myself!
// Custom function to slugify a string
function slugify(str = '') { ... }
// Add slugs to all header elements
document.querySelectorAll('h1,h2,h3,h4,h5,h6').forEach(element => element.setAttribute('id', slugify(element.textContent)));
Table of Contents
Using the list of slugs and anchors I've generated, it was easy to add a table of contents with a list of hyperlinks in the same post-processing script. The only trick to this was to figure out what "level" of the table of contents to show the hyperlink. I grabbed the header type using element.tagName (e.g. <h1>) and extracted the level using a regular expression. Then with this value, I could place it accordingly in the table of contents.
const depth = +(element.tagName.match(/\d/)[0]);
Styling
At this point I had the metadata and navigation working which made my website somewhat useable, so it was time to add a touch of style. I wasted an embarrasing amount of time debugging my CSS. The <footer> element is right under my <main> element. It was either shown below the page content (for short posts, it would show up in the middle of the page) or for long posts, it would show up right below the main viewport, so when you scroll down it would be covering some of the content! I knew flex boxes were somehow the solution for this and I've used them many times before, but you wouldn't believe what the CSS fix was.
main {
flex: 1;
}
Before I explain this, what do you think this does?
Explained by Google Gemini:
This is the "magic" line. In a flex container,
flex: 1tells an element to expand to fill all remaining space in the parent. Since the header and footer have fixed or automatic heights,mainstretches to fill the gap.flex: 1sets three properties at once:flex-grow: 1,flex-shrink: 1, andflex-basis: 0. It is the recommended best practice because it handles the "reset" of basis and shrink for you, leading to more predictable layouts.
Truly magic.
Social Links
I used another one of Eleventy's features for this, global data files. This is a JSON file containing any data that can be referenced in a file or layout. I created 2 global data files, one for header links and another for footer links. These files were not necessary, since I could have coded each link directly within the layout once, to be used in all my pages, but it makes it more manageable to update links if I wish to do that later on.
For example, header.json looks like this:
{
"Example": "https://example.nicfv.com/",
...
}
I can generate my header links by referencing the data:
{% for link in header %}
<a href="{{ link[1] }}">{{ link[0] }}</a>
{% endfor %}
I do the same for the footer links, with a small twist.
Icons
For my old website that used mkdocs, I had social icons in the footer which linked to my socials. I really liked that style and wanted to rebuild it myself. After some research, I found 3 possible solutions:
- Fontawesome - Requires an account and gives you an API key... too much barrier to entry.
- Google Icons - Free, no account, and supported by Google, but did not contain the social icons I needed.
- Bootstrap Icons - Free, no account, and contained all the icons I was looking for.
I ended up using Bootstrap Icons which I could quickly get started with by including a single line of CSS and then display icons using a tag attribute.
<i class="bi bi-{{ icon }}"></i>
Syntax Highlighting
I write code for fun! Can you believe that? Also, I like to write about it, sometimes. Which means I post a lot of code. Syntax highlighting is a way for programmers to determine keywords and other special variables, values, and information in their code. Through a similar vetting process as the icons, I went through several libraries for syntax highlighting, including some Eleventy plugins. In the end, I decided on the widely-supported HighlightJS library with the default theme.
Rendering Math
I also like math! Like, like it. Some of my favorite YouTube channels include Numberphile, 3Blue1Brown, and 2Swap. So yeah, I'm a nerd. I get it. Anyway, I'd like a way to render math on my website, please. Thankfully there's a library called MathJax which I already use in many of my projects which does just that, so that was a no-brainer. It was super easy to integrate with my website, since all I had to do was reference the library in my layout - so all pages will automatically render math if present.
I did have to add a minor configuration to allow wrapping math in single dollar-signs, so \$y=mx+b\$ turns into $y=mx+b$. I set the configuration in the same script that I used to add anchors.
Fonts
Lastly, I wanted to select a prettier font than plain old Arial. Nothing wrong with Arial, but having a clean, easy to read font face is really important for text-heavy websites. Through all the research I'd been doing for icons, I learned about Google Fonts, maybe a little late in the game! Google basically hosts hundreds of fonts on their own servers that you can import directly into your CSS with a one-liner.
@import url('https://fonts.googleapis.com/...'); /* Import the font */
element {
font-family: "Font Name";
}
I almost picked Noto Sans, Google's "global" font containing thousands of glyphs, but that's overkill for an English-only blog. I decided on Inter on personal choice, another clean font with more glyphs than I need. The nice thing about this is that it's no-download, easy to switch, and that it's officially supported by Google, which isn't going to change anytime soon.
I definitely see myself using this in other projects.
Wrapping Up
I decided to forgo several of my old website's features in favor of simplicity.
Search barI decided it added too much complexity for the value added.Dark/light themesHowever, I may work on this in a future version.Rendering flowchartsSince I hardly ever used this feature.Code block "copy" buttonIt's so easy toCTRL+C/CTRL+V.Google Analytics/Cookie ConsentWe're free from oppression! It was interesting to get some Analytics - as of the final 90 days using Google Analytics, I had about 10 active users per month. Which is low, but not nothing!
I looked into adding a custom 404 page, but I would need to move off GitHub pages and set up my own server for that.
Final Dependencies
So in the end, I do depend on quite a lot of separate projects. However, I'm not as worried now, since my mkdocs setup was depending on all these initially, plus more.
- Eleventy Just the templating engine, all the navigation, styling, and metadata extraction is all my own.
- Eleventy does have many of its own dependencies, but the developers have been working hard to minimize them.
- Bootstrap Icons Only used for social icons shown in the footer.
- HighlightJS For code syntax highlighting.
- MathJax For rendering math equations.
- Google Fonts For custom font faces.
- GitHub Pages For free web hosting.
- NodeJS for installing Eleventy.
Also, I could live without most of these, if for example the developers decided to stop supporting HighlightJS, then I could forgo syntax highlighting on my website or find another dependency to do that. But all these projects are very well-established, mature, and have a lot of support, and I would be very surprised if any of them disappeared. The only "firm" dependency I have is on the core Eleventy engine itself, and I'm not using any extra plugins or 3rd party software which I will continue to avoid if possible.
More Challenges
Sadly, it's not all sunshine and rainbows with switching from mkdocs to Eleventy. I faced one minor and one major issue. The minor issue is that all of my post dates were off by one day. Doing some research, I discovered that's because the dates are set to UTC but are displayed in my local timezone. Eleventy's documentation offered some complicated fixes which included more dependencies (no, thank you) but after a quick chat with Gemini, it recommended this extremely simple build script, which I couldn't find anywhere in Eleventy's documentation.
TZ=UTC npx @11ty/eleventy
Basically this sets the TZ variable internally within NodeJS, so it's not part of Eleventy at all, actually. But this fix was so simple and easy compared to the other presented solutions.
The major issue I've been facing while switching over is that all of my intra-hyperlinks (from one of my website pages to another) were broken because of Eleventy's minimal processing (which is a good thing.) Instead of converting the hyperlink into the destination file URL, it keeps the original file path ending in *.md. Eleventy does offer plugins to fix this, but the whole reason why I'm making the switch is to depend on as little software as possible.
Breaking news! Coming back to this post the following day I figured it out. The same way I'm post-processing the anchors, I can post-process links to other files as well!
I named each post file by this pattern: YYYY-MM-DD-title.md
The correct hyperlink looks something like this: /YYYY-MM-DD-slug/
I can use a little bit of text processing along with the slugify function I wrote earlier to update all the hyperlinks on the page that match this pattern! Let's test it out by linking to my first-ever blog post, about Rock Paper Scissors.
Conclusion
Holy moly. When I started working on this post I did not think I would be writing this much at all. But I wanted to get it all out while it was still fresh in my mind. I went through my search history, commit history, and chats with Gemini to remember all that I did to get this new website going, and man, it was a lot. I realize now that I have been working on my website framework for almost 5 days straight and that time doesn't include all the research and testing beforehand. One other reason I'm writing this post is because I'm going to be maintaining more website features myself now, and this will help me go back and fix things that may occasionally break. I learned so much doing this project, and honestly, it's been a lot of fun too!
Published on 03 January 2026. Go back to all posts.