Pragmatic book
Commands
# create new theme
hugo new theme theme-name
# add new theme to config.toml
theme = "theme-name"
# create new page in /content with default archetype (do not need to create dir first)
hugo new page-name.md
# create new page in /<archetyp-name> with /archetype file
hugo new <archetype-name>/page-name.md
# delete /public and regen /public
hugo --cleanDestinationDir
# minify /public contents
hugo --cleanDestinationDir --minify
Adding a content section
- Create an archetype.
- Create a layouts directory with a
list.html
andsingle.html
page:themes/basic/layouts/presentations/ ├── list.html └── single.html
- Create an index page and any content pages:
hugo new <archetype-name>/_index.md hugo new <archetype-name>/content-one.md
Context
When you work with a Hugo layout, there’s a scope or context that contains the data that you want to access. The current context is is represented by a dot (.
). This context is set to the Page context. For convenience, Hugo makes all site data available in the Page context. The Page context looks like this:
Context (.)
├── Site
│ └── Title
├── Title
├── Content
- You can access the site title in
config.toml
with{{ .Site.Title }}
. {{ .Content }}
comes from the.md
file
Variables
.Date
Get the date and format it:
Posted {{ .Date.Format "January 2, 2006"}}
$.Variable
Prefixing a variable with a $
tells Hugo that you want values in the global scope, not current local scope:
{{- $title := printf "%s - %s" $.Page.Title $.Site.Title -}}
{{- if $.Page.IsHome -}}
{{ $title = $.Site.Title }}
{{- end -}}
.Pages
List layouts (list.html
) let you access a collection that contains all pages related to the section you’re working with:
// do something with each Page in Pages
{{ range .Pages }}
// set the context of the range function to each Page in Pages
{{ end }}
.Pages
is a context-specific variable that represents a collection of pages within the current scope. The scope can vary depending on where you are in the template hierarchy. It can represent pages within a section, taxonomy, or any other subset of the site’s content.
Key Points of .Pages
:
- Context-Sensitive: The contents of
.Pages
depend on the current context. For example, within a section template,.Pages
will contain the pages of that section. - Flexibility:
.Pages
can be used in different parts of the site to represent different subsets of content.
.Site.RegularPages
Lets you access all pages in a site. You can use SQL-esque syntax to filter the pages to the ones that you need:
{{ range (where .Site.RegularPages "Type" "in" "projects").ByDate.Reverse }}
<li><a href="{{ .RelPermalink }}">{{ .Title }}</a></li>
{{ end }}
.Site.RegularPages
is a site-wide variable that represents all regular content pages of the site. Regular content pages are those that are not list pages, taxonomy pages, or other special pages. This variable is used when you need to work with the entire set of content pages across the site.
Key Points of .Site.RegularPages
:
- Site-Wide Scope:
.Site.RegularPages
includes all content pages from the entire site, regardless of their location or type. - Excludes Non-Content Pages: It specifically excludes list pages, taxonomy pages, and other special pages.
.Params
Lets you access custom front matter parameters:
<img src="{{ .Params.image }}" alt="{{ .Params.alt_text }}">
<h3>Tech used</h3>
<ul>
{{ range .Params.tech_used }}
<li>{{ . }}</li>
{{ end }}
</ul>
You don’t have to use .Params
to access predefined Hugo fields. (you can use )
.Page.IsHome
Checks whether the page you are on is the home page.
Functions
isset
Checks whether a variable has a value, but only when the variable doesn’t have a default method.
The isset function is used to check whether a variable, map, or slice has been set or not. It’s useful for conditional statements where you need to determine if a variable exists before proceeding with further logic.
with
Checks whether a variable has a value, including a default value. Also rebinds the context. If the first value is not nil, then the value becomes the context:
// check if there is a val set for default page var .Description
{{- with .Page.Description -}}
// if there is, then the new context is .Page.Description
{{ . }}
{{- else -}}
{{ .Site.Params.description }}
{{- end -}}
The with function is used to simplify nested templates and to provide a scoped context. When with is used, it evaluates its argument, and if it is not nil, it executes the enclosed block with the argument as the context. If the argument is nil, it skips the block.
getJSON
Fetches and parses JSON data from a specified URL or file. This function allows you to integrate external JSON data into your Hugo site, enabling dynamic content generation based on external sources:
{{ $data := getJSON "URL or file path" }}
.Site.GetPage
Pulls data from any page in the site:
{{ with .Site.GetPage "/opensource.md" }}
<h3><a href="{{ .RelPermalink }}">{{ .Title }}</a></h3>
<p>{{ .Summary }}</p>
{{ end }}
safeHTML
Use only on HTML snippets that you create.
Ensures an HTML snippet is not escaped. It allows you to mark a string as safe HTML content, meaning that Hugo will not escape the HTML tags in the string when rendering it. This is particularly useful when you want to include raw HTML in your content without Hugo escaping it, which would prevent the HTML from being rendered correctly in the browser:
{{ printf $link .Rel .MediaType.Type .Permalink $title | safeHTML }}
.Summary
Hugo can pull a page summary with this variable. You can control where the summary ends with <!-- more -->
:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
<!-- more -->
urlize
Encodes text content as a url
<a href="/tags/{{ . | urlize}}" class="tag">{{ . }}</a>
Math
Perform math operations with nested inline equations:
Reading time: {{ math.Round (div (countwords .Content) 200.0) }} minutes
Blocks and partials
themes/basic/layouts/
├── 404.html
├── _default # layouts
│ ├── baseof.html # base of every layout that you create
│ ├── list.html
│ └── single.html
├── index.html
└── partials # all partials (components) to include in _default/<layout>.html
├── footer.html
├── header.html
└── head.html
Blocks
// single.html
{{ define "main" }}
<h2>{{ .Title }}</h2>
{{ .Content }}
{{ end }}
// baseof.html
// pulls in the "main" block for each layout
...
<body>
...
<div id="content">
{{- block "main" . }}{{- end }}
</div>
...
</body>
...
Partials
Place common HTML in partials so you can reuse them as components:
// {{- -}} suppresses whitespace
// partial function takes a filename and context for data
// . is the current/default context, which is the Page context
-->
{{- partial "header.html" . -}}
Conditional logic
// check if there is a val set for default page var .Description
{{- with .Page.Description -}}
{{ . }}
{{- else -}}
{{ .Site.Params.description }}
{{- end -}}
Data
Local data
Store local data in the data/
directory, and access it with .Site.Data.file-name.object-name
:
// data file
{
"accounts": [
{
"name": "Twitter",
"url": "https://twitter.com/bphogan"
},
...
]
}
// in a layout
<ul>
{{ range .Site.Data.socialmedia.accounts }}
<li><a href="{{ .url }}">{{ .name }}</a></li>
{{ end }}
</ul>
Remote data
You can fetch remote data and iterate over it in your site. The following snippet gets the public GH repos for the url and user stored in config.toml
params, and iterates over them to create a list:
// url == gh_url/gh_user/repos
{{ $url := printf "%s/%s/repos" .Site.Params.gh_url .Site.Params.gh_user }}
// getJSON fetches and parses JSON data
{{ $repos := getJSON $url }}
<section class="oss">
{{ range $repos }}
<article>
<h3><a href="{{ .html_url}}">{{ .name }}</a></h3>
<p>{{ .description }}</p>
</article>
{{ end }}
Layouts
Add custom layouts in the layouts/_default/
directorys for the site, not the theme. Then, you can add the custom layout to the frontmatter with layout
:
---
title: "Contact"
date: 2024-06-25T23:58:40-04:00
draft: false
layout: contact
---
Assets and Pipes
To use pipes, you have to create an /assets
directory for your JS, SCSS, etc. files.
In the following snippet:
resources.Get
function uses/assets
dir as its base$css.RelPermalink
writes a CSS file to/public/css
and adds the relative URL to the HTML docminify
minifies the filefingerprint
creates a unique filename for the asset file so users don’t cache the wrong filetoCSS
transpiles Sass to CSS
{{ $css := resources.Get "css/style.scss" | toCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $css.RelPermalink }}">
Sass
Use toCSS
to transpile your Sass to CSS:
{{ $css := resources.Get "css/style.scss" | toCSS | minify | fingerprint }}
<link rel="stylesheet" href="{{ $css.RelPermalink }}">
Partials
A partial is a file that you can include in the main Sass file. Partial filenames begin with an underscore:
/themes/.../_navbar.scss
Then import the file into your main Sass page where you want it to be loaded into the browser:
nav,
footer {
background-color: #333;
color: #fff;
text-align: center;
}
@import "navbar";
Images
Image processing can increase build times. To make it shorter, Hugo caches any converted images and saves them in the /resources
folder. You can clean up these images with this command:
hugo -gc
For templates, you can use the image processing functions. You can’t use these in content pages, only layouts. To manipulate images, use shortcodes.
You can put images in the site /static
directory, or the /theme/<theme>/static
directory. The static directories are merged together.
The contents of the /static
folder are copied to the root of the site, and the relURL
function generates the image path relative to the page:
<footer>
<small>Copyright {{now.Format "2006"}} {{ .Site.Params.author -}}. {{- partial "social.html" . -}}</small>
<p>Powered by<br>
<a href="https://gohugo.io">
<img src="{{ "hugo-logo-wide.svg" | relURL }}" alt="Hugo image" width="128" height="38">
</a></p>
</footer>
Shortcodes
Use shortcodes when you would use HTML in a Markdown document. Shortcodes are functions powered by Go’s templating mechanism that you can use in md. You call them and pass them options, and they generate output. Shortcodes let you do things in your md content that you’d normally only be able to do in layouts.
Put shortcodes in /layouts/shortcodes
, either in your theme or site with an .html
extension:
// .Get 0 gives you access to the first argument you pass to the shortcode.
// .Resize resizes the image. Pass only one value so Hugo maintains the aspect ratio.
{{ $image := $.Page.Resources.GetMatch (.Get 0)}}
{{ $smallImage := $image.Resize "1024x" }}
<figure class="post-figure">
<a href="{{ $image.RelPermalink }}">
{{ with $smallImage }} // changes context so you don't have to prefix .Width and .Heigth with $smallImage
<img src="{{ .RelPermalink }}"
width="{{ .Width }}"
heigth="{{ .Height }}"
alt="{{ $.Get 1 }}" /> // fetches 2nd arg to the shortcode. Use $ because the context is changed, and you need
{{ end }} // to reach outside current scope
</a>
<figcaption>{{ .Get 1 }}</figcaption>
</figure>
Javascript
You can combine all scripts into a singl eminified file and fingerprint the file to protect against incorrect cached files.
Put your JS files in themes/<theme-name>/assets/js
, and add them to your themes/<theme-name>/layouts/_default/filename.html
file like this:
// no webpack
{{ $lunr := resources.Get "js/lunr.js" }} // <script src="//unpkg.com/lunr@2.3.6/lunr.js"></script>
{{ $axios := resources.Get "js/axios.js" }} // <script src="//unpkg.com/axios@0.19.0/dist/axios.js"></script>
{{ $search := resources.Get "js/search.js" }} // <script src="{{ "js/search.js" | relURL }}"></script>
{{ $libs := slice $lunr $axios $search }} // create a slice
{{ $js := $libs | resources.Concat "js/app.js" | minify | fingerprint }} // combine into single file, minify, then fingerprint
<script src="{{{ $js.RelPermalink }}</script>
Webpack and npm
Webpack manages and builds frontend applications. Its powered by Node.js, so you have access to npm for package management and automation.
To use Webpack with Hugo, you have to do the following:
- Install the dependencies
- Create a
package.json
file with scripts that run Webpack and hugo - Create a
webpack.config.js
file that tells Webpack where to find your source JS files and build the output. - Add a path to your Webpack JS output file in your HTML.
Install dependencies
# Install webpack and its CLI
npm install --save-dev webpack webpack-cli
# Install dependencies -- you don't have to use
# <script src="<dep[1,2,...]>" in HTML files
npm install --save <dep1> <dep2> ...
# Run multiple tasks at once or parallel
npm install --save-dev npm-run-all
Create package.json
Sample package.json
file:
{
"name": "portfolio",
"version": "1.0.0",
"description": "My portfolio",
"private": true,
"scripts": {
"build": "npm-run-all webpack hugo-build", // run webpack and hugo server sequentially
"hugo-build": "hugo --cleanDestinationDir", // build hugo
"hugo-server": "hugo server --disableFastRender", // run dev server, nothing is cached
"webpack": "webpack", // runs webpack
"webpack-watch": "webpack --watch", // webpack watches changes for hugo and generates new .js file (run in new terminal)
"dev": "npm-run-all webpack --parallel webpack-watch hugo-server"
},
"devDependencies": {
"npm-run-all": "^4.1.5",
"webpack": "^5.93.0",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"axios": "^1.7.4",
"lunr": "^2.3.9"
}
}
Create webpack.config.js
Add webpack.config.js
:
const path = require('path');
module.exports = {
entry: './themes/basic/assets/js/index.js', // looks for index.js
output: { // generates output in themes/basic/assets/js/app.js
filename: 'app.js',
path: path.resolve(__dirname, 'themes', 'basic', 'assets', 'js')
}
};
Integrate JS output file in HTML file
Integrate the file that Webpack generates into the layout:
{{ $js := resources.Get "js/app.js" | minify | fingerprint }}
<script src="{{ $js.RelPermalink }}"</script>