hugo

hugo

Hugo was always this black box to me, it still is. But it’s powerful. And I don’t have to deal with Javascript frameworks.

I wouldn’t build a whole theme from scratch though. Just pick a Hugo theme like Typo or Hextra and modify it to your heart’s content.

I would be mentioning little tips or workarounds that I found useful.

My opinion

If you want the “shiny new stuff” like client-side rendering, resumability, Tailwind integration, Shadcn & DaisyUI components and all, then I guess you’re better off using something else. Sure, you can use Tailwind with Hugo, and you can somehow incorporate the whole of React into Hugo, but is it worth it? I don’t think so. I don’t mind page reloads at all. Hugo is so much better than the “Vite + shadow DOM + 200 NPM packages + megabytes of JS bundles” shitshow that is the Javascript ecosystem.

Displaying links to ’tag’ pages

We can assign each Markdown file tags (eg. blog, web, tech, experiences). If you want to show tag page links that display the name & the number of posts associated with it, add this HTML to the concerned layout file:

layouts/index.html
<div id="tags-container">
{{ range $name, $taxonomy := .Site.Taxonomies.tags }}
    {{ with $.Site.GetPage (printf "/tags/%s" $name) }}
    <a id="tag" href="/tags/{{ $name }}/">{{ $name }} ({{ len $taxonomy }})</a>
    {{ end }}
{{ end }}
</div>

This is what I did in my website.

Hardwraps

In markdown, two lines get treated as a paragraph and they get wrapped. I don’t like that behavior. Initially I spammed <br> tags or two spaces after every single line. Now I just enable hardwraps.

hugo.toml
[markup.goldmark]
[markup.goldmark.renderer]
hardWraps = true

Note: This will only work if you use Goldmark as the renderer, which is the default.

Unsafe

Hugo by default doesn’t include HTML written inside Markdown. Enable unsafe to negate this behavior:

hugo.toml
[markup.goldmark]
[markup.goldmark.renderer]
unsafe = true

Disabling RSS

I enable RSS everywhere I can, but for certain kinds of websites, it’s not really needed.

hugo.toml
disableKinds = ["RSS"]

Frontmatter

You can add custom stuff to the frontmatter in a Markdown file and have it render in HTML.

song.md
---
title: "Song 1"
params:
    scale: Fm
    time_signature: 4/4
    tempo: 80
---
layouts/_default/single.html
<div>
    {{ if .Param "scale" }}
        <span><b>Scale</b>: {{ .Params.scale }}</span>
    {{ end }}

    {{ if .Param "time_signature"}}
        <span><b>Time Signature</b>: {{ .Params.time_signature }}</span>
    {{ end }}

    {{ if .Param "tempo"}}
        <span><b>Tempo</b>: {{ .Params.tempo }}</span>
    {{ end }}
</div>

By the way, you can either edit the HTML files within themes/<theme>/layouts directory, or you could just copy the file and place it inside the layouts directory at the root of the project. Make sure to keep the folder hierarchy intact (within layouts) if there is one.

Search

I integrated Pagefind into a website I made for my father, and while Pagefind is okay, I had some problems integrating it with Typo. And I have to run Pagefind on the compiled site for it to work. Theming Pagefind wasn’t fun either, but it works.

Hextra, on the other hand, comes with Flexsearch built-in, so one less thing to think about.

Get Pages From a Directory

The site.GetPage method expects a path (in this case, /tools) and returns all the Page objects from the path.

{{ $tools := site.GetPage "tools" }}
{{ range $tools.RegularPages }}
    {{ partial "card.html" . }}
{{ end }}

Notice the dot in the partial: we’re passing the whole Page object. It could be .Title, .Permalink etc. Here is a list of methods associated with Page objects.

Using Partials

Now this is something messed up.

Hextra provides a “card” shortcode and a “cards” shortcode which is basically a grid container wrapper:

<!-- apparently, shortcodes just get rendered even inside in a code block! -->
<!-- Have to turn < > into </*  */> -->

{{< cards >}}
  {{< card link="../callout" title="Callout" icon="warning" >}}
  {{< card link="../callout" title="Card with tag" icon="tag" tag= "A custom tag" >}}
  {{< card link="/" title="No Icon" >}}
{{< /cards >}}

Which looks like this:

But as far as I know, I can’t use shortcodes in HTML, only partials. Luckily, Hextra provides a card partial. But it won’t take in a Page object. If it did, I could just do

{{ partial "shortcodes/card.html" . }} <!-- notice the dot -->

I can’t do this either:

{{ partial "shortcodes/card.html" link= .RelPermalink title = .Title }}

Instead, I have to do this…

{{ partial "shortcodes/card.html"
    (
        dict
        "link" .RelPermalink
        "title" .Title
    )
}}

I hope I’m wrong, and if I am, I’ll add the solution above, and strike all this text I’ve written.

And I can’t wrap a partial with another partial. It’s possible with shortcodes (just as shown below), but not with partials:
{{< cards >}} {{< card title="card" >}} {{< /cards >}}

Custom PublishDir

By default, when you run hugo build, it compiles the HTML & CSS into the public/ dir. But since I rely on GitHub Pages and I don’t want to deal with GitHub Actions and manage a ~100-line script that spawns a container with Hugo & NodeJS every time I push a commit, I just compile the website to docs/ dir instead and point GitHub Pages to serve from docs/.

hugo.toml
publishDir = "docs"

Simple.