Featured image of post Notes on Migrating the Hugo Stack Theme from v3 to v4

Notes on Migrating the Hugo Stack Theme from v3 to v4

Overview Link to this heading

Notes on the changes I made and the issues I ran into while migrating the Hugo Stack theme used on this site from v3 to v4.

I mostly followed the official migration guide below.

Environment Link to this heading

  • OS: Windows 11 Pro 25H2
  • Hugo: v0.155.3 → v0.165.0
  • hugo-theme-stack: v3.31.0 → v4.0.3

Upgrade Hugo Link to this heading

Stack v4 requires Hugo v0.157.0 or later. My local version was v0.155.3, so I had to upgrade. I went with the latest version at the time (v0.165.0).

Hugo binaries are available from Hugo’s GitHub releases page.

Stack requires the extended version of Hugo in both v3 and v4, so download a release with extended in its name. Extract the downloaded file and put its contents somewhere on your PATH. I’m on Windows, so I put it in C:\HugoExtended\bin and added C:\HugoExtended\bin to the PATH environment variable.

Checking the Hugo version
$ hugo version
hugo v0.165.0-76a5e1880ab46688155b02e99bab9be2a6134492+extended windows/amd64 BuildDate=2026-08-12T14:26:28Z VendorInfo=gohugoio

This site is hosted on Netlify, so I also had to bump Netlify’s build environment to Hugo v0.165.0. If you set HUGO_VERSION in netlify.toml, or as an environment variable in the Netlify dashboard, change that too.

Fix the X shortcode Link to this heading

This one comes from upgrading Hugo rather than from the Stack theme itself: Hugo’s built-in tweet shortcode was removed in Hugo v0.156. Use the x shortcode instead.

Example of the shortcode change
- {{< tweet user="username" id="1234567890" >}}
+ {{< x user="username" id="1234567890" >}}

Replace .Site.Data with hugo.Data Link to this heading

This is another change caused by the Hugo upgrade: Hugo’s built-in .Site.Data was deprecated in Hugo v0.156. Use hugo.Data instead. In my case a custom shortcode I had written used .Site.Data, so I replaced it.

Temporarily move custom files aside Link to this heading

Going from v3 to v4, the way the theme is written and how its files are split up have both changed. So if you have layouts or assets directories that override the theme, move them aside temporarily.

One caveat: shortcodes (layouts/shortcodes in v3) must exist or the build will fail, so if you want builds to keep succeeding during the migration, move just the shortcodes to layouts/_shortcodes/.

Migrate the config files Link to this heading

The config files were YAML in v3, but v4 switched to TOML. Hugo converts YAML automatically, so it seems that sticking with YAML should be fine. Since I was going to review the settings anyway, I took the opportunity to switch to TOML.

hugo-theme-stack-starter is a good reference for how to write the config. The Stack theme demo works too. Judging by Stack’s Getting Started, hugo-theme-stack-starter seems to be the official template, so that’s what I used as my reference. Create a config/_default directory and put the following files in it.

config.toml Link to this heading

config.toml holds the basic information about the site. I changed the following from the official template.

  • Added theme = "hugo-theme-stack"
  • Changed baseurl to my own site’s URL
  • Changed locale and defaultContentLanguage to Japanese, and accordingly set hasCJKLanguage to true
I manage the Hugo theme as a Git Submodule rather than a Hugo Module, which is why theme = "hugo-theme-stack" is needed. If you manage it as a Hugo Module, you don’t need theme.
In TOML, settings that don’t belong to any table won’t take effect unless you write them first.
config.toml after the changes

config.toml
theme     = "hugo-theme-stack"
baseurl   = "https://notes.nakurei.com/"
locale    = "ja-JP"
title     = "NakuRei's Notes"
copyright = "NakuRei"

# Theme i18n support
# See all available values at https://github.com/CaiJimmy/hugo-theme-stack/tree/master/i18n
defaultContentLanguage = "ja"

# Set hasCJKLanguage to true if DefaultContentLanguage is in [zh-cn ja ko]
# This will make .Summary and .WordCount behave correctly for CJK languages.
hasCJKLanguage = true

enableRobotsTXT = true
enableGitInfo = true

timeout = "300s"

[services.googleAnalytics]
    ID = "G-XXXXXXXXXX"

[pagination]
    pagerSize = 10

[frontmatter]
    lastmod = ["lastmod", ":git", "date", "publishDate"]

languages.toml Link to this heading

languages.toml holds the multilingual settings. These options are the same as languages in v3.

In the hugo-theme-stack-starter template the file is named _languages.toml; the leading underscore is there to keep the settings from being applied. So when you actually configure this, create it as languages.toml with the underscore removed.
languages.toml after the changes

languages.toml
[ja]
    label  = "日本語"
    locale = "ja"
    weight = 1

    [ja.params]
        description = "努力の積み重ねを記録しておくためのサイト。"

    [ja.params.sidebar]
        subtitle = "努力の積み重ねを記録しておくだけ"

[en]
    label  = "English"
    locale = "en"
    weight = 2

    [en.params]
        description = "A website to keep track of my efforts."

    [en.params.sidebar]
        subtitle = "Just keep track of my efforts."

markup.toml Link to this heading

markup.toml holds the settings for Markdown rendering. There’s no change from what I had under markup in v3, so it ports over as-is.

markup.toml after the changes

markup.toml
# Markdown renderer configuration
[goldmark.renderer]
    unsafe = false

[goldmark.extensions.passthrough]
    enable = true

    [goldmark.extensions.passthrough.delimiters]
        block  = [['\[', '\]'], ['$$', '$$']]
        inline = [['\(', '\)']]

[tableOfContents]
    endLevel   = 4
    ordered    = true
    startLevel = 2

[highlight]
    noClasses          = false
    codeFences         = true
    guessSyntax        = true
    lineNoStart        = 1
    lineNos            = false
    lineNumbersInTable = true
    tabWidth           = 4

menu.toml holds the settings for the main menu and the social menu. I only configure the social menu on this site, but at least for that menu, the options are the same as in v3.

menu.toml after the changes

menu.toml
[[social]]
    identifier = "github"
    name       = "GitHub"
    url        = "https://github.com/NakuRei"

    [social.params]
        icon = "brand-github"

[[social]]
    identifier = "x"
    name       = "X"
    url        = "https://x.com/nakurei7901"

    [social.params]
        icon = "brand-twitter"

[[social]]
    identifier = "rss"
    name       = "RSS"
    url        = "https://notes.nakurei.com/index.xml"

    [social.params]
        icon = "rss"

[[social]]
    identifier = "zenn"
    name       = "Zenn"
    url        = "https://zenn.dev/nakurei"

    [social.params]
        icon = "link"

module.toml Link to this heading

module.toml holds the Hugo Module settings. I manage the theme as a Git Submodule rather than a Hugo Module, so I didn’t create module.toml.

params.toml Link to this heading

params.toml holds site-wide parameters — what used to live under params in v3. The following params changed in v4.

params.toml after the changes

params.toml
# Pages placed under these sections will be shown on homepage and archive page.
mainSections = ["post"]
# Output page's full content in RSS.
rssFullContent = true
favicon = "img/favicon.png"

# Accepted values: "default", "lastmod"
# default = see https://gohugo.io/quick-reference/glossary/#default-sort-order
# lastmod = sort by last modified date, in descending order
SortBy = "lastmod"

[author]
    name = "NakuRei"
    url  = "https://notes.nakurei.com/"

[footer]
    since      = 2022
    customText = ""

[dateFormat]
    published   = ":date_medium"
    lastUpdated = ":date_medium"

[sidebar]
    compact  = false
    emoji    = "🐢"
    subtitle = ""
    avatar   = "img/avatar.png"

[article]
    headingAnchor = true
    math          = false
    toc           = true
    readingTime   = true

    [article.license]
        enabled = true
        default = "Licensed under CC BY-NC-SA 4.0"

[widgets]
    homepage = [
        { type = "search" },
        { type = "categories", params = { limit = 10 } },
        { type = "tag-cloud", params = { limit = 10 } },
        { type = "archives", params = { limit = 5 } },
    ]
    page = [
      { type = "toc" },
      { type = "categories", params = { limit = 10 } },
      { type = "tag-cloud", params = { limit = 10 } },
    ]

[opengraph.twitter]
    site = "nakurei7901"
    card = "summary_large_image"

[colorScheme]
    toggle  = true
    default = "auto"

## Comments
[comments]
    enabled  = false
    provider = "disqus"

    # See all the available configurations at https://github.com/CaiJimmy/hugo-theme-stack/blob/master/config/_default/params.toml
    # Copy the configurations you need from there and paste it here.

## Original url-card shortcode
defaultNoImage     = "icons/photo-off.svg"
defaultNoLinkImage = "/icons/link-off.svg"
imageQuality       = 80

permalinks.toml Link to this heading

permalinks.toml contains the same settings that were under permalinks in v3.

permalinks.toml after the changes

permalinks.toml
post = "/post/:slug/"
page = "/:slug/"

related.toml Link to this heading

related.toml contains the same settings that were under related in v3. There are no new options.

related.toml after the changes

related.toml
# Related contents configuration
includeNewer = true
threshold    = 60
toLower      = false

indices = [
    { name = "tags", weight = 100 },
    { name = "categories", weight = 200 },
]

Move the favicon Link to this heading

The favicon and avatar images lived in static/ in v3, but v4 expects them in assets/. On this site the avatar was already in assets/img/, but the favicon was in static/img/, so I moved the favicon image to assets/img/.

Fix the front matter of your pages Link to this heading

The front matter of articles changed between v3 and v4. What was hidden in v3 became build.list in v4. So if you were setting hidden = true, you need to change build.list to never like this.

Example of the front matter change
---
- hidden: true
+ build:
+     list: never
---

If you were setting hidden = false, set build.list to always.

Example of the front matter change
---
- hidden: false
+ build:
+     list: always
---

Migrate your layout customizations Link to this heading

In Hugo, you can override a theme’s files by placing a file with the same name in the same location. Between v3 and v4, the Stack theme changed both how the theme is written and how its files are split up. So if you customized the theme, you need to fix the location, name and content of your customized files to match v4.

Move the contents of layouts/_default/ into layouts/ Link to this heading

With the Modern Template System introduced in Hugo v0.146.0, organizing files under a layouts/_default/ folder was deprecated and consolidated into placing them directly under layouts/. The Stack theme followed suit: in v4 the contents of layouts/_default/ moved into layouts/. So if you were using layouts/_default/, move it to layouts/.

In my case I had modified the theme’s layouts/_default/_markup, so I moved layouts/_default/_markup to layouts/_markup.

Move the contents of layouts/partials into layouts/_partials Link to this heading

This is another consequence of the Modern Template System introduced in Hugo v0.146.0. Stack v4 follows it and changed layouts/partials to layouts/_partials. So if you customized the theme, you need to move the contents of layouts/partials into layouts/_partials.

Change the header image height Link to this heading

In Stack v4 you can change the header image height in assets/scss/custom.scss1. The defaults are 150px on mobile, 200px in between, and 250px on large screens. In v3 I had customized these to 100px on mobile, 150px in between and 150px on large screens, so I created assets/scss/custom.scss with the following.

assets/scss/custom.scss
:root {
    --article-image-height: 100px;

    @include respond(md) {
        --article-image-height: 150px;
    }

    @include respond(xl) {
        --article-image-height: 150px;
    }
}

Remove var(--zh-font-family) Link to this heading

In v3 I had customized the CSS on this site to change the font, but Stack v4 removed var(--zh-font-family), so my existing setting was ignored if left as-is. I removed var(--zh-font-family). Referencing an undefined CSS variable invalidates the whole property, so just deleting it brought my customization back.

References and URLs Link to this heading


  1. In Stack v3 you had to override assets/scss/partials/article.scss to change this, so personally I’m glad about this change. ↩︎

Licensed under CC BY-NC-SA 4.0
Last updated on Sep 13, 2026