Skip to main content

Eterno Devir


Lazy loading YouTube embeds

Since I'm adding many YouTube videos to the new Web Gems section, I decided to lazy load all YouTube embeds on the website. The concept is straightforward: instead of loading the full YouTube player for every video on a page, only the video's thumbnail and a small bit of CSS to mimic the player interface are loaded. The actual player is only requested and loaded if the reader clicks "Play". This makes pages load significantly faster and saves bandwidth. You never know if the person reading lives on a sailboat where every bit of bandwidth matters.

To achieve this, I turned to paulirish/lite-youtube-embed. It is a small JavaScript file that handles the thumbnail display and the iframe swap on play, paired with the necessary CSS for the player replica. On top of its simplicity, lite-youtube-embed uses youtube-nocookie.com by default, so no extra privacy-friendly changes were necessary. Beyond curl -O -L these two files, it was just a matter of writing a Hugo shortcode and figuring out a workaround to support playlists.

YouTube videos provide different thumbnail qualities, ranging from default to maxres. I defined maxres as default value for poster-quality, as it looks better and makes the shortcode usage a bit more ergonomic. However, a given video may not provide maxres, which requires finding the highest quality available.

For videos, the final shortcode syntax looks like this:

{{<
    lite-youtube
    src="https://www.youtube.com/watch?v=SrKj4hYic5A"
    poster-quality="sd"
>}}

For playlists, where the workaround was required, it looks like this:

{{<
    lite-youtube
    src="https://www.youtube.com/playlist?list=PLdJRJcZwR07E0CsTwSAPK-4GdQyVLvyVN"
    poster-id="eY-eyZuW_Uk"
>}}

Even though the lite-yt-embed.js and lite-yt-embed.css files are tiny (3.63 kB and 1.88 kB), it would be a waste to load them on pages without videos. So I wrote a small partial to conditionally include these two resources only when needed. That is, only pages or sections using the lite-youtube shortcode will load them.

<!-- layouts/_default/baseof.html -->
<head>
...
{{ $hasYoutube := partial "conditional-youtube.html" . }}
{{ if $hasYoutube }}
  {{ $ytCSS := resources.Get "lite-yt-embed.css" | minify | fingerprint }}
  {{ $ytJS := resources.Get "lite-yt-embed.js" | minify | fingerprint }}
  <link rel="stylesheet" href="{{ $ytCSS.RelPermalink }}" integrity="{{ $ytCSS.Data.Integrity }}" >
  <script src="{{ $ytJS.RelPermalink }}" defer integrity="{{ $ytJS.Data.Integrity }}"></script>
{{ end }}
...
</head>
<!-- layouts/partials/conditional-youtube.html -->
{{ $hasYoutube := false }}
{{ $listTypes := slice "logs" "web-gems" }}

{{ if .IsPage }}
  {{ $hasYoutube = .HasShortcode "lite-youtube" }}
{{ else if in $listTypes .Type }}
  {{ range .Paginator.Pages }}
    {{ if .HasShortcode "lite-youtube" }}
      {{ $hasYoutube = true }}
      {{ break }}
    {{ end }}
  {{ end }}
{{ end }}

{{ return $hasYoutube }}

Finally, the shortcode itself. As I mentioned, playlists required a small workaround. Since lite-youtube-embed expects a videoid, I manually provide one via poster-id for the thumbnail, while the shortcode handles passing the playlist ID as a parameter to the iframe.

<!-- layouts/shortcodes/lite-youtube.html -->
{{ $src := .Get "src" }}
{{ $title := .Get "title" }}
{{ $posterQuality := .Get "poster-quality" | default "maxres" }}
{{ $customParams := .Get "params" }}

{{ $url := urls.Parse $src }}
{{ $v := $url.Query.Get "v" }}
{{ $list := $url.Query.Get "list" }}

{{ if not (or $v $list) }}
  {{ erroridf "lite-youtube-invalid-url" "%s: Invalid YouTube URL: %s" .Position $src }}
{{ end }}

{{ $posterId := .Get "poster-id" | default $v }}
{{ $posterUrl := printf "https://i.ytimg.com/vi/%s/%sdefault.jpg" $posterId $posterQuality }}

{{ $queryParams := dict }}
{{ with $v }}{{ $queryParams = merge $queryParams (dict "v" .) }}{{ end }}
{{ with $list }}{{ $queryParams = merge $queryParams (dict "list" .) }}{{ end }}

{{ $path := cond $list "playlist" "watch" }}
{{ $query := querify $queryParams }}
{{ $href := printf "https://www.youtube.com/%s?%s" $path $query }}

{{ $params := slice }}
{{ with $list }}{{ $params = $params | append (printf "list=%s" .) }}{{ end }}
{{ with $customParams }}{{ $params = $params | append . }}{{ end }}
{{ $params = delimit $params "&" }}

<div class="responsive-embed ratio-16-9">
  <lite-youtube
    videoid="{{ $v }}"
    title="{{ $title }}"
    style="background-image: url('{{ $posterUrl }}');"
    {{- with $params }}params="{{ . }}"{{- end }}>
    <a href="{{ $href }}"
       class="lyt-playbtn"
       title="{{ i18n "play" }} YouTube"
       aria-label="{{ i18n "play" }} YouTube {{ $title }}">
    </a>
  </lite-youtube>
</div>

{{ partial "nojs.html" $src }}

∗ ∗ ∗

logs   computing  

Comment by email ↪

Back to top ↑