Due to issues with updating PHP on my shared hosting which I cannot be bothered to resolve, I am now considering migrating from Grav to Hugo.

Hugo is a static site generator. Hugo does not have a GUI to create posts or view statistics, which are features I will miss. It will be that much more work to create a post, compile, and upload so I’m still considering my options…

But since my site was broken anyway, here is my basic setup with Hugo.

Hugo Project

I installed Hugo. I started with the initial theme, but below have changed it to PaperMod.

hugo new project mybyways --format yaml
cd mybyways
git init
git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod

Next, hugo.yaml file:

baseURL: https://myByways.com
locale: en-us
title: myByways
theme: PaperMod
subtitle: the road less travelled

menu:
  main:
    - name: Blog
      url: /post/
      weight: 10
    - name: Tags
      url: /tags/
      weight: 20

params:
  env: production
  title: myByways
  description: the road less travelled
  DateFormat: 2 Jan 2006
  defaultTheme: auto
  ShowPostNavLinks: true
  ShowBreadCrumbs: true
  ShowCodeCopyButtons: false
  UseHugoToc: true

Contents Folder

In the content folder, I first created the content for the home page:

---
title: myByways
subtitle: the road less traveled
---
Hi! Im *C.Y. Wong*, and **welcome** to my slice of the Internet!

And then create a directory called post that will contain all my posts. I want to follow a similar directory structure as I had with Grav, where:

  • each new post is in a directory of it’s own, so that all images, etc. are all together,
  • and each directory starts with a numeric prefix followed by a dot, e.g. 267.migrating-from-grav-to-hugo, which needs to be converted into a URL without the prefix (see slug below),
  • and instead of item.md, rename the content to index.md.

Markdown Migration

Lots to edit for each post including, but not limited to, fixing frontmatter:

  • mapping old frontmatter to new formats, e.g. I used updated: but Hugo uses modified: as the last modified date,
  • modifying dates from my previous format to yyyy-mm-dd format,
  • modifying the old nested Grav tag: to a root-level tags: YAML element,
  • removing unused items, like published: true and the old taxonomy: tags,
  • adding a slug to http://myByways.com/post/migrating-from-grav-to-hugo so that the numeric prefix is not expose.

And fixing content:

  • convert the manual summary divider from === to <! --more-->,
  • convert previous alert format !, !!, !!, !!! to the > [!NOTE], > [!TIP], > [!IMPORTAN], > [!WARNING] blockquote format,
  • and other fixes including removing the additional ?lightbox query string from items.

ℹ️ Warning

Vibe coding ahead.

I’m not posting the python code that migrated my old content to the new format, since the entire Python script is vibe coded. I used IBM Bob, because I like the way it works - planning, designing, coding, testing and fixing issues.

IBM Bob vibe coding a Python script to migrate from Grav to Hugo

A few manual fixes later… and hugo serve runs without errors!

Render Hooks

I use the standard render hooks for:

Alerts layouts/_markup/render-blockquote.html:

{{ $emojis := dict
  "caution" ":exclamation:"
  "important" ":information_source:"
  "note" ":information_source:"
  "tip" ":bulb:"
  "warning" ":information_source:"
}}

{{ if eq .Type "alert" }}
  <blockquote class="alert alert-{{ .AlertType }}">
    <p class="alert-heading">
      {{ transform.Emojify (index $emojis .AlertType) }}
      {{ with .AlertTitle }}
        {{ . }}
      {{ else }}
        {{ or (i18n .AlertType) (title .AlertType) }}
      {{ end }}
    </p>
    {{ .Text }}
  </blockquote>
{{ else }}
  <blockquote>
    {{ .Text }}
  </blockquote>
{{ end }}

And I like external links to be clearly marked, so layouts/_markup/render-link.html:

<a href="{{ .Destination | safeURL }}"
  {{- with .Title }} title="{{ . }}"{{ end -}}
>
  {{- with .Text }}{{ . }}{{ end -}}
</a>
{{- /* chomp trailing newline */ -}}

Then, to add custom CSS to the theme’s, create assets/css/extended/custom.css:

a[rel="external"]:after {
  content: " ↗";
  font-size: 0.8em;
}

Images Tweak

Grav automatically supported retina images (HiDPI) via the appropriate srcset as long as retina image filenames were prefixed with @2x.

So, I need a image render hook to check for if the file is @2x, even if it is not in the markdown image destination and build the srcset (also vibe coded and poorly tested):

{{- $dest  := .Destination -}}
{{- $alt   := .Text | safeHTML -}}
{{- $title := .Title -}}
{{- $page  := .Page -}}
{{- if or (hasPrefix $dest "http://") (hasPrefix $dest "https://") -}}
  <img src="{{ $dest }}"
       {{- with $alt }} alt="{{ . }}"{{ end -}}
       {{- with $title }} title="{{ . }}"{{ end }}
       loading="lazy">
{{- else -}}
  {{- $ext   := path.Ext $dest -}}
  {{- $base  := strings.TrimSuffix $ext $dest -}}
  {{- $probe := printf "%s@2x%s" $base $ext -}}
  {{- $res2x := $page.Resources.GetMatch $probe -}}
  {{- if $res2x -}}
    {{- $w1x  := div $res2x.Width  2 -}}
    {{- $h1x  := div $res2x.Height 2 -}}
    {{- $res1x := $res2x.Resize (printf "%dx%d Lanczos" $w1x $h1x) -}}
    <img srcset="{{ $res2x.RelPermalink }} 2x, {{ $res1x.RelPermalink }} 1x"
         src="{{ $res1x.RelPermalink }}"
         width="{{ $w1x }}"
         height="{{ $h1x }}"
         {{- with $alt }} alt="{{ . }}"{{ end -}}
         {{- with $title }} title="{{ . }}"{{ end }}
         loading="lazy">
  {{- else -}}
    {{- $res := $page.Resources.GetMatch $dest -}}
    {{- if $res -}}
      <img src="{{ $res.RelPermalink }}"
           width="{{ $res.Width }}"
           height="{{ $res.Height }}"
           {{- with $alt }} alt="{{ . }}"{{ end -}}
           {{- with $title }} title="{{ . }}"{{ end }}
           loading="lazy">
    {{- else -}}
      {{- warnf "render-image: resource not found: %q in page %s" $dest $page.RelPermalink -}}
      <img src="{{ $dest }}"
           {{- with $alt }} alt="{{ . }}"{{ end -}}
           {{- with $title }} title="{{ . }}"{{ end }}
           loading="lazy">
    {{- end -}}
  {{- end -}}
{{- end -}}

Build

I use this to build the final public folder to be uploaded to the web server:

hugo build --gc --minify --cleanDestinationDir 

Much more to come, as I do not want to use the standard template… when I find the time!