Hugo
This page is about the static website generator Hugo.
References
- Website: https://gohugo.io/
- YouTube tutorial series by Mike Dane: https://youtu.be/qtIqKaDlqXo
- Hugo themes gallery: https://themes.gohugo.io/
Glossary
- Front matter
- Stuff at the beginning of a content file that contains metadata about the actual content that comes later in the file.
- Section
- A site's content is structured into a tree of sections. The subfolders below
content
automatically are treated as the root sections. To define a subsection place an_index.md
file into a subfolder. - Theme
- How a site generated by Hugo looks.
Installation on Mac
Just use the Homebrew package manager and simply install Hugo with this command in the Terminal:
brew install hugo
Using Hugo
Once Hugo is installed on your system you can use this command in the Terminal to do something with Hugo:
hugo [...]
Creating a new site
Create a base folder for Hugo in which you can then manage several site:
mkdir /path/to/hugo-base-folder cd /path/to/hugo-base-folder
Create the new site with this command. It creates the site's base folder and populates it with a skeleton of files and folders:
hugo new site foo-site
Creating new content
Basics
Content files go into the content
subfolder of the site folder. Instead of manually creating the file you type this command:
hugo new foo/bar/baz.md
Notes:
- This creates a new content file in the specified subfolder and populates it with some default front matter.
- Hugo automatically figures out the format of the file based on the extension. In the example the
.md
extension indicates that this is a Markdown file. - The content is set to be a draft. To see drafts Hugo's test web server must be started with option
-D
.
Example content file
This is an example file that shows
- Some front matter in YAML format between the two
---
lines, - A bit of teaser text,
- A "read more" delimiter to delimit the teaser from the main text,
- And finally the main text.
--- title: "Little Go 1.6.0 released" date: "2021-03-11T00:27:00+01:00" author: "patrick" articlestyles: ["Release note"] softwareprojects: ["Little Go"] --- Libero adipisci ipsam consequuntur nesciunt voluptas <!--more--> Ut sint expedita quis dignissimos atque tempore asperiores.
Front matter
- Front matter contains meta data about the content.
- Front matter is usually stored as key/value pairs.
- Front matter can be written in YAML, TOML or JSON.
- YAML is delimited by surrounding lines with 3 dashes (
---
) and uses a colon (:) between key and value. - TOML is delimited by surrounding lines with 3 plus characters (
+++
) and uses an equals character (=) between key and value.
- YAML is delimited by surrounding lines with 3 dashes (
- Themes can define theme-specific keys.
- You can also specify custom keys - it's not yet clear how to use the data.
Markdown
Hugo supports several kinds of Markdown flavours. The default is Goldmark. Refer to the Markup Configuration page of Hugo's documentation to see what is supported.
Some of the things that are possible but that I haven't explored yet:
- Embed HTML in the Markdown content.
- Add CSS classes to titles and blocks of content.
- Generate a table of content
Shortcodes
A content file can contain shortcodes that are like placeholders that will be replaced with real HTML code generated by Hugo. Hugo comes with a lot of pre-defined shortcodes. Refer to the Shortcodes page of Hugo's documentation for details. You can define your own custom shortcodes - see this Hugo doc page for details.
Note: Shortcodes cannot be used in template files - use a partial template for that.
Examples:
{{% shortcode-name param1 %}} {{% shortcode-name param1 %}}<p>Hello world!</p>{{% /shortcode-name param1 %}} {{< shortcode-name param1 >}}<p>Hello world!</p>{{< /shortcode-name param1 >}}
Notes:
- Parameters may be named, positional, or both - it depends on how the shortcode is defined.
- Shortcodes may require closing shortcodes, similar to how HTML elements must have a begin/end tag.
- Instead of the "%" character you can use angle brackets ("<>") as delimiters. This indicates that the shortcode’s inner content does not need further rendering.
Taxonomies
- Taxonomy information is defined in the front matter
- Taxonomies might work differently depending on the theme that is being used.
- Hugo comes with two pre-defined taxonomies: Tags (= keywords) and Categories.
- Tags are defined like this:
tags: ["tag1", "tag2", "tag3"]
- Categories are defined like this:
categories: ["cat1", "cat2", "cat3"]
- Tags are defined like this:
- Custom taxonomies
- Need to be defined in the
config.toml
file like this:
- Need to be defined in the
[taxonomies] tag = "tags" category = "categories" foo = "foos" "the singular" = "the plural"
- The default taxonomies need to be defined as well if you want them.
- The key is the singular, the value is the plural.
- You can have a singular with a space in it by enclosing it within double quotes.
- Hugo automatically manages uppercasing.
- Now just add the taxonomy name and the term names to the front matter like it was shown above for "tags" and "categories".
Single page vs. list page
Hugo distinguishes content into two "categories":
- Single pages: These are pages that display actual content. Example: A page that displays the content of a content file.
- List pages: These are pages that list other other content. Example: A page that lists the content of a folder.
Hugo automatically generates a list page for the site's top-level folder and for folders immediately below that. It does NOT automatically create list pages for folders that are deeper down in the hierarchy. To achieve this you have to create a special file named _index.md
within such a folder. The file does not need to have any content besides the usual front matter, but if you want to you can add text.
hugo new foo/bar/_index.md
If you create an _index.md
file in a folder for which Hugo would normally auto-create a list page, the _index.md
file is now used instead.
Archetypes
Archetypes are templates that Hugo uses when you create new content files. A new Hugo site is automatically populated with the
archetypes/default.md
archetype file which defines the front matter that is used to initialize a new content file with.
You can have several archetype files. Which one is used depends on the folder in which you create a new content file. If there is an archetype file named the same as the containing folder, then that archetype file is used. Otherwise the default.md
file is used.
Archetype files are not static, they define placeholders that are filled when the archetype file is used. TODO: Provide more information.
Templates
Basics
Hugo uses Go templates. Go Templates are HTML files (extension .html
) with the addition of variables, functions and processing logic (conditionals, loops). Go Template variables and functions are accessible within
{{ }}
When Hugo renders the site the templates are processed and as a result of the processing logic the variables and functions within a template are replaced with actual content.
Refer to the Templates Introduction page of Hugo's documentation for details.
Syntax
Comments:
Template:/* A comment */
Local variable:
{{ $foo := "bar" }} {{ $bar := $foo }}
Theme templates
Hugo templates are used for generating the actual output of Hugo. When you're using a theme that theme consists - among other things - of a number of template files located in the folder
themes/foo-theme/layouts
Custom templates
You can also define your own templates under
layouts
These override the templates from the theme. The templates to be used by default must be placed in a folder named _default
:
layouts/_default
Single page and list page templates
The template for single pages and for list pages must be named specifically
single.html list.html
Site main page template
The site's main page (the one at path location /
) is styled with a special template file
layouts/index.html
Section templates
A site's content is structured into a tree of sections. The subfolders below content
automatically are treated as the root sections. To define a subsection place an _index.md
file into a subfolder.
A section template is a template that is used only for a particular section of the site.
- Create a subfolder below the
layouts
folder with the exact same path as the section in thecontent
folder. - Place
single.html
orlist.html
into the newlayouts
subfolder. These are now applied to the content below the corresponding content folder.
Base templates
Name and location of a base template:
layouts/_default/baseof.html
Example content of the base template that references a block named "main":
[...] <body> {{ block "main" . }} {{ end }} </body> [...]
Now the single and list templates can define the "main" block which is then inserted into the base template:
{{ define "main" }} Everything in here is placed at the block reference location in the base template {{ end }}
Notes:
- The block name "main" is nothing special, you can choose something else.
- You can reference and define more blocks.
Partial templates
Place partial templates under here:
layouts/partials
Partial templates can then be referred to and expaned from other templates.
{{ partial "foo" . }}
Notes:
- The first parameter defines the name of the partial template file. In the example the partial template
foo.html
is expanded. - The second parameter defines the context to be passed into the template. The "." parameter means the content file that is currently being processed by the template that includes the partial.
To pass a dictionary with custom data
{{ partial "foo" (dict "key1" "value1" "key2" "value2") }}
To access the data:
{{ .key1 }}
Shortcode templates
Place shortcode templates under here:
layouts/shortcodes
Refer to the shortcode template in the content file like this.
{{< foo key="value" >}}
Notes:
- This expands the shortcode template
layouts/shortcodes/foo.html
. - It passes the key/value pairs to the template. This is optional.
To access the data:
{{ .Get "key" }}
Shortcode templates can also be used in a spanning manner. In the content file:
{{< foo >}} bar {{< /foo >}}
In the template you access the content spanned by the template:
{{ .Inner }}
When you use "<" and ">" the content spanned by the template is not rendered as markdown. Use "%" instead to make it render as usual.
Themes
- Go to the Hugo themes gallery website (see References section)
- Select a theme that you like
- Click its Download button. This will not download anything, it will lead you to the GitHub repository where the theme is developed.
- Read through repository's README file. It will tell you how to actually install the theme in your Hugo site folder.
- Basically you'll clone the repository and tweak some settings in the
config.toml
file. - A theme is made up - among other things - of Hugo templates. The template files are stored in the theme's
layouts
folder.
Variables
Variables can only be used in the "layouts" folder within templates.
Syntax 1 refers to front matter in the content file:
{{ .Foo }}
Some variables:
- .Title. Expands to the value of the front matter key "title".
- .Date. Expands to the value of the front matter key "date".
- .URL: Expands to the URL path only.
- .Params.Foo: Expands to the value of the front matter custom key "foo".
Syntax 2 refers to a variable value defined earlier in the template.
{{ $Foo }}
And the definition looks like this:
{{ $Foo := "bar" }}
There are many other kinds of variables which are explained in the Hugo docs. Please refer to that for more.
Functions, conditionals
TODO
Data folder
Serves as a kind of a mini database. Can contain .yaml, .toml or .json files.
Access the content of a data file from a template like this:
<!-- Iterates over the content of a file foo.json --> {{ .range .Site.data.foo }} <!-- Expands to the value of key "bar" --> {{ .bar }} {{ end }}
Testing
Start the web server built into Hugo:
hugo server
Then use this URL in the browser to access the site:
http://localhost:1313
(the actual URL is displayed at the end of the output of the hugo server
command)
Note: To also see draft content, use the -D
option.
Recipes
Footnotes
At the place of origin:
[¹]({{< ref "#footnote-1" >}} "Footnote 1") [²]({{< ref "#footnote-2" >}} "Footnote 2")
Enumerate the footnotes at the end of the page:
--- # {#footnote-1} ¹ Footnote text. # {#footnote-2} ² Another footnote text.
Notes:
- Use Unicode superscript digit characters to compose the footnote number. Example: U+00B9 (character name = SUPERSCRIPT DIGIT ONE).
- Use the usual Markdown syntax to create a link at the site of origin. In the URL part use the Hugo shortcode template "ref" to generate the URL with a specific page-local anchor (e.g.
#footnote-1
). - At the end of the page use "---" (which is, I believe, Markdown) to create a ruler line that separates the footnotes from the main content of the page.
- Enumerate the footnotes with an empty heading and a Hugo construct that specifies the anchor to generate. This works because Hugo automatically creates anchors for all headings. Without the Hugo construct shown in the example above, Hugo generates an anchor based on the heading text. Since we don't really want a heading to be shown we use an empty heading text and specify the anchor name with the Hugo construct.
Building and publishing the site
General procedure
Type
hugo
to build. This creates a folder
public
and populates it with the generated content. It's probably a good idea to add the public
folder to .gitignore
.
To rebuild the site, delete the public folder first as it won't get cleaned out by Hugo itself.
For herzbube.ch
Locally:
zip -r public.zip public scp public.zip root@pelargir.herzbube.ch:/var/www/hugo rm public.zip
On pelargir
:
cd /var/www/hugo rm -rf new-site.herzbube.ch unzip public.zip -d new-site.herzbube.ch rm public.zip mv new-site.herzbube.ch/public/* new-site.herzbube.ch rmdir new-site.herzbube.ch/public
Test in browser:
https://new-site.herzbube.ch/
On pelargir
when finished with testing:
cd /var/www/hugo rm -rf old-site.herzbube.ch mv herzbube.ch old-site.herzbube.ch mv new-site.herzbube.ch herzbube.ch mkdir new-site.herzbube.ch
(the last statement is necessary so that Apache does not generate a warning about a missing document root when it is periodically restarted)
For kino.herzbube.ch
On pelargir
:
cd /var/www/hugo rm -rf kino.herzbube.ch
Locally:
scp -r public root@pelargir.herzbube.ch:/var/www/hugo/kino.herzbube.ch
For grunzwanzling.ch
On pelargir
:
cd /var/www/hugo rm -rf grunzwanzling.ch
Locally:
scp -r public root@pelargir.herzbube.ch:/var/www/hugo/grunzwanzling.ch
Migrating from Drupal
Data to migrate
- Nodes. Node types are:
- Story
- Title. Simple text.
- Body. HTML. Conceptually this can be broken into a teaser and into the body that follows after the teaser. The HTML comment
<!--break>
is sometimes - but not always - used to mark the teaser break. - 0-n file attachments.
- Page. 10 pages in total: About, Software projects, CMS Evaluation, Evaluation of free UML Tools, one page per software project.
- Same structure as Story.
- Book review
- Title. Simple text.
- Several headings. HTML. Typically one of the heading elements, sometimes with the "style" attribute. These can also be ignored by the migration because both reviews (yes, there are only two) contain the same static headings.
- 1-n Authors. Text.
- Rating. Text in the format n/max. Max is always 5.
- Recommendation, Plot, Opinion, Bibliography. HTML.
- 0-n Links. URL + link text.
- Story
- Meta data for nodes
- Publishing date + time. This may be different from the date of the last revision.
- Author.
- URL alias. Specifies the path of the rendered page.
- 0-n Topics.
- 0-1 Article Style.
- 0-1 Project. (not for book reviews)
- Comments
- Body. HTML.
- Publishing date + time.
- Author.
- Taxonomy. Vocabularies:
- Topics. Tree.
- Article Styles. Flat list.
- Software Projects. Flat list.
- Views
- List of book reviews
- Links. The static link menu displayed on every page. Contains only two links.
- Footer. The static footer displayed on every page.
- Files
- Some of the files are explicitly attached to a node. Example: https://www.herzbube.ch/blog/2012/09/cd-r-will-self-destruct-15-years
- Some of the files exist in the site's
files
folder and are then referenced from the HTML of a node's body. Example: https://www.herzbube.ch/article/software-projects
- Redirects
- Can be found under Administer > Configuration > URL Redirects.
- There are currently only 7, which means that these are migrated manually.
Data to drop:
- A node's view count
- Old node revisions
Functionality/data that is no longer needed/wanted:
- Authentication
- CAPTCHA
- Comments
- Search
- Page view count
- Hierarchical structure of the taxonomy vocabulary "Topics"
Manually migrate
- Writing the automated migration script is a brainfuck, so anything that is not bulk will be migrated manually.
- Book reviews. There are only 2 of these
- List of book reviews -> hopefully will be auto-generated by Hugo
- Links
- Footer
- Files
- Redirects. There are only 7 of these.
Automated migration
- Node types "story" and "page": Title, body, publishing date, author, URL path, taxonomy terms
- Comments
- Taxonomy vocabularies/terms
Automation
I wrote a PHP script drupal2hugo.php
for exporting bulk data from Drupal. The script can be found in my tools Git repo here:
https://git.herzbube.ch/gitweb.cgi/tools.git/blob/HEAD:/drupal2hugo/drupal2hugo.php
Hugo issues to solve for herzbube.ch
Taxonomy terms for custom taxonomies not shown
When a page is displayed that is tagged with one or more terms from the custom taxonomies "Article Styles" and "Software Projects", these terms are not shown at the end of the page.
- Positive example: The page Implementing Binary Search shows the taxonomy term "Algorithms" at the bottom.
- Negative example: The page Little Go 1.7.0 released should show the terms "Release notes" and "Little Go" at the bottom, but neither of the two terms is shown.
This could be a problem with the Mainroad theme and not with Hugo in general.
List pages with wrong titles
The main menu is configured with menu entries. When the user clicks one of the entries, the resulting list page has a tab title and page title that does not match the menu entry name.
Example: The main menu has an entry "Blog entries". When the user clicks this the resulting list page says "Blogs" at the top, and also in the title of the browser tab.
Notes from initial research:
- Likely has to do with section configuration
- May not be a theme problem
Categories or tags not shown on list pages
The Mainroad theme does not display categories or tags on list pages.
Example: https://www.herzbube.ch/blog/
List page for a tag does not show the current filter
When the list page for a tag is shown, the Mainroad theme does not provide an indication which filter is in effect.
Since tags are also not shown on list pages (see previous issue) the user has to look at the URL to get an indication what she is looking at.
On list pages the Mainroad theme only has buttons to jump to the previous/next page, but not to the first/last page.
Links and formatting not rendered in summaries
The Mainroad theme does not render links or other formatting in summaries generated on list pages.
Examples:
- Post Added Bugzilla when shown on this list page does not display the link in the post text.
- However, the post How do *you* spend your free time? when shown on this list page does display the links in the post text!
- Post New PGP key when shown on this list page does not display the paragraph break.
- Post Aqua Crystal Ball when shown on this list page does not display the image.
- Post CAcert Assurer Challenge - Passed! when shown on this list page does not render the HTML table.
Incorrect rendering of "Read more" link on list pages
The "Read more" link for the post Bye bye Subversion is rendered with red text color instead of white text color on list pages. Example: this list page.