Adding dynamic content to HUGO


This website is build using Hugo, a framework to generate static websites from markdown-files. This means that after the compile-step, there are only pure HTML-files, which can just be hosted on any webserver. While this has several advantages in terms of eg. speed, debugability, search engine optimization and much more, a big disadvantage is that there is no straightforward way to include dynamic content. As everything is unchanged from the compilation on, allowing for content dependent on user input or from real-time data, or just including content generated from other web frameworks such as Python’s flask or Django, is not easy. However, thanks to Javascript, it is absolutely possible to incorporate such dynamic content, as I have done for example in my CV, which is generated with my custom CV-Generator.

Javascript

function includeHTML(url, rootid, stylecontent="", url_params_fn = null) {
  if (url_params_fn === null) {
    url_params_fn = () => ""
  }
  elmnt = document.getElementById(rootid);
  if (url && rootid) {
    console.log(url + url_params_fn());
    try {
      document.getElementById('open_full').href = url + url_params_fn();
    } catch (error) {}
    try {
      var shadowRoot = elmnt.attachShadow({ mode: 'open' });
      if (shadowRoot) {
        console.log("Shadow DOM created successfully:", shadowRoot);
        fetch(url + url_params_fn())
          .then(response => {
            if (!response.ok) {
              throw new Error("Page not found");
            }
            return response.text();
          })
          .then(html => {
            /* Extract the <body> content from the response and set the shadow DOM innerHTML from it */
            var doc = new DOMParser().parseFromString(html, 'text/html');
            var bodyContent = doc.body.innerHTML;
            shadowRoot.innerHTML = bodyContent;
            /* Add inline styles within the shadow DOM for increased specificity */
            var inlineStyles = document.createElement('style');
            inlineStyles.textContent = stylecontent;
            shadowRoot.appendChild(inlineStyles);
            /* Clone and append stylesheets to the stylesheets-container */
            var stylesContainer = document.createElement('div');
            var stylesheets = doc.querySelectorAll('link[rel="stylesheet"]');
            stylesheets.forEach(function (stylesheet) {
              var clonedStylesheet = document.createElement('link');
              clonedStylesheet.setAttribute('rel', 'stylesheet');
              clonedStylesheet.setAttribute('href', stylesheet.href);
              stylesContainer.appendChild(clonedStylesheet);
            });
            shadowRoot.appendChild(stylesContainer); /* Append stylesheets container to shadow DOM */
            console.log("Processing element:", elmnt);
            console.log("custom-sandbox dimensions:", elmnt.offsetWidth, elmnt.offsetHeight);
          })
          .catch(error => {
            console.error("Error fetching Document:", error);
          });
      } else {
        console.error("Error creating shadow DOM: shadowRoot is null");
      }
    } catch (error) {
      console.error("Error creating shadow DOM:", error);
    }
  }
}

which I build in like this:

{{ if (findRE "<custom-sandbox" .Content 1) }}
    <script src='{{ "js/include_html.js" | absURL }}'></script>
{{ end }}

The shortcode content is this:

{{ $url := .Get "link" }}
{{ if .Get "link_from_env" }}
  {{ $url = getenv (.Get "link_from_env") }}
{{ end }}

{{ $url_params := "function () { \n const urlparams = new URLSearchParams(window.location.search); " }}
{{ range $key, $val := .Params }}
  {{ if hasPrefix $key "default_arg" }}
    {{ $key = substr $key 12 }}
    {{ $url_params = printf "%s%s%s%s%s%s%s%s" $url_params "if (urlparams.get(\"" $key "\") === null) { urlparams.set(\"" $key "\", \"" $val "\")}" }}
  {{ end }}
{{ end }}
{{ $url_params = printf "%s%s" $url_params "; console.log(urlparams.toString()); return \"?\"+urlparams.toString(); }" }}


<script>
  document.addEventListener("DOMContentLoaded", function() {
    includeHTML({{ $url }}, {{ .Get "class_id" }}, {{ .Get "custom_css" }}, {{ $url_params | safeJS }}) });
</script>

<custom-sandbox id="{{ .Get "class_id" }}"></custom-sandbox>

And it’s called like this:

{< include_html link_from_env="HUGO_GETCV_URL" class_id="cvroot"
    custom_css=`:host {line-height: 1.6; font-size: 13px; color: black; font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; display: block; }
     h2, h3 {font-size: larger !important;}
     h1 {font-size: 40px !important;}`
     default_arg_language="en"
>}}