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:
- Logs an
INFOmessage for 3 minor releases - Logs a
WARNmessage for another 12 minor releases - Logs an
ERRORand 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 file | What it was | What I did |
|---|---|---|
partials/head.html | Full copy of the old theme file, with 3 extra tags | Deleted it, moved the tags to extend_head.html |
partials/templates/*.html | Copies of even older theme files, no real changes | Deleted them |
partials/comments.html | My giscus comments | Kept it (and had to fix it later) |
partials/index_profile.html | My customized profile section | Updated 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.
