Well suffice to say that starting a blog has been on my list of things to do for a while. Today I have finally got round to setting one up, thanks in part to a brief discussion this last week on the fediverse (in the aftermath of the birdsite’s recent takeover). Being something of a perfectionist I’ve been reluctant to get started worried both that each post would take hours and without having fully planned out the whole blog I didn’t want to end up with another “half done” project.
However, in the words of Laura Langdon, “Starting a blog is only as hard as you want it to be!” so I decided to stop making it hard for myself and just start with something very basic. Let’s see what happens…
Requirements
To keep things simple, I don’t want a blog which will take much maintenance with security patches, updates, and the like. Ideally for me this means a generated static site. I also want the blog posts to be available via an RSS feed for anyone who wants to consume the content that way.
I am very familiar with markdown, so that seems that would be a nice way to write simple blog posts without excessive formatting options getting in my way.
To summarise:
- Low maintenance
- Ideally statically generated
- Supports RSS
- Markdown for content
- Quick and simple to initially setup
Having briefly played with Hugo in the past it was vaguely familiar and seemed to fit all of my criteria, so time to get going.
Making the blog
The Hugo quick start guide was a good place to start and made the process rather quick to achieve. The key instructions are summarised below.
Installing hugo
I am running PopOS, a debian linux derivative, so my notes may not work for you. See the install instructions if you are running a different operating system
sudo apt update
sudo apt install hugo
hugo version
to check it has installed correctly. You should see a valid hugo version number.
Creating the site
Complete this from within the directory you want to create the blog inside. The following will create a subdirectory to hold all code and content related to your blog.
hugo new site blog
(whereblog
is the name of your blog, and will also be the name of the directory being created)
Adding a theme
Add a theme, either from themes.hugo.io or making your own.
As I want to keep the initial setup both quick and simple, I’ll use the PaperMod theme for now.
- move into your blog directory we created in the last step (
cd blog
). You should see a set of directories, such ascontent
,themes
andpublic
git init
git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
git submodule update --init --recursive
(also needed when you re-clone your repo (submodules may not get cloned automatically))echo theme = \"PaperMod\" >> config.toml
If you later want to use a different theme, or even one you have created yourself, just add it to the themes/
directory and adjust theme
inside config.toml
Site configuration
Open config.toml
in a text editor and adjust the site name.
Enable RSS discoverability
RSS didn’t feature in the quick start guide, so I did a bit of googling and it turns out:
If you have a Hugo site, you probably already have a working RSS feed. You just might not know about it!
What this appears to mean is that Hugo is capable of generating RSS themes using it’s own RSS template. It even appears to do this out of the box, creating XML
files such as index.xml
. However it doesn’t advertise the available XML feed anywhere on the HTML page, so is undiscoverable.
How we enable RSS discoverability depends on the theme. This makes sense because the HTML is generated from the theme, so it is the theme’s responsibility to advertise the XML RSS feed. Some themes include the necessary <link>
tags out of the box, some require it to be turned on, and others don’t have it at all.
Automatic RSS using theme
The PaperMod theme I picked appears to have RSS advertising as an option. Simply add RSS
to the output config in config.toml
, or add the whole block to the file like I had to. I chose to add JSON
as well (because why not) but only HTML
and RSS
are necessary here to advertise the RSS feed.
[outputs]
home = [ 'HTML', 'RSS', 'JSON' ]
posts = [ 'HTML', 'RSS', 'JSON' ]
Manual RSS for custom themes
If you are not using PaperMod, you will need to consult your theme’s documentation. It may require a different configuration to enable RSS discoverability. Ultimately you need to get the following <link>
tag inside your the theme’s html <head>
tag, regardless of if that is by turning on a configuration option, or by manually adding the following.
<link rel="alternate" type="application/rss+xml" href="{{.Site.BaseURL }}/index.xml" title="{{ .Site.Title }}" />
Add full content to RSS feed
Unfortunately Hugo does not include the full post content in the RSS feed by default. This can be fixed by altering the RSS XML template.
Copy the contents of the default hugo rss template into the file layouts/_default/rss.xml
, creating the directory structure where necessary.
mkdir -p layouts/_default
curl https://raw.githubusercontent.com/gohugoio/hugo/master/tpl/tplimpl/embedded/templates/_default/rss.xml > layouts/_default/rss.xml
Then change the following line:
<description>{{ .Summary | html }}</description>
into
<description>{{ .Content | html }}</description>
Running hugo
hugo serve -D
(where-D
will render any draft posts)hugo serve --bind=10.22.0.14 --baseURL=http://10.22.0.14 -D
you can also optionally bind to an ip address, instead of localhost, if you want to preview from another machine
Save what we’ve got so far
It’s a good practice to regularly commit (save) to git, so let’s select everything and commit our empty site before we go any further.
git add .
git commit -m "Empty blog with theme added"
Let’s also add some configuration to git to prevent it saving files which are generated. Create a file called .gitignore
in your blog directory containing the following content.
*.lock
public/
git add .gitignore
git commit -m "Ignore built files from git"
Adding content
Now that the site is created, we can start adding our blog posts.
URL structure
As with any custom site, you get to choose your own url structure. For example, the following are all valid options:
https://blog.example.com/2022/my-first-post
https://example.com/blog/2022/11/my-first-post
https://example.com/posts/my-first-post
https://example.com/my-first-post
It’s a good idea to have the year in the url of blog posts as they generally contain or make reference to content which ages over time. The year both makes it clear how old the content is and also provides a level of hierarchy to the site. Including the month as well as the year can be a useful subcategorisation if you post a lot. I don’t think that I will be posting particularly often so I plan on keeping my posts grouped only by year.
How do we achieve this?
Hugo assumes the subdirectories directly inside /content
denote the page layout you want to use to render the pages. This is called the content type. So all blog posts
should live inside /content/posts
. If you end up needing different kinds of content, such as author biographies, then these would each be a different content type and live in a different directory within /content
, such as /content/authors
. Each content type can have a different visual design and layout on the page. By default the content type name also gets added to the start of the page url, such as https://example.com/posts/my-first-post
.
With the correct configuration each content type can automatically have a unique URL structure. Using this to also add the year, month and other fields from each post’s front matter prevents us needing to store the posts in year based subdirectories within /content/posts
. This can be configured in a few places but it makes most sense to apply it in the main config file.
Add the following to the bottom of config.toml
to automatically remove the prefix /posts
, and add both the year and month to the url for the posts
content type. More options and examples in the Hugo URL documentation.
[permalinks]
posts = '/:year/:month/:slugorfilename/'
Note: At the time of writing
:slugorfilename
is relatively new and doesn’t work on older Hugo versions. If you get errors, you can use a different variable or update to the latest Hugo version.
Creating a post
Creating a post is as simple as either running a command, or creating a new text file in the correct content directory. Keep in mind your url structure when creating posts. In general file path inside your content
directory should be the same as the url path you would like, however any permalink configuration, like the ones we have just made, will take prescience over the directory structure.
So to create a post at https://blog.example.com/2022/my-first-post
you could either:
If you have added the permalink config:
- run
hugo new posts/my-first-post.md
- or manually create
content/posts/my-first-post.md
with the correct front matter
If you do not added have the permalink config:
- run
hugo new posts/2012/my-first-post.md
- or manually create
content/posts/2022/my-first-post.md
with the correct front matter
Writing the posts
This is where you get creative and write the blog post. You can include text paragraphs, subheading, images, video embeds and the like. If you haven’t used Markdown before, then you probably want to read the instructions or complete the 10 minute tutorial for the Commonmark version of Markdown which Hugo uses.
Remember to spell check your work. Depending on what application you are using to write your posts, it may already automatically spell check for you, or you may be able to install a spell checking extension/plugin.
Save posts into git
Once the content is added, let’s save it again. This time let’s just save the posts one at a time.
git add content/posts/my-first-post.md
git commit -m "Add first post"
If writing posts takes more than one sitting it may be a good idea to commit each draft, allowing you to see the changes you are making and easily undo an edit if you are unhappy with it.
Let’s also take a moment to consider if we want to push our git repo to a remote server. Since your blog posts are about to be published freely on the internet, it generally isn’t a problem having the whole blog in a public git repo. However be aware that if you do keep your git repo public then any draft posts will be visible in git before being published and also leave a public history of any edits you make to any posts, both draft and published posts.
Deploying
Hosting a static side can be done with pretty much any commercial web hosting company, or one of the following.
- Sourcehut pages
- Github pages
- Self hosted on an existing web server
Since these three all simple options, I’ll cover each of these three quickly.
Sourcehut pages
The following instructions are based on the official sourcehut pages documentation.
You can use the following commands to build the site, zip it into an archive file and deploy straight to sourcehut pages. If you have your own domain, you can use it instead of username.srht.site
by following the custom domain instructions to configure DNS. Then use your own domain in place of username.srht.site
when publishing with either the curl request or hut
command.
Manual deployment
In the commands block, replace the following:
username
with your actual username, orusername.srht.site
with your custom domainaccess token
with the access token you generate (limit scope to the grant stringpages.sr.ht/PAGES:RW
).
hugo
tar -C public -cvz . > site.tar.gz
curl --oauth2-bearer "access token" \
-Fcontent=@site.tar.gz \
https://pages.sr.ht/publish/username.srht.site
Automated deployment
You can also automate deployment with a Sourcehut build script by:
- creating a new git repo on sourcehut
- creating a file called
.build.yml
in your repo with following contents (changingusername.srht.site
to your blog url).
image: alpine/latest
packages:
- hut
- hugo
oauth: pages.sr.ht/PAGES:RW
environment:
site: username.srht.site
tasks:
- package: |
cd $site
hugo
tar -C public -cvz . > ../site.tar.gz
- upload: |
hut pages publish -d $site site.tar.gz
Now, each time you make a change and push it to your sourcehut repo it will get automatically built and deployed to your blog.
Github pages
Github pages is similar to Sourcehut pages but works slightly differently. Whereas in Sourcehut we can build and deploy the site as a tarball (.tar.gz
file) for Github the site’s contents needs to be committed into a git repo. This means that, while the manual deployment method is still possible, it is more convoluted if we also want to use Git to track our source code changes. (If I, however, am mistaken then please let me know)
The following instructions summarised from Hugo’s documentation for GitHub Actions. This will set things up so that each time you make a change and put it into your github repo, it will build and deploy the changes to your blog.
- Create a public git repo on GitHub named
username.github.io
- Add github actions file into your repo to build the site, see the hugo documentation for github actions example
- Add your github repo as a remote to your local git repo
git remote add origin git@github.com:username/username.github.io
- Push your repo to github
git push -u origin master
- Navigate to
https://username.github.io
in your browser - Configure your custom domain and verify your domain on github
Self hosted on existing web server
If you are running a webserver then I will assume that you know how to know how to deploy html, css and js files to it as the steps will depend on exactly how your server is setup.
The key step is to run hugo
during each deployment of the site.
For example, I’ll be using sourcehut builds to both build the site and also rsync onto the web server, though GitHub Actions, GitLab CI or just a simple bash script should work fine too.
rrsync
For example on a Linux server you can use the rrsync
script forced in authorized_keys
to further limit a specific public ssh key:
command="rrsync /var/www/blog.example.com/public/",no-agent-forwarding,no-port-forwarding,no-pty,no-user-rc,no-X11-forwarding ssh-rsa AAAAB1NzaC1qc2...qa== sourcehut
Sourcehut builds
The following .build.yml
file will download the git repo, run hugo in teh package
section then deploy it with rsync to the web server.
image: alpine/latest
packages:
- hugo
- rsync
sources:
- https://git.sr.ht/~techwilk/blog
secrets:
- xxxxxxxxxx
- xxxxxxxxxx
environment:
site: blog
tasks:
- package: |
cd $site
hugo
- deploy: |
cd $site
echo "StrictHostKeyChecking=no" >> ~/.ssh/config
rsync -avpP public/ blog-user@webserver.example.com:/ --delete
triggers:
- action: email
condition: always
to: "Your Name" <your-public-email-address@example.com>
The two secrets contain
- the private ssh key for the blog user on your webserver
- an ssh config file to hold any ssh ports and
Summary
You should now have a blog site live on the internet, well done!
There are a few things we haven’t setup yet, including the sending and receiving of webmentions. However, those are a tasks for another day. Once I get them working I’ll write another post and link it here.
To make the most of both Hugo and your blog, it’s worth planning to go back and read a few bits of their documentation to become more familiar with the tools and tweak some more configurations. I’ve found it helpful to read the following.
Further reading
For your theme
- https://adityatelange.github.io/hugo-PaperMod/posts/papermod/papermod-features/
- or the equivalent for your theme.
Note that if you’ve made your own theme I still recommend reading though at least one published theme’s documentation for inspiration.
For understanding configurations in config.toml
- https://gohugo.io/getting-started/configuration/
- https://gohugo.io/getting-started/configuration-markup/
For understanding blog posts, front matter and urls
- other people’s blog posts, such as this one
- https://gohugo.io/content-management/organization/
- https://gohugo.io/content-management/urls/#permalinks