Missing WordPress Hooks in Hugo
· Field Note
As I’ve been setting up my site, I’ve added a few small features that aren’t included out-of-the-box with Hugo. For example, I’m self-hosting Plausible Analytics. In order to use Plausible on my site, I have to load a script. Coming from a WordPress background, this is something I would normally do with a WordPress Hook.
With WordPress, I can abstract out the logic to load scripts without needing to modify my theme or template files. If I want to insert a new script in my <head>, I can edit or create a functionality plugin. Then I can use the wp_enqueue_script function to properly enqueue the script and inject it into the <head> with the wp_enqueue_scripts hook.
On this hypothetical site, that could look something like this:
📂 /my-wp-site/wp-content/plugins/bnux-func/bnux-functionality.php
<?php
/*
Plugin Name: ~/.bnux Site Functionality Plugin
Description: Custom functionality for read.ryancowl.es.
Version: 1.0
Author: bnux
*/
// Enqueue the Plausible script in the header
function bnux_plausible_script() {
wp_enqueue_script(
'bnux_plausible_script',
'https://stats.ryancowl.es/js/script.tagged-events.js',
array(), // Dependencies
'1.0', // Version
false // Load in footer
);
}
add_action('wp_enqueue_scripts', 'bnux_plausible_script');
?>
With this approach, the script will load regardless of the theme I am using. I don’t need to worry about theme changes or updates inadvertantly removing it in the future.
However, if I want to add this same script to my Hugo site, it’s not quite as clean. I instead need to copy my theme’s head.html partial into the root layouts/partials directory. Then I can edit the copied partial and call the script with something like this:
📂 layouts/partials/head.html
<!-- Plausible Analytics -->
{{ if .Site.Params.plausible.enable }}
{{ if eq hugo.Environment "production" }}
<script async defer
data-domain="{{ .Site.Params.plausible.domain }}"
src="https://stats.ryancowl.es/js/script.tagged-events.js">
</script>
{{ end }}
{{ end }}
This will do the trick to load the script. But now if I change my theme, I’ll need to manually copy these edits to the new theme. Or if the theme I am using has changes upstream, I need to be mindful of the files I copied and modified when pulling those theme updates to my site.
I’ve noticed that many Hugo themes include a way to hook in custom CSS. For example, the Paper theme that I am currently using includes the following:
📂 /layouts/partials/head.html:
<!-- CSS & JS -->
{{ $main := resources.Get "main.css" }}
<!---->
{{ $custom := resources.Get "custom.css" }}
<!---->
{{ $css := slice $main $custom | resources.Concat "main.css" | minify }}
<link rel="preload stylesheet" as="style" href="{{ $css.Permalink }}" />
I can trust that custom.css will not get overwritten in future theme updates, thus preserving any additions or modifications I make to that file. Following similar logic, theme authors could implement something like this for scripts by using a Hugo partial. We can create a custom-scripts.html file in our project root’s layouts/partials directory. And then include it in head.html:
📂 /layouts/partials/head.html
<!-- CSS & JS -->
{{ $main := resources.Get "main.css" }}
<!---->
{{ $custom := resources.Get "custom.css" }}
<!---->
{{ $css := slice $main $custom | resources.Concat "main.css" | minify }}
<link rel="preload stylesheet" as="style" href="{{ $css.Permalink }}" />
<!-- Include custom scripts -->
{{ partial "custom-scripts.html" . }}
If a theme were to include something like this, then we can replicate similar basic functionality of the WordPress’ wp_enqueue_scripts hook. Of course, this approach is still theme-dependent. But if this became a standardized best practice then we could come to rely on this as a way to inject custom functionality into our Hugo site’s <head>, regardless of what theme a given site is currently using.
In moving from a full-fledged CMS to a static site generator, this type of tradeoff may just be par for the course1. It’s also quite possible I’m unaware of a better way to do this with Hugo. Either way, it would be nice to be able to more easily include sitewide functionality out-of-the-box independent of the theme on the site2. If that’s something you’d also like or if you are aware of a better way to do this, let me know :)
Footnotes
-
To be expected; normal; common wiktionary.org/wiki/par_for_the_course ↩
-
I did find an old feature request for hooks but it was auto-closed due to lack of recent activity. I would be curious to learn if this sort of functionality would be beneficial to others. And if so, perhaps a new issue is warranted for further discussion. ↩