Motivation

When started this blog in 2017 I decided to use jekyll to generate my page as static sites. This makes it easy for me as I do not have to maintain any software or databases at the server level. The page loads quite fast as well due to only static html, some css and a js file for Mermaid. It worked quite well but caused some issues on the generation side. I was looking for an alternative to jekyll to make the page generation faster and management easier.

What about jekyll?

Jekyll is still a great site generator. It is quite stable, has a lot of extensions and is well-known. It is easy to find some help out there. There are a few things I do not like about my jekyll setup.

  • The only application based on ruby I use
  • Updates break building quite often due to dependencies
  • Container to build needs often to be updated before regenerating
  • Performance is not the best
  • Theme was not as separated as it is today

What about gohugo?

  • Written in GO - I also write go
  • Just one executable
  • amazing fast (my whole page gets generated under 500ms)
  • clean separation of theme and content
  • mighty templating system with overridable partials
  • everything I need in one application

Running gohugo for development

For development I wrote two bash scripts. One to just build the site and one to use the gohugo web server to host the page and update it on file changes.

Here is the server.sh that starts a development server which includes drafts and posts with publishdate in the future.

#!/bin/bash
hugo --config config.draft.toml --buildFuture -s ./src server

The second file build.sh can be used to just build the site.

#!/bin/bash
hugo -s ./src -d ../public

Migration steps I took

  1. Create a new gohugo site using the hugo import jekyll helper
  2. Add a theme for prototyping and fix some content
  3. Adapt the front matter of the last few posts to test
  4. Port over the current jekyll theme and adapt it for hugo
  5. Check the front matter values of all posts and adjust them
  6. Ensure drone is able to build and publish the page
  7. Release the theme for others to use :-D

Migrating content

This is not the part, which takes the least effort. The gohugo documentation provides some links and hugo itself is able to import the posts from a jekyll setup. The migration builds up the file structure but has its limit. The front matter does not get converted correctly. This import does not give you a ready to use site but a good starting point. Just import it into an empty folder and add the additional files later on.

Add a theme and complete filesystem strucutre

To make the new created site work, you have to add a theme to your site. You can find a list of themes using the gohugo theme browser . I used the terminal theme to test my format. If you find a theme that suits your needs perfectly, just use this theme. I just wanted something to work and test with until my original theme got ported.

Next is adding a config.toml file to the root folder sets up the basics to make the page work. Now you should be able to run hugo -s ./src server to start your local hosting. Please note that the directory ./src is the location of my hugo page from the view of the git repository root directory.

My filesystem looked close to the listing below.

.<git_repo_root>
├── assetoriginals
│   ├── some folders with original images
│   └── ...
└── src
    ├── content
    │   └── posts
    │       ├── 2017
    │       ├── 2018
    │       └── 2019
    ├── layouts
    │   └── partials
    ├── resources
    │   └── _gen
    │       └── ...
    ├── static
    │   └── assets
    │       ├── images
    │       └── ...
    └── themes
        └── tiles
            ├── archetypes
            ├── assets
            │   ├── css
            │   │   └── fonts
            │   └── js
            ├── layouts
            │   ├── _default
            │   ├── partials
            │   └── shortcodes
            └── static
                └── fonts

Starting the hugo server should show you something like the following output.

                   | EN
+------------------+-----+
  Pages            | 248  
  Paginator pages  |   7  
  Non-page files   |   0  
  Static files     |  69  
  Processed images |   0  
  Aliases          | 103  
  Sitemaps         |   1  
  Cleaned          |   0  

Total in 95 ms
Watching for changes in /home/<username>/<gitreporoot>/src/{content,layouts,static,themes}
Watching for config changes in src/config.toml
Environment: "development"
Serving pages from memory
Running in Fast Render Mode. For full rebuilds on change: hugo server --disableFastRender
Web Server is available at //localhost:1313/ (bind address 127.0.0.1)
Press Ctrl+C to stop

Fixing markdown files

Your post will display but might be out of order and miss all the metadata. Yea, I could have written a script to migrate them as well. As you can see, I do not have that many posts, so I just migrated the front matter by hand and tuned it during the theme build up. I also ensured the category and tags are set and adjusted them

Creating a theme

The idea of a theme and how it is built is not completely different from the ones in jekyll. But the implementation differs a lot. I just started with an empty folder, and the terminal theme that I used to experiment. There are some things you just have to know or it will not really work out well :-D

First is the directory structure as the folders have some meanings.

.<theme_root>
├── archetypes
├── assets
│   ├── css
│   │   └── fonts
│   └── js
├── layouts
│   ├── _default
│   ├── partials
│   └── shortcodes
└── static
    └── fonts

The folder archetypes contains some default item templates. I only have one type of content template, so I prepared the default.md file with the default tags for new posts.

Next follows the assets folder that contains some files like css (scss), javascript. Files within this folder get processed and can be bundled, minified extended with an integrity check attribute on the links.

In the layout folder go the core template files of the theme. Under _default are the base pages of all generated pages and the page templates located. These define the structure of the html output as define the extension points. The partials folder contain reusable partial rendering files like the site footer or header. These files can be overwritten by the page. A special feature are the files in the shortcodes folder. These little templates define some short codes that can be used in content files. I use one to generate a link to a external site with the relevant attributes and an icon.

Last but not least is the static folder that contains all unprocessed files like images that do not get changed during the page build.

With these basic folders, it is possible to build the new theme for the first time. I got a hint to take a closer look to the Academic theme as it has a wide list of supported features. I did look at it but it is far to big for my little needs. So I did not use this parts. In fact, I created the files on my own and moved over the html template content I would like to keep from jekyll to the new theme. I also had to restructure the templates to fit in the new ones using the partials.

Shortcodes

Next I started to add new features like the linkBlank shortcode and paging support, which I was missing in my old jekyll setup. To build such a shortcode, just place a file e.g. linkBlank.html within the shortcodes folder.

<!--
Usage: {{< linkBlank "URL" "TITLE" >}}
-->
{{ $linkTitle := .Get 1 }}
{{ $linkUrl := .Get 0 }}

{{ if $linkTitle }}
<a target="_blank" rel="noreferrer" href="{{ $linkUrl | absLangURL }}" aria-label="{{ $linkTitle | markdownify }}">{{ $linkTitle | markdownify }} <i class="fa fa-external-link"></i></a>
{{ else }}
<a target="_blank" rel="noreferrer" href="{{ $linkUrl | absLangURL }}" aria-label="{{ $linkUrl }}">{{ $linkUrl }} <i class="fa fa-external-link"></i></a>
{{ end }}

Hugo pipes

I also added the scss processing, minification, fingerprinting and integrity checks to the css and js tags. Using the Hugo Pipes to do all this in one line without complex code. I did the same for the javascripts as well.

{{ $style := resources.Get "css/main.scss" | toCSS | minify | fingerprint "sha512" }}
<link rel="stylesheet" href="{{ $style.Permalink }}" integrity="{{ $style.Data.Integrity }}" crossorigin="anonymous">

All this security and integrity checks come with a price tag. This is the price of adding headers to your http request to make all of this work and not getting blocked. This is important if you host your page using more than one URL. E.g. haefelfinger.ch and www.haefelfinger.ch. Using the one with www did not load the css and js files as they are referenced using the one without host part in the domain.

HTTP headers

Here is an example of such a .htaccess as you may use it with your apache web server. First I redirect all not https requests to https. The important headers are the two at the end of the file. The first defines the allowed sources for the given kind of content file. This enables downloading of scripts and styles from the given locations. The last one allows www.haefelfinger.ch to access the resources as well. Using these headers you should be able to get an A rating on securityheaders.com checks.

RewriteEngine On
RewriteCond %{HTTPS} off
RewriteCond %{HTTP:X-Forwarded-Proto} !https
RewriteRule ^(.*)$ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options nosniff
Header always set X-Frame-Options "SAMEORIGIN"
Header always set X-Xss-Protection "1; mode=block;"
Header always set Referrer-Policy "strict-origin"
Header always set Content-Security-Policy "default-src 'self' https://haefelfinger.ch https://*.haefelfinger.ch https:; script-src 'self' https://haefelfinger.ch https://*.haefelfinger.ch; style-src 'self' 'unsafe-inline' https://haefelfinger.ch https://*.haefelfinger.ch; img-src 'self' https://haefelfinger.ch https://*.haefelfinger.ch; require-sri-for style;"
Header always set Access-Control-Allow-Origin "https://www.haefelfinger.ch"

Building hugo using drone.io

Now I do not like to build the page every time on my on. I like to use my drone.io setup to build and publish my site. Check out the drone.io web site on how to set up drone using github or gitea. When the server is running, you can use this example .drone.yml as starting point. My final version has more than this. It supports a beta build to a different domain and a production build for the main domain.

kind: pipeline
name: BuildAndPublish

steps:
- name: build
  image: phaefelfinger/drone-hugo
  settings:
    pull: always
    url: https://haefelfinger.ch
    source: /drone/src/src
    output: /drone/src/public

- name: upload
  image: cschlosser/drone-ftps
  environment:
    PLUGIN_HOSTNAME: your.server.tld:21
    FTP_USERNAME:
      from_secret: ftp_username
    FTP_PASSWORD:
      from_secret: ftp_password
    PLUGIN_SECURE: true
    PLUGIN_SRC_DIR: /public
    PLUGIN_DEST_DIR: /

trigger:
  branch:
  - master

Docker container to build the page

I also built my own drone-hugo plugin as the original messed up the extended drone version somehow. You can find the original plugin on github.com . I just took the code and built the container using the extended drone version to prevent permanent download of hugo itself. Just use the original drone-hugo plugin if you do not need the newest or extended version.

Uploading via FTP

The drone-ftps plugin is a handy plugin to upload a local directory using ftp to your server. It supports TLS to secure your ftp connection and synchronizes the content of the local folder. It also removes the files no longer present during the update.

What I messed up

For sure, I messed some stuff up as well. The most important thing I messed up, were the permalinks of my blogposts. Yea, I know. It is not ideal but to be honest, I do not get that many hits and do not have so many posts that it would be hard to find the information published on the site. Is this fixable? Sure, but I am just to lazy to write all the redirects and build a .htaccess for this.

Another thing was the migration. I had to update the metadata multiple times as I was not sure if my custom theme would be the final one. As the metadata contain theme related stuff like the size of the tile, this has to be configured in all files. Again here as well, it is just a small site with lower requirements.

I like to get your theme for my page

Sure, but it still needs some work until I can push and release it. I plan to add it to the theme git repository and this needs some little preparation ;-) As soon as it gets available, I’ll update this post with the new link.