Hugo Guide

So you wanted to get started with hugo? Its one of the most used static site generators out there and its pretty powerful. However, that being said the new user experience is lackluster, so I wrote this guide to help make getting up and running with it an easier endeavor. This guide is light on images as I want to focus on the essence of hugo rather than tangential semantics. I assume that you have already have everything installed and are smart enough to understand how to get hugo installed and running.

First Steps

First navigate to where you want to put the site files. Use the cli to create a new site.

sh$ hugo new site .
Congratulations! Your new Hugo site was created in /home/theorytoe/projects/hugo_tut2.

Just a few more steps...

1. Change the current directory to /home/theorytoe/projects/hugo_tut2.
2. Create or install a theme:
   - Create a new theme with the command "hugo new theme <THEMENAME>"
   - Or, install a theme from https://themes.gohugo.io/
3. Edit hugo.toml, setting the "theme" property to the theme name.
4. Create new content with the command "hugo new content <SECTIONNAME>/<FILENAME>.<FORMAT>".
5. Start the embedded web server with the command "hugo server --buildDrafts".

See documentation at https://gohugo.io/.

You get some output (that we will ignore) and you’ll notice that your working directory is now populated with some items. I’ll be focusing on the following and ignoring the rest.

This is the structure of a basic website, however, we are missing a couple things in order to actually see any content. If we run hugo with no args, we will see the new public directory with all of the built content in it. You’ll notice this directory is empty. Lets fix this!

Layout Basics

Hugo content is interlaced into layouts, these define how the content is placed into the rest of the document. In hugo, we have 3 primary types of layouts for markdown/html documents. The default layouts that apply to every page are in layouts/_default.

Generally, most hugo sites will write the main document skeleton in layouts/_default/baseof.html Then have blocks that are defined by single.html and list.html respectively. Lets go ahead and define these layouts.

layouts/_default/baseof.html:

<!doctype HTML>
<html>
<head>
<title>{{ .Site.Title }}</title>
</head>
<body>
{{ block "main" . }}
<p>No content to be found!</p>
{{ end }}
</body>
</html>

layouts/_default/single.html:

{{ define "main" }}
<article>
{{ .Content }}
</article>
{{ end }}

layouts/_default/list.html:

{{ define "main" }}
<ul>
{{ range .Pages }}
    <li><a href='{{ .RelPermalink }}'>{{ .Title }}</a></li>
{{ end }}
</ul>
{{ .Content }}
{{ end }}

Now if you run hugo serve in your shell and open the link it gives you in your browser, you will see a page! Its rather empty however, so lets change that. By default, hugo will define a default archtype for a post. I generally dont like the TOML front matter, so we will be changing it from toml to yaml (which has better editor support for being integrated into markdown).

--- archetypes/default.md       2024-10-29 21:20:08.665684146 -0700
+++ archetypes/default.md.~1~   2024-10-31 13:19:34.784986313 -0700
@@ -1,5 +1,5 @@
-+++
-title = '{{ replace .File.ContentBaseName "-" " " | title }}'
-date = {{ .Date }}
-draft = true
-+++
+---
+title: "{{ replace .File.ContentBaseName "-" " " | title }}"
+date: {{ .Date }}
+draft: true
+---

Now if you run hugo new page.md, hugo will create a new file in content/page.md with the default archetype that we just modified. Open the file in your editor and add some text to it. Make sure to remove the draft option in the metadata or you wont see the page!

--- /dev/null	2024-10-31 12:52:51.231645990 -0700
+++ content/page.md	2024-10-31 13:22:42.938320466 -0700
@@ -0,0 +1,10 @@
+---
+title: "Page"
+date: 2024-10-31T13:22:12-07:00
+---
+
+
+# Webpage
+
+This is a sample webpage.

When you open the browser, you’ll just see a link to the page that you just made. When you click the link, you will see what you wrote.

Now that we have a page, lets create a menu. Start by adding this into layouts/_default/baseof.html.

--- baseof.html~1~      2024-11-16 13:33:57.609448357 -0700
+++ baseof.html 2024-11-16 13:33:12.326114708 -0700
@@ -3,6 +3,13 @@
 <head>
 <title>{{ .Site.Title }}</title>
 </head>
+<header><nav>
+{{ range site.Menus.main }}
+  <a href="{{ .URL }}">
+    {{ .Name }}
+  </a>
+{{ end }}
+</nav></header>
 <body>
 {{ block "main" . }}
 <p>No content to be found!</p>

With this, you can either include a page in a menu by adding the following to any page front matter:

menus: main

Or by adding the menu to the site configuration:

[menus]
[[menus.main]]
name = 'Home'
pageRef = '/'
weight = 10
[[menus.main]]
name = 'Articles'
pageRef = '/articles'
weight = 20

Both contribute to the menu, but the method of editing the site configuration gives you a bit more control over the menu as opposed to just including it in the page.

Editing the Index page

Now we have a page with a page and a menu, lets add to that ‘Articles’ section we added in the menu.

content/articles/article1.md:

--- /dev/null   2024-11-16 12:50:29.706096829 -0700
+++ content/articles/article1.md        2024-11-16 13:55:24.456124003 -0700
@@ -0,0 +1,11 @@
+---
+title: "Article1"
+date: 2024-11-16T13:53:04-07:00
+---
+
+Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
+incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis
+nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
+Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
+fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
+culpa qui officia deserunt mollit anim id est laborum.

content/articles/article2.md:

--- /dev/null   2024-11-16 12:50:29.706096829 -0700
+++ content/articles/article2.md        2024-11-16 13:55:25.419457343 -0700
@@ -0,0 +1,39 @@
+---
+title: "Article2"
+date: 2024-11-16T13:53:11-07:00
+---
+
+Lorem ipsum odor amet, consectetuer adipiscing elit. Fringilla aliquet nisl
+facilisi velit mus accumsan sodales. Quam orci convallis primis fermentum
+feugiat nam amet. Tortor et dapibus maximus potenti dictum erat. Tortor velit
+primis turpis laoreet quis habitant luctus hendrerit. Eget leo inceptos
+pharetra augue porta eu egestas feugiat. Ac ut vel magna rutrum bibendum urna
+amet sociosqu.
+
+Mollis mi ex fermentum quisque semper elementum? Eros sem in metus condimentum
+lacus posuere consequat nunc montes? Dictum facilisi tortor penatibus auctor
+molestie fringilla vestibulum. Morbi suscipit donec imperdiet ac primis mus
+rhoncus neque? Risus cursus congue facilisi adipiscing curae consectetur?
+Aliquam nec habitant lobortis nascetur tempus consequat orci morbi. Tincidunt
+luctus nec sollicitudin erat aliquam nisi aliquet cubilia.
+
+Scelerisque ullamcorper fermentum faucibus porta, ligula eget. Lacinia
+elementum a scelerisque sagittis, risus nunc neque odio. Eros dolor tellus quis
+vitae pretium bibendum? Molestie semper gravida nunc habitant porta consequat
+fusce vehicula luctus. Senectus nostra placerat fringilla; fames risus
+tristique sagittis placerat. Consequat aliquam non curae in habitant egestas
+duis eros.

Now if you go to /articles on the site, you’ll get a listing of the documents within that section. However, we will probably want to add some text to this landing page, so lets define an index document. In the root of any section, if you have a file named _index.md, its content will be added to the list template, so long as you added the {{ .Content }} directive to the template.

content/articles/_index.md:

--- /dev/null   2024-11-16 12:50:29.706096829 -0700
+++ content/articles/_index.md  2024-11-16 14:03:57.712794250 -0700
@@ -0,0 +1,6 @@
+---
+title: "Articles"
+date: 2024-11-16T14:03:39-07:00
+---
+
+This is a collection of my articles! Enjoy reading them!

Now we can see the text, but the list of articles is after the list. Ideally we want the text to follow the list. As well, I want to make the articles have the date they were created put before the link. Lets do that by making a copy of the default list template that we made, and make it specifically for the articles section.

sh$ mkdir layouts/articles
sh$ cp layouts/_default/list.html layouts/articles/list.html

Then lets change the template:

--- layouts/_default/list.html  2024-10-31 13:28:33.218321992 -0700
+++ layouts/articles/list.html  2024-11-16 14:09:25.862796540 -0700
@@ -1,9 +1,9 @@
 {{ define "main" }}
 <h1>List of {{ .Title }}</h1>
+{{ .Content }}
 <ul>
 {{ range .Pages }}
-    <li><a href='{{ .RelPermalink }}'>{{ .Title }}</a></li>
+<li>{{ .Date.Format "2006-01-02" }} <a href='{{ .RelPermalink }}'>{{ .Title }}</a></li>
 {{ end }}
 </ul>
-{{ .Content }}
 {{ end }}

With this, the body text comes before the list of articles, and we additionally have a timestamps before the link to the article.

The final touch: RSS

RSS is a spec that defines a xml structure that represents a feed of your content. People often subscribe to feeds with feed readers. Sort of how you subscribe to a magazine. (does anybody read magazines anymore?) Hugo has some defaults in place, but it has some things to be desired, so we will edit the default template to display the entire content of an article, rather than just a summary.

layouts/_deafult/rss.xml:

--- /dev/null	2024-11-16 22:01:45.086566412 -0700
+++ layouts/_default/rss.xml	2024-11-16 23:12:52.529918361 -0700
@@ -0,0 +1,59 @@
+{{- $authorEmail := "" }}
+{{- with site.Params.author }}
+  {{- if reflect.IsMap . }}
+    {{- with .email }}
+      {{- $authorEmail = . }}
+    {{- end }}
+  {{- end }}
+{{- end }}
+
+{{- $authorName := "" }}
+{{- with site.Params.author }}
+  {{- if reflect.IsMap . }}
+    {{- with .name }}
+      {{- $authorName = . }}
+    {{- end }}
+  {{- else }}
+    {{- $authorName  = . }}
+  {{- end }}
+{{- end }}
+
+{{- $pctx := . }}
+{{- if .IsHome }}{{ $pctx = .Site }}{{ end }}
+{{- $pages := slice }}
+{{- if or $.IsHome $.IsSection }}
+{{- $pages = $pctx.RegularPages }}
+{{- else }}
+{{- $pages = $pctx.Pages }}
+{{- end }}
+{{- $limit := .Site.Config.Services.RSS.Limit }}
+{{- if ge $limit 1 }}
+{{- $pages = $pages | first $limit }}
+{{- end }}
+{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
+  <channel>
+    <title>{{ if eq .Title .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} on {{ end }}{{ .Site.Title }}{{ end }}</title>
+    <link>{{ .Permalink }}</link>
+    <description>Recent content {{ if ne .Title .Site.Title }}{{ with .Title }}in {{ . }} {{ end }}{{ end }}on {{ .Site.Title }}</description>
+    <generator>Hugo</generator>
+    <language>{{ site.Language.LanguageCode }}</language>{{ with $authorEmail }}
+    <managingEditor>{{.}}{{ with $authorName }} ({{ . }}){{ end }}</managingEditor>{{ end }}{{ with $authorEmail }}
+    <webMaster>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</webMaster>{{ end }}{{ with .Site.Copyright }}
+    <copyright>{{ . }}</copyright>{{ end }}{{ if not .Date.IsZero }}
+    <lastBuildDate>{{ (index $pages.ByLastmod.Reverse 0).Lastmod.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
+    {{- with .OutputFormats.Get "RSS" }}
+    {{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
+    {{- end }}
+    {{- range $pages }}
+    <item>
+      <title>{{ .Title }}</title>
+      <link>{{ .Permalink }}</link>
+      <pubDate>{{ .PublishDate.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
+      {{- with $authorEmail }}<author>{{ . }}{{ with $authorName }} ({{ . }}){{ end }}</author>{{ end }}
+      <guid>{{ .Permalink }}</guid>
+      <description>{{ .Content | transform.XMLEscape | safeHTML }}</description>
+    </item>
+    {{- end }}
+  </channel>
+</rss>

With this, you can access any feed via the index.xml page.

Now what?

This marks the end of my short guide on getting up and running with hugo, its short, but it covers 90% of the stuff that isnt directly documented on how to use. This is 90% of the information that you really need to write cool websites with hugo. This guide was purposely short because there is a lot I could talk about, but there are generally better places to find that information.

I would highly recommend the following sites for extra related documentation/resources: