Personal Website with Jupyter Support using Nikola and GitHub Page

I decide to build my new website and occasionally post blogs on research, software, and computing.

For the blogging tool, basic requirements are:

  • It must be a static site that can be easily version-controlled on GitHub. So, not WordPress.

  • I'd like the framework to be written in Python. So, not Jekyll.

  • It should support Jupyter notebook format, so I can easily post computing & data analysis results.

The options come down to Pelican (the most popular one), Tinkerer (Sphinx-extended-for-blog), and Nikola. I finally settle down on Nikola because it is super intuitive, has a clear and user-friendly documentation, and supports Jupyter natively. It takes me like 10 minutes to set up the desired website layout. Other tools are also very powerful but it takes me longer to get the configuration right.

This post records my setup steps, to help others and the forgetful future me. Building such a website only requires basic knowledge in:

  • Git and GitHub

  • Python

  • Markdown (works fine) or reStructuredText (preferred)

I assume no knowledge in Web front-end stuff (HTML/CSS/JS). This should be typical for many computational & data science students.

2 Installation and initialization

The official getting-started guide explains this well. I just summarize the steps here.

I come from the scientific side of Python, so use Conda to create a new environment:

conda create -n blog python=3.6
source activate blog
pip install Nikola[extras]

Making the first demo site is as simple as

  • Run nikola init in a new folder, with default settings.

  • Then run nikola auto to build the page and start a local server for testing.

  • Visit localhost:8000 in the web browser.

Adding a new blog post is as simple as:

  • Run nikola new_post (defaults to reStructuredText format, add -f markdown for *.md format)

  • Write blog contents in the newly generated file (*.rst or *.md) in the posts/ directory.

  • Rerun nikola auto. You can also keep this command running so new contents will be automatically updated.

All configurations are managed by a single conf.py file, which is extremely neat.

Before adding any real contents, you should first configure GitHub deployment, layout, themes, etc.

3 Github deployment and version control

There is an official deployment guide but I feel that more explanation is necessary.

Most posts mention deployment at the final step, but I think it is good to do so at the very beginning.

We will use GitHub pages to host the website freely. Any GitHub repo can have its own GitHub page, but we will use the special one called user page that only works for a repo named [username].github.io, where [username] is your GitHub account name (mine is jiaweizhuang). The website URL will be https://[username].github.io/.

You will need to:

  1. Create a new GitHub repo named [username].github.io.

  2. Initialize a Git repo (git init) in your Nikola project directory.

  3. Link to your remote GitHub repo (git remote add origin https://github.com/[username]/[username].github.io.git)

So far all standard git practices. The non-standard thing is that you shouldn't manually commit anything to the master branch. The master branch is used for storing HTML files to display on the web. It should be handled automatically by the command nikola github_deploy. To version control your source files (conf.py, *.rst, *.md), you should create a new branch called src:

git checkout -b src

My .gitignore is:

cache
.doit.db*
__pycache__
output
.ipynb_checkpoints
.DS_Store

You can manually commit to this src branch and push to GitHub.

In conf.py, double-check that branch names are correct:

GITHUB_SOURCE_BRANCH = 'src'
GITHUB_DEPLOY_BRANCH = 'master'
GITHUB_REMOTE_NAME = 'origin'

I also recommend setting:

GITHUB_COMMIT_SOURCE = False

So that the nikola github_deploy command below won't touch your src branch.

To deploy the content on master, run:

nikola github_deploy

This builds the HTML files, commits to the master branch, and pushes to GitHub. The actual website https://[username].github.io/ will be updated in a few seconds.

You end up having:

  • A well version-controlled src branch, with only source files. You can add meaningful commit messages like for other code projects.

  • An automatically generated master branch, with messy html files which you never need to directly look at. It doesn't have meaningful commit messages, and the commit history is kind of a mess (diff between HTML files).

Note

Remember that nikola github_deploy will use all the files in the current directory, not the most recent commit in the src branch! master and the src are not necessarily synchronized if you set GITHUB_COMMIT_SOURCE = False.

For all the tweaks later, you can incrementally update the GitHub repo and the website, by manually pushing to src and using nikola github_deploy to push to master.

4 Change theme

The default theme looks more like a blog than a personal website. Twitter's Bootstrap is an excellent theme and is built into Nikola. In conf.py, set:

THEME = "bootstrap4"

The theme can be further tweaked by Bootswatch but I find the default theme perfect for me :)

5 Set non-blog layout

Official non-blog guide explains this well. I just summarize the steps here.

Nikola defines two types of contents:

  • "Posts" generated by nikola new_post. It is just the blog post and will be automatically added to the main web page whenever a new post is created.

  • "Pages" generated by nikola new_page. It is a standalone page that will not be automatically added to the main site. This is the building block for a non-blog site.

In conf.py, bring Pages to root level:

POSTS = (
    ("posts/*.rst", "blog", "post.tmpl"),
    ("posts/*.md", "blog", "post.tmpl"),
    ("posts/*.txt", "blog", "post.tmpl"),
    ("posts/*.html", "blog", "post.tmpl"),
)
PAGES = (
    ("pages/*.rst", "", "page.tmpl"),  # notice the second argument
    ("pages/*.md", "", "page.tmpl"),
    ("pages/*.txt", "", "page.tmpl"),
    ("pages/*.html", "", "page.tmpl"),
)

INDEX_PATH = "blog"

Generate your new index page (the entry of you website):

$ nikola new_page
Creating New Page
-----------------

Title: index

To add more pages to the top navigation bar:

$ nikola new_page
Creating New Page
-----------------

Title: Bio

And then add it to conf.py:

NAVIGATION_LINKS = {
    DEFAULT_LANG: (
        ("/index.html", "Home"),
        ("/bio/index.html", "Bio"),
        ...
    ),

6 Enable Jupyter notebook format

Just add *.ipynb as recognizable formats:

POSTS = (
    ("posts/*.rst", "blog", "post.tmpl"),
    ("posts/*.md", "blog", "post.tmpl"),
    ("posts/*.txt", "blog", "post.tmpl"),
    ("posts/*.html", "blog", "post.tmpl"),
    ("posts/*.ipynb", "blog", "post.tmpl"), # new line
)
PAGES = (
    ("pages/*.rst", "", "page.tmpl"),
    ("pages/*.md", "", "page.tmpl"),
    ("pages/*.txt", "", "page.tmpl"),
    ("pages/*.html", "", "page.tmpl"),
    ("pages/*.ipynb", "", "page.tmpl"), # new line
)

With the current version (v8), that's all you need to do!

Create a new blog in notebook format:

$ nikola new_post -f ipynb

7 Add social media button

You might want to add buttons for other sites like GitHub and Twitter, or any icons from Font Awesome.

Taken from this post by Jaakko Luttinen, the minimal example is (only one GitHub button):

EXTRA_HEAD_DATA = '<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/latest/css/font-awesome.min.css">'

CONTENT_FOOTER = '''
<div class="text-center">
<p>
<span class="fa-stack fa-2x">
<a href="https://github.com/[username]">
    <i class="fa fa-circle fa-stack-2x"></i>
    <i class="fa fa-github fa-inverse fa-stack-1x"></i>
</a>
</span>
</p>
</div>
'''

8 Various tweaks

8.1 Add table of content

For *.rst posts, simple add:

.. contents::

Or optionally with numbering:

.. contents::
.. section-numbering::

8.2 Tweak Archive format

To avoid grouping posts by years:

CREATE_SINGLE_ARCHIVE = True

The creation time of each blog post is displayed down to minutes by default. Only showing the date seems enough:

DATE_FORMAT = 'YYYY-MM-dd'

8.3 Enable comment system

Because static sites do not have databases, you need to use a thiry-party comment system as documented on the official doc. The steps are:

  1. Sign up for an account on https://disqus.com/.

  2. On Disqus, select "Create a new site" (or visit https://disqus.com/admin/create/).

  3. During configuration, take note on the "Shortname" you use. Other configs are not very important.

  4. At "Select a plan", choosing the basic free plan is enough.

  5. At "Select Platform", just skip the instructions. No need to insert the "Universal Code" manually, as it is built into Nikola. Keep all default and finish the configuration.

In conf.py, add your Disqus shortname:

COMMENT_SYSTEM = "disqus"
COMMENT_SYSTEM_ID = "[disqus-shortname]"

Deploy to GitHub and the comment system should be enabled.

Comments

Comments powered by Disqus