Migrate from jekyll to gohugo
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
- Create a new gohugo site using the hugo import jekyll helper
- Add a theme for prototyping and fix some content
- Adapt the front matter of the last few posts to test
- Port over the current jekyll theme and adapt it for hugo
- Check the front matter values of all posts and adjust them
- Ensure drone is able to build and publish the page
- 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.