Skip to main content
  1. Posts/

Simple Dynamic Javascript variable passing in Hugo

·3 mins

Background #

The Congo theme I am using for this site has a custom 404 error page that I just discovered, along with localizations for multiple languages. Since I’m only going to have this site in English and since I love wasting time getting dynamic error messages I wanted to further customize the 404 page.

Result #

Visit this page and refresh a few times to see what I’m talking about.

Steps #

First, I created a notfound.json file saved in the data folder, with lots of funny / punny error messages (a lot of them were auto generated by AI):

{
    "messages": [
        "not found",
        "page not found",
        "oops! looks like the page is gone..",
        "this link is broken or the page has moved",
        "oh no! it looks like the demogorgon took this page to the upside down",
        "either we broke something or you cannot type",
        ...
    ]
}

Then, I copied over the default 404.html file from the themes’ layouts folder to my own layouts folder.

{{ define "main" }}
  <h1 class="mb-3 text-4xl font-extrabold">{{ i18n "error.404_title" | emojify }}</h1>
  <p class="mb-12 mt-8 text-neutral-400 dark:text-neutral-500">
    {{ i18n "error.404_error" | emojify }}
  </p>
  <div class="prose dark:prose-invert">
    <p>{{ i18n "error.404_description" | emojify }}</p>
  </div>
{{ end }}

The section I wanted to update is the error.404_description. I want to randomly pull that from the json file.

Hugo has a some built-in functions to generate random numbers, strings, etc. as per this GeekThis post, but the problem is that using something like now.Unix or shuffle $slice is only run during the Hugo build time, and the value remains the same until the next time the site is built.

I wanted the message to change on every page reload, so needed to incorporate some simple javascript somehow. It seemed pretty straightforward, but passing variables and data back and forth between the 404.html template and the javascript was a little tricky.

Final solution #

This is what ended up working, and I’m not 100% sure why it works. It seems the magic line is document.write(message) – without that line, the message doesn’t get printed, even with a <p>{{ $message | emojify }}</p> after the closing script tag.

  <div class="prose dark:prose-invert">
      {{ $p := getJSON "data/notfound.json" }}
      {{ $slice := $p.messages }}
      {{ $message := "" }}
      <script>
        let end = {{ len $slice }};
        let index = Math.floor(Math.random() * end);
        message = {{ $slice }}[index];
        document.write(message);
      </script>
  </div>
  1. use getJSON Hugo function to read json file
  2. get messages from json file and save as a $slice
  3. initialize an empty $message string
  4. randomly create index based on length of the $slice
  5. save and print the message

References #