Introduction

If you run a Hugo blog, you have probably seen something like this in your terminal:

WARN  deprecated: .Language.LanguageDirection was deprecated in Hugo v0.158.0 and will be removed in a future release. Use .Language.Direction instead.

My blog runs on Hugo with the PaperMod theme, and every Hugo upgrade added a warning or two. The site still built fine, so I did what most of us do. I ignored them.

I had been putting off updating PaperMod for quite a while. When I finally checked, my fork was 120 commits behind. I figured updating it would get rid of the warnings, but the update broke my build. Once I fixed that, I noticed giscus wasn’t switching themes properly either. Here’s what I had to fix.

Why Does Hugo Deprecate Things?

Hugo is still at version 0.x, so things get renamed and replaced fairly often. The good news is that it doesn’t break your site overnight. Once something is deprecated, Hugo:

  1. Logs an INFO message for 3 minor releases
  2. Logs a WARN message for another 12 minor releases
  3. Logs an ERROR and fails the build after that

So if you see a WARN, you still have time. The message even tells you what to use instead, which is nice of it.

By default, you won’t see the INFO messages at all. If you want to catch changes early, run this:

hugo build --logLevel info

As a rule of thumb, run this every time you upgrade Hugo.

Where Did the Warnings Come From?

I was on Hugo v0.165.0, and hugo server greeted me with three warnings:

WARN  deprecated: .Language.LanguageDirection was deprecated in Hugo v0.158.0 and will be removed in a future release. Use .Language.Direction instead.
WARN  The "gist" shortcode was deprecated in v0.143.0 and will be removed in a future release. See https://gohugo.io/shortcodes/gist for instructions to create a replacement.
WARN  deprecated: .Language.LanguageCode was deprecated in Hugo v0.158.0 and will be removed in a future release. Use .Language.Locale instead.

Before fixing anything, we need to know whether a warning comes from our own files or from the theme. A quick search tells us that:

grep -rn "LanguageDirection\|LanguageCode" layouts themes/PaperMod/layouts

Two of the warnings came from PaperMod’s baseof.html and rss.xml. The gist one, on the other hand, was all mine.

Fixing the gist Warning

Hugo is removing its built-in gist shortcode and asks you to keep your own copy instead. At some point I had copied Hugo’s version into layouts/shortcodes/gist.html, and the copy came with this line:

{{- warnf "The %q shortcode was deprecated in v0.143.0 and will be removed in a future release. See https://gohugo.io/shortcodes/gist for instructions to create a replacement." .Name -}}

Yes, my shortcode was warning about itself. I deleted that line, and this is all that’s left:

<script src="https://gist.github.com/{{ index .Params 0 }}/{{ index .Params 1 }}.js{{if len .Params | eq 3 }}?file={{ index .Params 2 }}{{end}}"></script>

Since it lives in my own layouts/shortcodes folder, it will keep working even after Hugo removes its version.

For the other two warnings, the easiest fix was updating the theme.

Updating PaperMod

My Setup: A Fork as a Submodule

PaperMod lives in my repo as a Git submodule, pointing to my own fork instead of the original. Why a fork? Well, if the original repo ever disappears, I still have a copy and my blog keeps building.

To see how your submodule is set up, run these two:

git submodule status
cat .gitmodules

Here’s what mine looked like:

[submodule "themes/PaperMod"]
	path = themes/PaperMod
	url = https://github.com/ayonkhan/hugo-PaperMod.git
	branch = v8.0

There were two problems. My fork was 120 commits behind the original, and the submodule was pinned to the v8.0 tag from September 2024. So even syncing the fork wouldn’t have changed anything on its own.

Step 1: Sync the Fork

On GitHub, open your fork, click Sync fork, then Update branch. I had never changed anything in my fork, so there were no conflicts.

Step 2: Follow master Instead of an Old Tag

git submodule set-branch --branch master themes/PaperMod

This changes branch = v8.0 to branch = master in .gitmodules.

Step 3: Pull the Latest Theme

git submodule update --remote themes/PaperMod

This pulls the newest commit from the fork into themes/PaperMod.

From now on, updating the theme takes just two steps: Sync fork on GitHub, then this command. The fork only changes when I sync it, so my backup is always a version I have already tested.

Bonus: A Pile of Failure Emails

Shortly after syncing, I got four emails from GitHub telling me that workflow runs had failed in my fork. Not exactly the welcome I was hoping for.

It turns out PaperMod has its own GitHub Actions workflows, one to test the theme and one to publish its demo site. My fork copied them. Syncing 120 commits counts as a push, so GitHub ran them in my fork, where they failed because nothing is set up for them. They have nothing to do with the blog itself.

Since I only keep the fork as a backup, I turned Actions off for it. You can do that under Settings → Actions → General → Disable actions, or with the GitHub CLI:

gh api -X PUT repos/ayonkhan/hugo-PaperMod/actions/permissions -F enabled=false

The Build Broke

Now for the fun part. After the update, hugo server refused to build (paths shortened):

ERROR error building site: render: [en v1.0.0 guest] failed to render pages: render of "/404" failed:
".../themes/PaperMod/layouts/baseof.html:16:8": execute of template failed:
template: 404.html:16:8: executing "404.html" at <partial "head.html" .>: error calling partial:
".../layouts/partials/head.html:50:42": execute of template failed:
template: _partials/head.html:50:42: executing "_partials/head.html" at <resources.Concat>:
error calling Concat: expected slice of Resource objects, received []interface {} instead

Take a closer look at the second path. The problem wasn’t in the theme. It was my own layouts/partials/head.html.

At some point I had copied PaperMod’s entire head.html into my own layouts folder, just to add three tags. Hugo always prefers your copy over the theme’s, so it kept using my old file with the new theme, and the build fell over.

So I went through my overrides and compared each one with the same file from the old theme, to see what I had actually changed. If you do this, do it before updating the theme. Otherwise, you’ll be comparing against the new version.

diff themes/PaperMod/layouts/partials/head.html layouts/partials/head.html

Here’s what I found:

My fileWhat it wasWhat I did
partials/head.htmlFull copy of the old theme file, with 3 extra tagsDeleted it, moved the tags to extend_head.html
partials/templates/*.htmlCopies of even older theme files, no real changesDeleted them
partials/comments.htmlMy giscus commentsKept it (and had to fix it later)
partials/index_profile.htmlMy customized profile sectionUpdated it from the new theme

Use extend_head.html Instead

PaperMod comes with an empty file called extend_head.html, which it loads at the end of its own head.html. Whatever you put there ends up in the page’s <head>. You add your tags, and the theme takes care of the rest.

My layouts/partials/extend_head.html now has just this:

<link rel="manifest" href="{{ site.Params.assets.manifest | default "manifest.json" | absURL }}">
<meta name="apple-mobile-web-app-title" content="{{ site.Title }}">
<meta name="application-name" content="{{ site.Title }}">

Then I removed the old copies:

git rm layouts/partials/head.html
git rm -r layouts/partials/templates

There’s also an extend_footer.html for things that go at the end of the page.

If you only need to add something, use these files. Copy a whole template only when you need to change how it works.

After that, the build was clean. No errors, and no warnings either, since the new PaperMod already uses .Language.Direction and .Language.Locale.

One more thing. The new PaperMod needs Hugo v0.158.0 or newer, and it stops the build with a clear message if you’re on an older version. So upgrade Hugo first.

Giscus Stopped Following Dark Mode

I pushed everything and thought I was done. Then I opened a post with comments.

The giscus comments no longer switched with the theme. Even worse, in light mode they were using my dark theme, with pale text on a transparent background. On a white page, you could barely see them.

My giscus script checked for dark mode like this:

function isDark() {
    return new RegExp('(\\s|^)' + 'dark' + '(\\s|$)').test(document.body.className);
}

The old PaperMod added a dark class to <body>. The new one sets an attribute on <html> instead:

<html lang="en" data-theme="dark">

So my script never found the class and always thought the page was light. When I clicked the theme toggle, it sent giscus the opposite of what it thought, which is how light mode ended up with dark comments.

Here’s my layouts/partials/comments.html now. The giscus values come from params.giscus in my config.yml.

{{ if and (eq site.Params.comments true) (not .Draft) }}
<script type="text/javascript">
(function() {
    var lightTheme = '{{ site.Params.giscus.theme }}';
    var darkTheme = '{{ site.Params.giscus.theme_dark }}';

    // PaperMod marks the current theme on <html data-theme="dark|light">
    function isDark() {
        return document.documentElement.dataset.theme === 'dark';
    }

    var giscusEl = document.createElement('script');

    giscusEl.src = 'https://giscus.app/client.js';
    giscusEl.setAttribute('data-repo', '{{ site.Params.giscus.repo }}');
    giscusEl.setAttribute('data-repo-id', '{{ site.Params.giscus.repo_id }}');
    giscusEl.setAttribute('data-category', '{{ site.Params.giscus.category }}');
    giscusEl.setAttribute('data-category-id', '{{ site.Params.giscus.category_id }}');
    giscusEl.setAttribute('data-mapping', '{{ site.Params.giscus.mapping }}');
    giscusEl.setAttribute('data-reactions-enabled', '{{ site.Params.giscus.reactions_enabled }}');
    giscusEl.setAttribute('data-emit-metadata', '0');
    giscusEl.setAttribute('data-theme', isDark() ? darkTheme : lightTheme);
    giscusEl.setAttribute('crossorigin', 'anonymous');
    giscusEl.async = true;

    document.getElementsByTagName('article')[0].appendChild(giscusEl);

    // Send the theme that matches the page to giscus
    function syncTheme() {
        var frame = document.querySelector('iframe.giscus-frame');
        if (!frame) return;
        frame.contentWindow.postMessage({ giscus: {
            setConfig: { theme: isDark() ? darkTheme : lightTheme }
        }}, 'https://giscus.app');
    }

    // Whenever PaperMod changes data-theme, update giscus
    new MutationObserver(syncTheme)
        .observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme'] });

    // When giscus finishes loading, sync once in case the theme changed while it loaded
    window.addEventListener('message', function onGiscusReady(event) {
        if (event.origin !== 'https://giscus.app') return;
        window.removeEventListener('message', onGiscusReady);
        syncTheme();
    });
})();
</script>
<noscript>Please enable JavaScript to view the <a href="https://giscus.app/">comments powered by giscus.</a></noscript>
{{ end }}

isDark() now checks data-theme on <html>. I also dropped the click listener on the toggle button, since it relied on my handler running before PaperMod’s. A MutationObserver on data-theme is more reliable, because it fires no matter what changed the theme.

The message listener covers one edge case. If you toggle the theme before the comments finish loading, there’s no iframe to update yet, so the script syncs once when giscus says it’s ready.

A Small Visual Change

I also noticed that the profile section on my home page looked a bit more spread out. That’s because the new PaperMod changed the gap between the profile items from 10px to 1rem.

I liked it a little tighter, so I added this to a CSS file in assets/css/extended/. PaperMod loads everything in that folder after its own styles, so these rules win.

.profile .profile_inner {
    gap: 10px;
}

Conclusion

None of these problems were hard on their own. Each warning was a small fix. Letting them pile up is what turned it into one bigger job, with an outdated theme and old overrides breaking at the same time.

If I had to pick one mistake to avoid, it would be copying whole theme files just to add a few lines. Hooks like extend_head.html would have saved me from the broken build entirely.

Also, test a page with comments before you push. I checked the home page but not a post with comments, so that bug went live. Giscus works on localhost too, so there’s really no excuse (I know, I know).

All in all, the fixes took less than an hour. My blog now runs on the latest Hugo and PaperMod with no warnings, and the next upgrade should be a lot less eventful.