Scientific Blogs: Jekyll, Mkdocs, R-Markdown or Quarto?

What are the best tools to write and publish scientific or research blogs? I end up using Jekyll with Katex rendering on server-side.

If you want to create a website to organize your writings, technological notes, research ideas, etc., you might struggle over choosing different frameworks to do a ‘proper’ job. Here are some well-known frameworks in this business: Hexo, Hugo, Jekyll, Mkdocs, Quarto, R Markdown, Jupyter Book You name it!

There are many posts talking about pros and cons for each framework, here are some of them:

Over the last five years, I have tried Hugo with academic theme, Mkdocs with material theme, R Bookdown, etc. In the end, I chose Jekyll over Mkdocs with material theme. Why?

First, the performance score of Mkdocs with material theme is not very good. Even though it does have fantastic plugins like mkdocs-jupyter, it makes the website and each post become heavy. The following figure gives the lighthouse performance score of landing page of my old website built with Mkdocs Material.

Screenshot of performance score
Figure 1. Performance Score for Mkdocs Material

Things get worse when I tried to write a mathematical blogs. Figure 2 speaks for itself.

Screenshot of performance score for a math post
Figure 2. Performance Score for a Mathematical Post Made by Mkdocs Material

Rendering math fast

Since many of my posts include lots of math equations. I have to make sure the page will be rendered smoothly. With Jupyter Notebook, only MathJax 2.7 is supported, which makes it very slow when your post grows bigger. For example, no one could finish reading this post without being distracted.

I have tried to build up my own framework by leveraging nbconvert, which does have some functions to make the post become clean, such as hiding In[], Out[] in the post, providing copy button for the code block. However, keeping the style consistent and pre-rendering math equation become very difficult and time consuming. I did manage to render math equations on the server side by using mathjax-node-page(here is the repo if you want to do the same thing.). It turned out this issue is more general than I thought (Fedorin, n.d.; joashc, n.d.).

Regards rendering math online, there are many options. Readers are recommended checking those demos. In the end, I was nudged to chose Jekyll because of the plugin jekyll-katex allows me to render math on server-side easily. The theme of this blog was taken from Gundersen’s blog (Gundersen, 2020).

Here are some examples of rendering math equations in the post.

Variance of the sum of multiple random variables. For random variables XiX_i we have:

Var(i=1nXi)=i=1nj=1nCov(Xi,Xj)\text{Var}\left(\sum_{i=1}^{n}X_{i}\right)=\sum_{i=1}^{n}\sum_{j=1}^{n}\text{Cov}\left(X_{i},X_{j}\right)

Using the decomposition formula of variance, we have

var(i=1nXi)=E([i=1nXi]2)[E(i=1nXi)]2{\rm var} \left( \sum_{i=1}^{n} X_i \right) = E \left( \left[ \sum_{i=1}^{n} X_i \right]^2 \right) - \left[ E\left( \sum_{i=1}^{n} X_i \right) \right]^2

Now note that (i=1nai)2=i=1nj=1naiaj(\sum_{i=1}^{n} a_i)^2 = \sum_{i=1}^{n} \sum_{j=1}^{n} a_i a_j, which is clear if you think about what you’re doing when you calculate

(a1+...+an)(a1+...+an),(a_1+...+a_n) \cdot (a_1+...+a_n),

by hand. Therefore,

E([i=1nXi]2)=E(i=1nj=1nXiXj)=i=1nj=1nE(XiXj)E \left( \left[ \sum_{i=1}^{n} X_i \right]^2 \right) = E \left( \sum_{i=1}^{n} \sum_{j=1}^{n} X_i X_j \right) = \sum_{i=1}^{n} \sum_{j=1}^{n} E(X_i X_j)

similarly,

[E(i=1nXi)]2=[i=1nE(Xi)]2=i=1nj=1nE(Xi)E(Xj)\left[ E\left( \sum_{i=1}^{n} X_i \right) \right]^2 = \left[ \sum_{i=1}^{n} E(X_i) \right]^2 = \sum_{i=1}^{n} \sum_{j=1}^{n} E(X_i) E(X_j)

So,

var(i=1nXi)=i=1nj=1n(E(XiXj)E(Xi)E(Xj))=i=1nj=1ncov(Xi,Xj){\rm var} \left( \sum_{i=1}^{n} X_i \right) = \sum_{i=1}^{n} \sum_{j=1}^{n} \big( E(X_i X_j)-E(X_i) E(X_j) \big) = \sum_{i=1}^{n} \sum_{j=1}^{n} {\rm cov}(X_i, X_j)

Pairwise independence. In general:

Var(i=1nXi)=i=1nj=1nCov(Xi,Xj)\text{Var}\left(\sum_{i=1}^{n}X_{i}\right)=\sum_{i=1}^{n}\sum_{j=1}^{n}\text{Cov}\left(X_{i},X_{j}\right)

If XiX_i and XjX_j are independent then EXiXj=EXiEXj\mathbb{E}X_{i}X_{j}=\mathbb{E}X_{i}\mathbb{E}X_{j} and consequently

Cov(Xi,Xj):=EXiXjEXiEXj=0\text{Cov}\left(X_{i},X_{j}\right):=\mathbb{E}X_{i}X_{j}-\mathbb{E}X_{i}\mathbb{E}X_{j}=0

This leads to:

Var(i=1nXi)=i=1nVarXi\text{Var}\left(\sum_{i=1}^{n}X_{i}\right)=\sum_{i=1}^{n}\text{Var}X_{i}

Set up the website

Unlike python’s community, the tutorial documents for setting up a website with Github Pages are not very easy to follow. It took me a while to set up this website. Since all static site generators are to organize files and templates, one has to understand how Jekyll organize your markdown files and templates of themes. Now, let’s generate a static website step by step.

ModuleNotFoundError: No module named 'apt_pkg'
ModuleNotFoundError: No module named 'apt_inst'

This means you need link your packages:

# go to the package folder
cd /usr/lib/python3/dist-packages
# check apt_pkg version
ls -l | grep apt_pkg
# check apt_inst version
ls -l | grep apt_inst
# link the package for your version
sudo cp apt_pkg.cpython-38-x86_64-linux-gnu.so apt_pkg.so
sudo cp apt_inst.cpython-38-x86_64-linux-gnu.so apt_inst.so
cd github-repository
# you can call it something else, but docs is a good folder name 
mkdir docs 
cd docs 
# create the jekyll website
jekyll new . 

Generally, you have two kinds of folders:

  1. folders with underscore like _layouts
  2. folders without underscore like images and assets
.
├── index.markdown  # home page - index 
├── _config.yml  # manager the configure options 
├── Gemfile  # manager the ruby environment 
├── Gemfile.lock  # lock the environment 
├── 404.html
├── about.markdown  # another landing page if you need it
├── assets  # js file 
│   └── katex.min.js
├── css  # css files 
│   ├── blog.css
│   ├── main.css
│   ├── markdown.css
│   └── trac.css
├── images  # images for your website 
│   ├── blog
│   │   ├── mkdocs1.png
│   │   └── mkdocs2.png
│   ├── favicon.png
│   └── inclusion-exclusion.png
├── blog  # blog section 
│   └── index.html
├── _blogs  # all blogs 
│   ├── example.md
│   └── jekyll-mkdocs-quarto.md
├── _bibliography
│   └── references.bib
├── _includes  # header and nav templates 
│   ├── header.html
│   └── nav.html
├── _layouts  # layout template for different sections 
│   ├── blog_default.html
│   └── default.html
└── _site  # built site contents goes into this folder

Let’s say you want to build up a section or portfolio for your website called teaching section. Then you need:

  1. teaching folder to set up the landing page
  2. _teaching folder to store different contents
  3. a html file in _layout folder if you want a different layout

To learn how this website was built, please visit my repo.

Here is my CICD file in .github/workflows:

name: Build and Deploy My Jekyll Site to GitHub Pages

on:
  push:
    branches:
      - main

jobs:
  jekyll:
    runs-on: ubuntu-latest

    permissions:
      contents: write

    steps:
      - name: 📂 setup github actions 
        uses: actions/checkout@v2
      
      # setup ruby environment 
      - name: 💎 setup ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 3.1.2

      - name: 🔨 install dependencies & build site
        uses: limjh16/jekyll-action-ts@v2
        with:
          enable_cache: true

      - name: 🚀 deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          github_token: $
          publish_dir: ./_site

After comitting and pushing, your website should be posted. The following is the performance score of this post.

Screenshot of performance of this post
Figure 3. Performance Score of This Post

Adding tags and tags page

Sooner or later, your posts will grow to a number that you need to sort them based on different tags of categories. To do this, you can add tags for each post and tags page to sort your blogs. I learned to do this with the following post:

only use lowercase for tags in the post!

  1. Fedorin, D. Math Rendering is Wrong. https://danilafe.com/blog/math_rendering_is_wrong/
  2. Gundersen, G. (2020). How I Built This Blog. https://gregorygundersen.com/blog/2020/06/21/blog-theme/
  3. joashc. Prerendering Mathjax. https://joa.sh/posts/2015-09-14-prerender-mathjax.html