A few Hugo Shortcodes

, 12 minutes to read, 1.4k views

After having seen Tom Scott’s video about taking a break, I wanted to write a bit about how awesome Tom Scott and his videos are. You can find this blog post here. Incidentally, this also pushed the previous week’s post a week further behind. In fact, before I even had written about the technical bits on this blog, I had to go into the code again and add two shortcodes1. Anyway, I needed to be able to embed some third-party content on my blog, specifically a Mastodon post and some Youtube Videos. Here is the story of how I approached this.

Embed third-party content, the privacy-first way

In the connected web, you’ll often want to link to another website, another resource some place else. Typically, this is done through a link or something similar, where a user can click on it and will then be shown another website. This is a great way to link from one place to another, but it fails if you’d like to provide some context to a certain resource that is online. Most media have traditionally done this by embedding foreign content on their website. An example of this is an article by The Verge embedding a Tweet, or another article by The Guardian that is embedding a YouTube video. This is convenient for the newspapers, as both of them provide a code to embed the content on their website and also allows, for the most part, to keep people on the newspaper’s page (which is always good, as newspaper’s can show readers advertising if they stay on the page) while they are watching or reading something that is important context to the story that is written.

No, of course, this means that there is third-party content on the website, which I’m generally not a big fan of. Especially since these are mostly iframes, which load automatically. This means if I embed a video or a post, that content from a different domain is loaded automatically. Means you are transmitting your IP address to a different party than the website you have opened. While some people might think this is okay, for me, this violates the consent of the website’s users, as the user only agreed to open up that site, not automatically a different one as well.

Now, in the case of YouTube and other video sharing websites, such as Vimeo, this is even handled quite fine. In the example of Vimeo, the code to embed looks like this:

<iframe src="https://player.vimeo.com/video/877473729?h=255330332d" width="640" height="384" frameborder="0" allow="autoplay; fullscreen; picture-in-picture" allowfullscreen></iframe>
<p>
    <a href="https://vimeo.com/877473729">The Sidewalk Artist</a> from <a href="https://vimeo.com/watchale">WATCHALE</a> on <a href="https://vimeo.com">Vimeo</a>.
</p>

While I’m personally not a big fan of directly loading the embedded third-party content, at least this is okay in that it presents a watchable video, that is in a special area of the website, that is separated from the rest. But if we have a look at the way that Twitter recommends embedding a tweet, it’s even worse2:

<blockquote class="twitter-tweet">
    <p lang="en" dir="ltr">just setting up my twttr</p>&mdash; jack (@jack) <a href="https://twitter.com/jack/status/20?ref_src=twsrc%5Etfw">March 21, 2006</a>
</blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script> 

Why would anyone in their right mind load a random JavaScript file from a source you don’t control, not even using subresource integrity3? This is just asking for trouble. Change the js file provided at that URL and you can suddenly wreak havoc on every website with this embed. This does not seem like a good idea, especially for a company that is potentially going bankrupt and might be bought by just any highest bidder.

There is one really great aspect about this embedding stuff, though, and that is that many services support a standard called oEmbed. Pages with oEmbed support have a bit of an API to provide info about a specific resource that gives you a title and often an image as well. This can, of course, be used to generate a preview.

Introducing the YouTube shortcode

To display an image and link to the image, I used exactly this oEmbed standard to fetch some data from the video and then display it. So without further ado, a great video:

The shortcode is actually basic, and it is shown below:

{{ $id := .Get "id" }}
{{ $ytURL := printf "https://www.youtube.com/watch?v=%s" $id }}
{{ $urlToGet := printf "https://youtube.com/oembed?url=%s" $ytURL }}
{{ $imgWidth := 656 }}

{{- with resources.GetRemote $urlToGet  -}}
    {{ if (resources.GetRemote $urlToGet).Err }}
        <blockquote class="toot">
            <p class="ctr legal">[Source not online<br />
            at time of site build.]</p>
        </blockquote>
    {{ else }}
        {{ $json := unmarshal .Content }}
        <blockquote class="youtube not-prose">
            <div class="thumbnail">
                <picture>
                    {{ with $thumbnail := (resources.GetRemote $json.thumbnail_url).Fill "480x270 Center" }}
                    {{ with resources.GetRemote (replace $json.thumbnail_url "/hqdefault." "/maxresdefault." ) }}
                        {{ $thumbnail = . }}
                    {{ end }}
                    <source 
                        srcset="
                            {{- range $.Site.Config.pixeldensities -}}
                            {{ ( ( printf "%dx webp" (int (mul . $imgWidth)) ) | $thumbnail.Resize ).RelPermalink }} {{ . }}x,
                            {{- end -}}"
                        type="image/webp"
                    />
                    <source 
                        srcset="
                            {{- range $.Site.Config.pixeldensities -}}
                            {{ ( ( printf "%dx" (int (mul . $imgWidth)) ) | $thumbnail.Resize ).RelPermalink }} {{ . }}x,
                            {{- end -}}"
                        type="{{ .MediaType.Type }}"
                    />
                    {{ with (printf "%dx" $imgWidth | $thumbnail.Resize) }}
                    <img src="{{ .RelPermalink }}" alt="" width="{{ .Width }}" height="{{ .Height }}">
                    {{ end }} 
                    {{ end }}                             
                </picture>
                <div class="meta">
                    <a class="title" href="{{ $ytURL }}" target="_blank">
                        {{ $json.title }}
                    </a>
                    <a class="author" href="{{ $json.author_url }}" target="_blank">
                        {{ $json.author_name }}
                    </a>
                </div>
                <a href="{{ $ytURL }}" target="_blank">
                    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor" class="play">
                        <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm14.024-.983a1.125 1.125 0 0 1 0 1.966l-5.603 3.113A1.125 1.125 0 0 1 9 15.113V8.887c0-.857.921-1.4 1.671-.983l5.603 3.113Z" clip-rule="evenodd" />
                    </svg>
                </a>
                {{ $youtubeIcon := resources.Get "icons/youtube-white.svg" }}
                    <img src={{ ($youtubeIcon | fingerprint).RelPermalink }} class="icon" />
            </div>
        </blockquote>
    {{ end }}
{{ end }}

Let walk through it step by step:

  1. Provide the shortcode with the ID of the video.
  2. Build the URL from the video ID.
  3. build the oEmbed URL from the video URL.
  4. Load the data and unmarshall the json.
  5. Build an optimised image in many sizes, including a WebP version and different sizes of the original image.
  6. Display the title, as well as the author’s name.
  7. Add a play symbol and a youtube logo.
  8. Finally, arrange everything and make the play button link to the video.

Solving it this way means that just like everything else on this website, the YouTube “embed” (in heavy quotation marks) is statically built at compile time and if the user’s open’s it up, nothing is served from YouTube’s server, but everything is served from the generated code and available to the user. Fast, pretty and very much privacy preserving.

What about Mastodon

As you can see at the top of the post, I have also embedded a Mastodon post. Since Mastodon is an open system, it has a publicly accessible, easy to use API that can be used to fetch the data. While I would trust Mastodon developers enough to embed their iframe (of course, no matter the trust, I would not embed a script tag) I still prefer to embed it completely statically.

Hello world

It is that simple. Well, unless it isn’t. The main difference is, that instead of YouTube, where we just have plenty of different things that can be embedded in a toot.

As such, my approach was the following. I started off by using the Shortcode provided by Bryce Wray. I adapted it for my usage. as such, I ensured that no 3rd party content would get loaded on my site and everything is highly optimised. I finally also added some style from Tailwind CSS to make it look nice. I also added the YouTube hack to try to get higher quality thumbnail images, as I was using the embed primarily for my Tom Scott article. You can see the code below:

{{ $masIns := .Get "instance" }}
{{ $id := .Get "id" }}
{{ $tootLink := "" }}
{{ $card := "" }}
{{ $handleInst := "" }}
{{ $mediaMD5 := "" }}
{{ $imageCount := 0 }}
{{ $votesCount := 0 }}
{{ $urlToGet := print "https://" $masIns "/api/v1/statuses/" $id }}

{{ $avatarSize := 48 }}
{{ $imgWidth := 624 }}

{{- with resources.GetRemote $urlToGet  -}}
    {{ if (resources.GetRemote $urlToGet).Err }}
        <blockquote class="toot">
            <p class="ctr legal">[Source not online<br />
            at time of site build.]</p>
        </blockquote>
    {{ else }}
        {{ $json := unmarshal .Content }}

        {{ if isset $json "account" }}
            {{ $tootLink = $json.url }}
            {{ $handleInst = print "@" $json.account.acct "@" $masIns }}
        {{ end }}

        {{ if isset $json "content" }}
            <blockquote class="toot not-prose" cite="{{ $tootLink }}">
                <div class="header">
                    {{ $avatar := resources.GetRemote $json.account.avatar }}
                    <a class="profile" href="{{ $json.account.url }}" rel="noopener">
                        <picture>
                            <source 
                                srcset="
                                    {{- range $.Site.Params.pixeldensities -}}
                                    {{- if le (int (mul . $avatarSize)) ($avatar.Width) -}}
                                    {{ ( ( printf "%dx webp" (int (mul . $avatarSize)) ) | $avatar.Resize ).RelPermalink }} {{ . }}x,
                                    {{- end -}}
                                    {{- end -}}"
                                type="image/webp"
                            />
                            <source 
                                srcset="
                                    {{- range $.Site.Params.pixeldensities -}}
                                    {{- if le (int (mul . $avatarSize)) ($avatar.Width) -}}
                                    {{ ( ( printf "%dx" (int (mul . $avatarSize)) ) | $avatar.Resize ).RelPermalink }} {{ . }}x,
                                    {{- end -}}
                                    {{- end -}}"
                                type="{{ .MediaType.Type }}"
                            />
                            {{ with (printf "%dx" $avatarSize | $avatar.Resize) }}
                            <img src="{{ .RelPermalink }}" alt="Mastodon avatar for {{ $handleInst }}" width="{{ .Width }}" height="{{ .Height }}">
                            {{ end }}
                        </picture>
                    </a>
                    <div class="author">
                        <a class="name" href="https://{{ $masIns }}/@{{ $json.account.acct }}" rel="noopener">{{ $json.account.display_name }}</a>
                        <a class="handle" href="https://{{ $masIns }}/@{{ $json.account.acct }}" rel="noopener">{{ $handleInst }}</a>
                    </div>
                    {{ $mastodonIcon := resources.Get "icons/mastodon-white.svg" }}
                    <img src={{ ($mastodonIcon | fingerprint).RelPermalink }} class="icon" />
                </div>
                <div class="content">
                    {{ $json.content | safeHTML }}
                </div>
                {{ with $json.card }}
                    {{- $cardData := . -}}
                    {{ if eq $cardData.type "video" }}
                    <a href="{{ $cardData.url }}" rel="noopener" target="_blank">
                        <div class="card">
                            {{ $tootCardImage := resources.GetRemote $cardData.image }}
                            {{ if strings.Contains $cardData.provider_url "youtube.com" }}
                            {{ $ytOembed :=  (printf "https://www.youtube.com/oembed?url=%s&format=json" $cardData.url ) | getJSON }}
                            {{ $tootCardImage = resources.GetRemote (replace $ytOembed.thumbnail_url "/hqdefault." "/maxresdefault." ) }}
                            {{ end }}
                            <div class="thumbnail">
                                <picture class="card-image">
                                    <source 
                                        srcset="
                                            {{- range $.Site.Params.pixeldensities -}}
                                            {{- if le (int (mul . $imgWidth)) ($tootCardImage.Width) -}}
                                            {{ ( ( printf "%dx webp" (int (mul . $imgWidth)) ) | $tootCardImage.Resize ).RelPermalink }} {{ . }}x,
                                            {{- end -}}
                                            {{- end -}}"
                                        type="image/webp"
                                    />
                                    <source 
                                        srcset="
                                            {{- range $.Site.Params.pixeldensities -}}
                                            {{- if le (int (mul . $imgWidth)) ($tootCardImage.Width) -}}
                                            {{ ( ( printf "%dx" (int (mul . $imgWidth)) ) | $tootCardImage.Resize ).RelPermalink }} {{ . }}x,
                                            {{- end -}}
                                            {{- end -}}"
                                        type="{{ .MediaType.Type }}"
                                    />
                                    {{ with (printf "%dx" $imgWidth | $tootCardImage.Resize) }}
                                    <img src="{{ .RelPermalink }}" alt="" width="{{ .Width }}" height="{{ .Height }}">
                                    {{ end }}                               
                                </picture>
                                <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
                                    <path fill-rule="evenodd" d="M2.25 12c0-5.385 4.365-9.75 9.75-9.75s9.75 4.365 9.75 9.75-4.365 9.75-9.75 9.75S2.25 17.385 2.25 12Zm14.024-.983a1.125 1.125 0 0 1 0 1.966l-5.603 3.113A1.125 1.125 0 0 1 9 15.113V8.887c0-.857.921-1.4 1.671-.983l5.603 3.113Z" clip-rule="evenodd" />
                                </svg>
                            </div>
                            <div class="content">
                                <span class="provider">{{ $cardData.provider_name }}</span>
                                <span class="title">{{ $cardData.title }}</span>
                                <span class="author">By {{ $cardData.author_name }}</span>
                            </div>
                        </div>
                    </a>
                    {{ else if eq $cardData.type "link" }}
                    <a href="{{ $cardData.url }}" rel="noopener" target="_blank">
                        <div class="card">
                            {{ $tootCardImage := resources.GetRemote $cardData.image }}
                            <div class="image">
                                <picture class="card-image">
                                    <source 
                                        srcset="
                                            {{- range $.Site.Params.pixeldensities -}}
                                            {{- if le (int (mul . $imgWidth)) ($tootCardImage.Width) -}}
                                            {{ ( ( printf "%dx webp" (int (mul . $imgWidth)) ) | $tootCardImage.Resize ).RelPermalink }} {{ . }}x,
                                            {{- end -}}
                                            {{- end -}}"
                                        type="image/webp"
                                    />
                                    <source 
                                        srcset="
                                            {{- range $.Site.Params.pixeldensities -}}
                                            {{- if le (int (mul . $imgWidth)) ($tootCardImage.Width) -}}
                                            {{ ( ( printf "%dx" (int (mul . $imgWidth)) ) | $tootCardImage.Resize ).RelPermalink }} {{ . }}x,
                                            {{- end -}}
                                            {{- end -}}"
                                        type="{{ .MediaType.Type }}"
                                    />
                                    {{ with (printf "%dx" $imgWidth | $tootCardImage.Resize) }}
                                    <img src="{{ .RelPermalink }}" alt="" width="{{ .Width }}" height="{{ .Height }}">
                                    {{ end }}                               
                                </picture>
                            </div>
                            <div class="content">
                                {{ with $cardData.published_at }}<time class="published">{{ time.AsTime . | dateFormat ":date_medium" }}</time>{{ end }}
                                {{ with $cardData.provider_name }}<span class="provider">{{ . }}</span>{{ end }}
                                {{ with $cardData.title }}<span class="title">{{ . }}</span>{{ end }}
                                {{ with $cardData.author_name }}<span class="author">By {{ . }}</span>{{ end }}
                            </div>
                        </div>
                    </a>
                    {{ end }}
                {{ end }}
                <div class="footer">
                    {{ $date := time.AsTime $json.created_at }}
                    {{ $timestamp := $date | time.Format "2006-01-02T15:04:05-07:00" }}
                    {{ $dateHuman := $date | time.Format ":date_medium" }}
                    {{ $timeHuman := $date | time.Format "15:04" }}
                    <a href="{{ $tootLink }}" class="toot-date" rel="noopener"><time datetime="{{ $timestamp }}">{{ $dateHuman }}&nbsp;{{ $timeHuman }}</time></a>
                </div>
            </blockquote>
        {{ end }}
    {{ end }}
{{- end -}}

As you can see, I don’t yet support images, but I do support different type of open graph approaches. I decided that I will address the stuff that is lacking when I need it. In fact, if I don’t need the shortcode with images, I will not necessarily need it4.

I don't always share my blogposts here, but today I will. I made my own custom @gohugoio shortcode to embed Mastodon posts.

oli.fyi/2024/a-few-hugo-shortc

A few Hugo Shortcodes By Oliver Kamer

Postscript: Cloudflare Pages

As I mentioned in the previous post I’m using Cloudflare Pages to host this blog. While Cloudflare Pages is really spectacular and really fast, for some reason caching has not yet been enabled for Hugo projects. As such, it will need to download any and all images for the thumbnail and, for example, the profile image for the mastodon post and then also recalculate the different versions of said images. This doesn’t appear to be very practical or efficient to me. Now it doesn’t really matter, since with Cloudflare Pages, you get a certain number of page builds, not build minutes, but still this is not that efficient. Currently, a build for this blog takes about 5 minutes, which I assume for the most part is getting the remote images and calculating all the different sizes. Building the site locally, where I have all the images and resources cached (and potentially a faster machine than what is used to build it on Cloudflare) a full-page build only takes a bit more than a second, so that cache would really speed things up and reduce resource consumption on Cloudflare’s part.


  1. If you don’t know what shortcodes are and don’t know what I mean by Hugo, please note that this blog post assumes at least a bit of familiarity with Hugo. If you are not familiar with Hugo, I recommend becoming familiar with it, it is a really fantastic tool. ↩︎

  2. I know it’s easy to keep pointing the finger at Twitter for doing something stupid, mostly because they are doing lots of stupid stuff. But honestly, they are (were?) a big company, and they should know better. ↩︎

  3. Subresource integrity is a pretty awesome way to ensure that a script cannot be changed by a third party, ensuring user’s only run JavaScript code on your website that you have seen (and therefore approved) before. ↩︎

  4. Something in my brain really wants to implement the polls feature, as I think this would be a fascinating design challenge to do this properly. Maybe I will do it at some point, who knows. ↩︎

Tags: Blog, Hugo, Mastodon, OEmbed, SCSS, Shortcode, Style, Tailwind CSS, Technical, YouTube