Hugo

In this chapter, we will briefly introduce\index{Hugo} Hugo (https://gohugo.io), the static site generator on which blogdown is based. This chapter is not meant to replace the official Hugo documentation, but provide a guide to those who are just getting started with Hugo. When in doubt, please consult the official Hugo documentation.

Static sites and Hugo {#static-sites}

A static site\index{Static Site} often consists of HTML files (with optional external dependencies like images and JavaScript libraries), and the web server sends exactly the same content to the web browser no matter who visits the web pages. There is no dynamic computing on the server when a page is requested. By comparison, a dynamic site relies on a server-side language to do certain computing and sends potentially different content depending on different conditions. A common language is PHP, and a typical example of a dynamic site is a web forum. For example, each user has a profile page, but typically this does not mean the server has stored a different HTML profile page for every single user. Instead, the server will fetch the user data from a database, and render the profile page dynamically.

For a static site, each URL you visit often has a corresponding HTML file stored on the server, so there is no need to compute anything before serving the file to visitors. This means static sites tend to be faster in response time than dynamic sites, and they are also much easier to deploy, since deployment simply means copying static files to a server. A dynamic site often relies on databases, and you will have to install more software packages to serve a dynamic site. For more advantages of static sites, please read the page "Benefits of Static Site Generators" on Hugo's website.

There are many existing static site generators, including Hugo, Jekyll, and Hexo, etc. Most of them can build general-purpose websites but are often used to build blogs.

We love Hugo for many reasons, but there are a few that stand out. Unlike other static site generators, the installation of Hugo is very simple because it provides a single executable without dependencies for most operating systems (see Section \@ref(installation)). It was also designed to render hundreds of pages of content faster than comparable static site generators and can reportedly render a single page in approximately 1 millisecond. Lastly, the community of Hugo users is very active both on the Hugo discussion forum and on GitHub issues.

Although we think Hugo is a fantastic static site generator, there is really one and only one major missing feature: the support for R Markdown. That is basically the whole point of the blogdown package.^[Another motivation was an easier way to create new pages or posts. Static site generators often provide commands to create new posts, but you often have to open and modify the new file created by hand after using these commands. I was very frustrated by this, because I was looking for a graphical user interface where I can just fill out the title, author, date, and other information about a page, then I can start writing the content right away. That is why I provided the RStudio addin "New Post" and the function blogdown::new_post(). In the past few years, I hated it every time I was about to create a new post either by hand or via the Jekyll command line. Finally, I felt addicted to blogging again after I finished the RStudio addin.] This missing feature means that you cannot easily generate results using R code on your web pages, since you can only use static Markdown documents. Besides, Hugo's default Markdown engine is "Blackfriday", which is less powerful than Pandoc.^[The Pandoc support has been added in a Hugo pull request: https://github.com/gohugoio/hugo/pull/4060. However, I think the support is quite limited, and I'd recommend that you use the R Markdown format instead, because with the official Pandoc support in Hugo, you cannot customize the Pandoc command-line options, rendering is not cached (it could be slow), and you will not be able to use any Markdown extensions from the bookdown package (such as numbering figure captions).]

Hugo uses a special file and folder structure to create your website (Figure \@ref(fig:folders)). The rest of this chapter will give more details on the following files and folders:

(ref:folders) Possible files and folders created when you create a new site using blogdown.

knitr::include_graphics('images/folder-structure.png')

Configuration

The first file that you may want to look at is the configuration\index{config.toml} or config file in your root directory, in which you can set global configurations of your site. It may contain options like the title and description of your site, as well as other global options like links to your social networks, the navigation menu, and the base URL for your website.

When generating your site, Hugo will search for a file called config.toml first. If it cannot find one, it will continue to search for config.yaml.^[Hugo also supports config.json, but blogdown does not support it, so we do not recommend that you use it.] Since most Hugo themes contain example sites that ship config.toml files, and the TOML\index{TOML} (Tom's Obvious, Minimal Language) format appears to be more popular in the Hugo community, we will mainly discuss config.toml here.

We recommend that you use the TOML syntax only for the config file (you can also use YAML if you prefer), and use YAML as the data format for the metadata of (R) Markdown pages and posts, because R Markdown and blogdown fully support only YAML\index{YAML}.^[TOML has its advantages, but I feel they are not significant in the context of Hugo websites. It is a pain to have to know yet another language, TOML, when YAML stands for "Yet Another Markup Language." I'm not sure if the XKCD comic applies in this case: https://xkcd.com/927/.] If you have a website that has already used TOML, you may use blogdown::hugo_convert(unsafe = TRUE) to convert TOML data to YAML, but please first make sure you have backed up the website because it will overwrite your Markdown files.

The Hugo documentation does not use TOML or YAML consistently in its examples, which can be confusing. Please pay close attention to the configuration format when copying examples to your own website.

TOML Syntax

If you are not familiar with the TOML Syntax, we will give a brief overview and you may read the full documentation to know the details.

TOML is made up of key-value pairs separated by equal signs:

key = value

When you want to edit a configuration in the TOML file, simply change the value. Values that are character strings should be in quotes, whereas Boolean values should be lowercase and bare.

For example, if you want to give your website the title "My Awesome Site," and use relative URLs instead of the default absolute URLs, you may have the following entries in your config.toml file.

title = "My Awesome Site"

relativeURLs = true

Most of your website's global variables are entered in the config.toml file in exactly this manner.

Further into your config file, you may notice some values in brackets like this:

[social]
    github  = "https://github.com/rstudio/blogdown"
    twitter = "https://twitter.com/rstudio"

This is a table in the TOML language and Hugo uses them to fill in information on other pages within your site. For instance, the above table will populate the .Site.Social variable in your site's templates (more information on this in Section \@ref(templates)).

Lastly, you may find some values in double brackets like this:

[[menu.main]]
    name = "Blog"
    url = "/blog/"

[[menu.main]]
    name = "Categories"
    url = "/categories/"

[[menu.main]]
    name = "About"
    url = "/about/"

In TOML, double brackets are used to indicate an array of tables. Hugo interprets this information as a menu. If the code above was found in a config.toml file, the resulting website would have links to Blog, Categories, and About pages in the site's main menu. The location and styling of that menu are specified elsewhere, but the names of each menu's choices and the links to each section are defined here.

The config.toml file is different for each theme. Make sure that when you choose a theme, you read its documentation thoroughly to get an understanding of what each of the configuration options does (more on themes in Section \@ref(themes)).

Options

All built-in options\index{options} that you may set for Hugo are listed at https://gohugo.io/overview/configuration/. You can change any of these options except contentDir, which is hard-coded to content in blogdown. Our general recommendation is that you'd better not modify the defaults unless you understand the consequences. We list a few options that may be of interest to you:

Besides the built-in Hugo options, you can set other arbitrary options in config.toml. For example, it is very common to see an option named params, which is widely used in many Hugo themes. When you see a variable .Site.Params.FOO in a Hugo theme, it means an option FOO that you set under [params] in config.toml, e.g., .Site.Params.author is Frida Gomam with the following config file:

[params]
    author = "Frida Gomam"
    dateFormat = "2006/01/02"

The goal of all these options is to avoid hard-coding anything in Hugo themes, so that users can easily edit a single config file to apply the theme to their websites, instead of going through many HTML files and making changes one by one.

Content

The structure of the content/ directory can be arbitrary. A common structure is that there are a few static pages under the root of content/, and a subdirectory post/ containing blog posts:

├── _index.md
├── about.md
├── vitae.md
├── post/
│   ├── 2017-01-01-foo.md
│   ├── 2017-01-02-bar.md
│   └── ...
└── ...

YAML metadata

Each page should start with YAML\index{YAML} metadata specifying information like the title, date, author, categories, tags, and so on. Depending on the specific Hugo theme and templates you use, some of these fields may be optional.

Among all YAML fields, we want to bring these to your attention:

Body

As we mentioned in Section \@ref(output-format), your post can be written in either R Markdown or plain Markdown. Please be cautious about the syntax differences between the two formats when you write the body of a post.

Shortcode

Besides all Markdown features, Hugo provides a useful feature named "shortcodes." You can use a shortcode\index{Shortcode} in the body of your post. When Hugo renders the post, it can automatically generate an HTML snippet based on the parameters you pass to the shortcode. This is convenient because you do not have to type or embed a large amount of HTML code in your post. For example, Hugo has a built-in shortcode for embedding Twitter cards. Normally, this is how you embed a Twitter card (Figure \@ref(fig:jtleek-tweet)) on a page:

<blockquote class="twitter-tweet">
  <p lang="en" dir="ltr">Anyone know of an R package for
    interfacing with Alexa Skills?
    <a href="https://twitter.com/thosjleeper">@thosjleeper</a>
    <a href="https://twitter.com/xieyihui">@xieyihui</a>
    <a href="https://twitter.com/drob">@drob</a>
    <a href="https://twitter.com/JennyBryan">@JennyBryan</a>
    <a href="https://twitter.com/HoloMarkeD">@HoloMarkeD</a> ?
  </p>
  &mdash; Jeff Leek (@jtleek)
  <a href="https://twitter.com/jtleek/status/852205086956818432">
    April 12, 2017
  </a>
</blockquote>
<script async src="//platform.twitter.com/widgets.js" charset="utf-8">
</script>
knitr::include_graphics('images/jtleek-tweet.png')

If you use the shortcode, all you need in the Markdown source document is:

{{< tweet user="jtleek", id="852205086956818432" >}}

Basically, you only need to pass the username and ID of the tweet to a shortcode named tweet.^[Before Hugo v0.89.0, you only need to pass the ID. Since Twitter has started to require the username since late 2021, this means that you have to use Hugo >= 0.89.0 and provide the username if you want to use the Twitter shortcode. For older versions of Hugo, the shortcodes will be broken, unfortunately.] Hugo will fetch the tweet automatically and render the HTML snippet for you. For more about shortcodes, see https://gohugo.io/extras/shortcodes/.

Shortcodes are supposed to work in plain Markdown documents only. To use shortcodes in R Markdown instead of plain Markdown, you have to call the function blogdown::shortcode(), e.g.,

`r ''````r
blogdown::shortcode(
  "tweet", user = "jtleek", id = "852205086956818432"
)
```

Themes

A Hugo theme\index{Themes} is a collection of template files and optional website assets such as CSS and JavaScript files. In a nutshell, a theme defines what your website looks like after your source content is rendered through the templates.

Hugo has provided a large number of user-contributed themes at https://themes.gohugo.io. Unless you are an experienced web designer, you'd better start from an existing theme here. The quality and complexity of these themes vary a lot, and you should choose one with caution. For example, you may take a look at the number of stars of a theme repository on GitHub, as well as whether the repository is still relatively active. Themes that have not been updated for a long time (e.g., a couple of years) may or may not still work with a later version of Hugo. You will have to test them carefully.

In this section, we will explain how the default theme in blogdown works, which may also give you some ideas about how to get started with other themes.

The default theme

The default theme in blogdown, hugo-lithium\index{Hugo Lithium Theme}, is hosted on GitHub at https://github.com/yihui/hugo-lithium. It was originally written by Jonathan Rutheiser, and I have made several changes to it. This theme is suitable for those who prefer minimal styles, and want to build a website with a few pages and some blog posts.

Typically a theme repository on GitHub has a README file, which also serves as the documentation of the theme. After you read it, the next file to look for is config.toml under the exampleSite directory, which contains sample configurations for a website based on this theme. If a theme does not have a README file or exampleSite directory, you probably should not use it.

The config.toml of the theme hugo-lithium contains the following options:

baseurl = "/"
relativeurls = false
languageCode = "en-us"
title = "A Hugo website"
theme = "hugo-lithium"
ignoreFiles = ["\\.Rmd$", "\\.Rmarkdown", "_files$", "_cache$"]

[permalinks]
    post = "/:year/:month/:day/:slug/"

[[menu.main]]
    name = "About"
    url = "/about/"
[[menu.main]]
    name = "GitHub"
    url = "https://github.com/rstudio/blogdown"
[[menu.main]]
    name = "Twitter"
    url = "https://twitter.com/rstudio"

[params]
    description = "A website built through Hugo and blogdown."

    highlightjsVersion = "9.12.0"
    highlightjsCDN = "//cdnjs.cloudflare.com/ajax/libs"
    highlightjsLang = ["r", "yaml"]
    highlightjsTheme = "github"

    MathJaxCDN = "//cdnjs.cloudflare.com/ajax/libs"
    MathJaxVersion = "2.7.5"

[params.logo]
    url = "logo.png"
    width = 50
    height = 50
    alt = "Logo"

Some of these options may be obvious to understand, and some may need explanations:

If you want to be a theme developer and fully understand all the technical details about these options, you have to understand Hugo templates, which we will introduce in Section \@ref(templates).

Templates

A Hugo theme consists of two major components: templates\index{Templates}, and web assets. The former is essential, and tells Hugo how to render a page.^[The most common functionality of templates is to render HTML pages, but there can also be special templates, for example, for RSS feeds and sitemaps, which are XML files.] The latter is optional but also important. It typically consists of CSS and JavaScript files, as well as other assets like images and videos. These assets determine the appearance and functionality of your website, and some may be embedded in the content of your web pages.

You can learn more about Hugo templates from the official documentation (https://gohugo.io/templates/overview/). There are a great many different types of templates. To make it easier for you to master the key ideas, I created a very minimal Hugo theme, which covers most functionalities that an average user may need, but the total number of lines is only about 150, so we can talk about all the source code of this theme in the following subsection.

A minimal example

XMin is a Hugo theme\index{XMin Theme} I wrote from scratch in about 12 hours. Roughly half an hour was spent on templates, 3.5 hours were spent on tweaking the CSS styles, and 8 hours were spent on the documentation (https://xmin.yihui.org). I think this may be a representative case of how much time you would spend on each part when designing a theme. It is perhaps our nature to spend much more time on cosmetic stuff like CSS than essential stuff like templates. Meanwhile, coding is often easier than documentation.

We will show the source code of the XMin theme. Because the theme may be updated occasionally in the future, you may follow this link to obtain a fixed version that we will talk about in this section: https://github.com/yihui/hugo-xmin/tree/4bb305. Below is a tree view of all files and directories in the theme:

hugo-xmin/
├── LICENSE.md
├── README.md
├── archetypes
│   └── default.md
├── layouts
│   ├── 404.html
│   ├── _default
│   │   ├── list.html
│   │   ├── single.html
│   │   └── terms.html
│   └── partials
│       ├── foot_custom.html
│       ├── footer.html
│       ├── head_custom.html
│       └── header.html
├── static
│   └── css
│       ├── fonts.css
│       └── style.css
└── exampleSite
    ├── config.toml
    ├── content
    │   ├── _index.md
    │   ├── about.md
    │   ├── note
    │   │   ├── 2017-06-13-a-quick-note.md
    │   │   └── 2017-06-14-another-note.md
    │   └── post
    │       ├── 2015-07-23-lorem-ipsum.md
    │       └── 2016-02-14-hello-markdown.md
    ├── layouts
    │   └── partials
    │       └── foot_custom.html
    └── public
        └── ...

LICENSE.md and README.md are not required components of a theme, but you definitely should choose a license for your source code so that other people can properly use your code, and a README can be the brief documentation of your software.

The file archetypes/default.md defines the default template based on which users can create new posts. In this theme, default.md only provided empty YAML metadata:

---
---

The most important directories of a theme are layouts/ and static/. HTML templates are stored under layouts/, and assets are stored under static/.

To understand layouts/, you must know some basics about HTML (see Section \@ref(html)) because the templates under this directory are mostly HTML documents or fragments. There are many possible types of subdirectories under layouts/, but we are only going to introduce two here: _default/ and partials/.

There is a special template 404.html, which Hugo uses to create the 404\index{404.html} page (when a page is not found, this page is displayed):

{{ partial "header.html" . }}

404 NOT FOUND

{{ partial "footer.html" . }}

With all templates above, we will be able to generate a website from Markdown source files. You are unlikely to be satisfied with the website, however, because the HTML elements are not styled at all, and the default appearance may not look appealing to most people. You may have noticed that in header.html, we have included two CSS files, /css/style.css and /css/fonts.css.

You can find many existing open-source CSS frameworks online that may be applied to a Hugo theme. For example, the most popular CSS framework may be Bootstrap: http://getbootstrap.com. When I was designing XMin, I wondered how far I could go without using any of these existing frameworks, because they are usually very big. For example, bootstrap.css has nearly 10000 lines of code when not minimized. It turned out that I was able to get a satisfactory appearance with about 50 lines of CSS, which I will explain in detail below:

The two CSS files are placed under the static/css/ directory of the theme. In the HTML template header.html, the path /css/style.css refers to the file static/css/style.css.

Lastly, this theme provided an example site under exampleSite/. The directory structure may be a little confusing because this is a theme instead of a website. In practice, everything under exampleSite/ should be under the root directory of a website, and the top-level hugo-xmin/ directory should be under the themes/ directory of this website, i.e.,

├── config.toml
├── content/
├── ...
├── themes/
│   └── hugo-xmin/
│
└── ...

The example site provides a sample config.toml, a home page _index.md, an about page about.md, two posts under note/ and two under post/. It also overrides the foot_custom.html in the theme.

Implementing more features {#how-to}

The XMin is actually a highly functional theme, but we understand that it may be too minimal for you. There are a few commonly used features (intentionally) missing in this theme, and we will teach you how to add them by yourself if desired. All these features and the source code can be applied to other themes, too.

knitr::include_graphics('images/github-edit.png')

After you digest the XMin theme and the implementations of additional features, it should be much easier to understand other people's templates. There are a large number of Hugo themes but the primary differences among them are often in styles. The basic components of templates are often similar.

Custom layouts

It is very likely that you want to customize a theme unless you designed it. The most straightforward way is to simply make changes directly in the theme,^[If you are new to web development, be careful changing content within the theme. Minor changes like colors and font sizes can be found within the CSS files of the theme and can be altered simply with minimal risk of breaking the theme's functionality.] but the problem is that a Hugo theme may be constantly updated by its original author for improvements or bug fixes. Similar to the "you break it, you buy it" policy (the Pottery Barn rule), once you touch someone else's source code, you will be responsible for its future maintenance, and the original author should not be responsible for the changes you made on your side. That means it may not be easy to pull future updates of this theme to your website (you have to carefully read the changes and make sure they do not conflict with your changes), but if you are completely satisfied with the current state of the theme and do not want future updates, it is fine to modify the theme files directly.

A theme author who is aware of the fact that users may customize their theme will typically provide two ways: one is to provide options in config.toml, so that you can change these options without touching the template files; the other is to leave a few lightweight template files under layouts/ in the theme, so that you can override them without touching the core template files. Take the XMin theme for example:

I have two empty HTML files head_custom.html and foot_custom.html under layouts/partials/ in the theme. The former will be added inside <head></head> of a page, e.g., you can load JavaScript libraries or include CSS style sheets via <link>. The latter will be added before the footer of a page, e.g., you may load additional JavaScript libraries or embed Disqus comments there.

The way that you customize these two files is not to edit them directly in the theme folder, but to create a directory layouts/partials/ under the root directory of your website, e.g., your directory structure may look like this:

your-website/
├── config.toml
├── ...
├── themes/
│   └── hugo-xmin/
│       ├── ...
│       └── layouts/
│           ├── ...
│           └── partials
│               ├── foot_custom.html
│               ├── footer.html
│               ├── head_custom.html
│               └── header.html
└── layouts
    └── partials
        ├── foot_custom.html
        └── head_custom.html

All files under layouts/ under the root directory will override files with the same relative paths under themes/hugo-xmin/layouts/, e.g., the file layouts/partials/foot_custom.html, when provided, will override themes/hugo-xmin/layouts/partials/foot_custom.html. That means you only need to create and maintain at most two files under layouts/ instead of maintaining all files under themes/. Note that this overriding mechanism applies to all files under layouts/, and is not limited to the partials/ directory. It also applies to any Hugo theme that you actually use for your website, and is not limited to hugo-xmin.

Static files

All files under the static/ directory\index{Static Directory} are copied to public/ when Hugo renders a website. This directory is often used to store static web assets like images, CSS, and JavaScript files. For example, an image static/foo/bar.png can be embedded in your post using the Markdown syntax ![](/foo/bar.png).^[The link of the image depends on your baseurl setting in config.toml. If it does not contain a subpath, /foo/bar.png will be the link of the image, otherwise you may have to adjust it, e.g., for baseurl = "http://example.com/subpath/", the link to the image should be /subpath/foo/bar.png.]

Usually a theme has a static/ folder, and you can partially override its files using the same mechanism as overriding layouts/ files, i.e., static/file will override themes/theme-name/static/file. In the XMin theme, I have two CSS files style.css and fonts.css. The former is the main style sheet, and the latter is a quite small file to define typefaces only. You may want to define your own typefaces, and you can only provide a static/css/fonts.css to override the one in the theme, e.g.,

body {
  font-family: "Comic Sans MS", cursive, sans-serif;
}
code {
  font-family: "Courier New", Courier, monospace;
}

To R Markdown users, another important application of the static/ directory is to build Rmd documents with custom output formats, i.e., Rmd documents not using the blogdown::html_page() format (see Section \@ref(output-format)). For example, you can generate a PDF or presentations from Rmd documents under this directory, so that Hugo will not post-process them but simply copies them to public/ for publishing. To build these Rmd files, you must provide a custom build script R/build.R (see Section \@ref(methods)). You can write a single line of code in this script\index{blogdown::build_dir()}:

blogdown::build_dir("static")

The function build_dir() finds all Rmd files under a directory, and calls rmarkdown::render() to build them to the output formats specified in the YAML metadata of the Rmd files. If your Rmd files should not be rendered by a simple rmarkdown::render() call, you are free to provide your own code to render them in R/build.R. There is a built-in caching mechanism in the function build_dir(): an Rmd file will not be compiled if it is older than its output file(s). If you do not want this behavior, you can force all Rmd files to be recompiled every time: build_dir(force = TRUE).

I have provided a minimal example in the GitHub repository yihui/blogdown-static, where you can find two Rmd examples under the static/ directory. One is an HTML5 presentation based on the xaringan package, and the other is a PDF document based on bookdown.

You need to be cautious about arbitrary files under static/, due to Hugo's overriding mechanism. That is, everything under static/ will be copied to public/. You need to make sure that the files you render under static/ will not conflict with those files automatically generated by Hugo from content/. For example, if you have a source file content/about.md and an Rmd file static/about/index.Rmd at the same time, the HTML output from the latter will overwrite the former (both Hugo and you will generate an output file with the same name public/about/index.html).



rstudio/blogdown documentation built on Feb. 5, 2024, 10:09 p.m.