×
Startseite RSS-Feed Info
Den Inhalt von Open Graph Meta-Tags selber festlegen.

Hugo - angepasste Open Graph Integration

Hugo - angepasste Open Graph Integration

Open Graph ist ein Protokoll, mit dem sich beeinflussen lässt wie eine Webseite dargestellt wird, wenn sie auf Social Media geteilt wird. Über Meta Einträge im Head jeder Webseite kann man selber beeinflussen welche Daten dafür benutzt werden. In diesem Artikel beschreibe ich meine in einem Partial zusammengefassten Open Graph Meta-Tags.

Allgemeine Informationen zu Open Graph

Ursprünglich wurde Open Graph von Facebook entwickelt. Seither wurde es aber auch von anderen Social-Media-Kanälen wie Mastodon, Twitter, Pinterest und LinkedIn übernommen.

Die meisten Inhalte werden als URL auf Facebook und anderen (Open Graph Protokoll) OGP-unterstützenden Websites geteilt. Aus diesem Grund kann es nur positiv sein, wenn die eigene Website mit Open Graph Meta-Tags ausgestattet ist. Nur so hat man die Kontrolle, welche Inhalte dort angezeigt werden.

Da ich verstehen möchte was zu einer entsprechenden Ausgabe im Sourcecode führt und was für Möglichkeiten es gibt, habe ich mir unter anderem die folgenden Webseiten angeschaut:

Hugo Open Graph Template

Hugo stellt ein Template für die Integration der Open Graph Meta-Tags zur Verfügung. Das Template wird im head HTML-Tag aufgerufen und bindet die Meta-Tags ein. Um das Template zu verwenden, genügt folgender Aufruf in der baseof.html:

{{ template "_internal/opengraph.html" . }}

Für ein besseres Verständnis was das Template so macht, gibt es eine Hugo Doku und einen Link zum Template Sourcecode:

Beitrag aktualisiert am 04.06.2023

Erst durch einen Hinweis in den - ahrefs Webmastertools - habe ich bemerkt, dass ich für paginierte Webseiten, also meine Startseite und meine Tag-Teaser-Liste, keine /page/X/ URL-Erweiterung an mein ogData.html Partial weitergebe.

Das ist komplizierter, als es auf den ersten Blick scheint. Die von Hugo verwendete Programmiersprache GO ist so konzipiert, dass Speicherbereiche effektiv geschützt werden. Vereinfacht gesagt, kann der Inhalt einer Variablen in einer nachfolgenden Abfrage oder Übergabe ungültig werden. Ich bin mit der Programmiersprache C aufgewachsen 😇, mit C-Pointern kann man sich wunderbar in den eigenen Fuß schießen. Deshalb ist es für mich bis heute manchmal unverständlich, wenn ich gegen eine GO-Wand renne.

Naja, ich finde Hugo gut und irgendwann werde ich auch die Eigenheiten von GO verstehen. Lange Rede kurzer Sinn, dem Partial muss der Seitenkontext und die komplette URL der aktuellen Webseite übergeben werden. Manchmal enthält die URL eine normale Webseite und manchmal eben eine paginierte Webseite mit der Endung /page/X/. Dem Partial ist das egal, es gibt nur die URL aus.

Zusätzlich habe ich die multilingualen Einträge in hugo.toml an die Änderungen in Hugo Version 0.112 angepasst.

Mein Partial ogData.html

Das Hugo Template ist für viele Einsatzzwecke konzipiert. Da ich nur die für meine Website angepassten Inhalte ausgeben möchte, habe ich Teile des Templates übernommen bzw. neu geschrieben. Dazu weiter unten mehr.

Ich habe das Partial ogData.html im head-Tag unter dem Quellcode für - SEO - Selbstreferenzierender hreflang auf mehrsprachiger Website - in meiner baseof.html eingebunden. So kann ich die dort gefüllte Variable $href für die Übergabe an das Partial verwenden:

{{ partial "ogData" (dict "context" . "href" $href) }}

Die Hugo-Dokumentation - Partial Templates - sagt nichts über die Übergabe mehrerer Parameter an ein Partial. Mert Bakir hat in seinem interessanten Blog einen hilfreichen Beitrag dazu - How To Pass Arguments in Hugo Partials .

Der Quellcode des Partials themes/tekki/layouts/partials/ogData.html sieht wie folgt aus:

<meta property="og:title" content="{{ if .context.IsHome }}{{ .context.Site.Params.mydomain }}{{ else }}{{ .context.Title }} &middot; {{ .context.Site.Params.mydomain }}{{ end }}">
<meta property="og:type" content="{{ if .context.IsPage }}article{{ else }}website{{ end }}">
<meta property="og:description" content="{{ if .context.Description }}{{ .context.Description }}{{ else }}{{ .context.Site.Params.description }}{{ end }}">
<meta property="og:site_name" content="{{ .context.Site.Params.mydomain }}">
<meta property="og:url" content="{{ .href }}">
{{ if eq .context.Site.Language.Lang "de" }}
  <meta property="og:locale" content="de_DE">
  <meta property="og:locale:alternate" content="en_GB">
{{ else }}
  <meta property="og:locale" content="en_GB">
  <meta property="og:locale:alternate" content="de_DE">
{{ end }}
{{ if and (.context.IsPage) (ne .context.Section "") }}<meta property="article:section" content="{{ .context.Section }}">{{ end }}
{{ $iso8601 := "2006-01-02T15:04:05-07:00" }}
{{ with .context.PublishDate }}<meta property="article:published_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>{{ end }}
{{ with .context.Lastmod }}<meta property="article:modified_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>{{ end }}
{{ $image := .context.Resources.GetMatch "featured" }}
{{ with $image }}
  <meta property="og:image" content="{{ .Permalink }}">
  <meta property="og:image:type" content="{{ .MediaType }}">
  <meta property="og:image:width" content="{{ .Width }}">
  <meta property="og:image:height" content="{{ .Height }}">
  <meta property="og:image:alt" content="{{ .Title }}">
  <meta property="og:image:secure_url" content="{{ .Permalink }}">
{{ else }}
  <meta property="og:image" content="https://tekki-tipps.de/tekki-tipps-og.png">
  <meta property="og:image:type" content="image/png">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">
    {{ if eq .context.Site.Language.Lang "de" }}
    <meta property="og:image:alt" content="Blog über Hugo, Webdesign, CSS/SCSS, SEO, Tools">
    {{ else }}
    <meta property="og:image:alt" content="Blog about Hugo, web design, CSS/SCSS, SEO, Tools">
    {{ end }}
  <meta property="og:image:secure_url" content="https://tekki-tipps.de/tekki-tipps-og.png">
{{ end }}

Zum besseren Verständnis werde ich jedes Meta-Tag einzelnd beschreiben.

meta property=“og:title”

<meta property="og:title" content="{{ if .context.IsHome }}{{ .context.Site.Params.mydomain }}{{ else }}{{ .context.Title }} &middot; {{ .context.Site.Params.mydomain }}{{ end }}">

Wenn die aktuelle Webseite die Startseite ist, wird der title aus der hugo.toml übernommen. Ansonsten, also für alle anderen Webseiten, wird der title dem Front Matter Eintrag der aktuellen Webseite entnommen. Ein Bindestrich eingefügt und der title aus der hugo.toml angefügt.

Der Kontext der Seite wird in context übergeben. Daher müssen Abfragen und Inhalte mit .context erweitert werden.

Die Einträge der hugo.toml für deutsch und englisch sehen wie folgt aus:

..
[languages]
  [languages.de]
    languageName = "Deutsch"
    weight = 1
    title = "Hugo, Webdev, SEO, Tools"
    [languages.de.params]
      description = "Blog über Hugo, Webdev, SEO, Tools"
      mydomain = "tekki-tipps.de 🇩🇪"
    ..
  [languages.en]
    languageName = "English"
    weight = 2
    title = "Hugo, Webdev, SEO, Tools"
    [languages.en.params]
      description = "Blog about Hugo, Webdev, SEO, Tools"
      mydomain = "tekki-tipps.de/en/ 🇬🇧"
    ..

Die Einträge in der hugo.toml wurden von mir nachträglich an die Hugo-Version 0.112 angepasst. Siehe dazu auch - Multilingual - Änderungen in Hugo ab Version 0.112 .

meta property=“og:type”

<meta property="og:type" content="{{ if .context.IsPage }}article{{ else }}website{{ end }}">

Auf meiner Website wird der og:type website für die Startseite, die Tag-Cloud und auf Tag-Listen angezeigt. Also auf allen Übersichtslisten. Alle anderen Webseiten erhalten den og:type article.

meta property=“og:description”

<meta property="og:description" content="{{ if .context.Description }}{{ .context.Description }}{{ else }}{{ .context.Site.Params.description }}{{ end }}">

Wenn die aktuelle Webseite im Front Matter eine description hat, wird diese durch .Description übernommen. Ansonsten wird die description der hugo.toml benutzt. <update 08.04.2023> Wenn die Description länger als 120 Zeichen ist, verstümmelt der Browser Firefox die og:description in der Version 111.0.1. Dies macht er aber auch im meta Tag Description. Google Chrome, Safari und Edge haben dies Problem nicht. Die Website ogp.me gibt keine genaue Länge an. Also ist dies ein Firefox Fehler. </update>

meta property=“og:site_name”

<meta property="og:site_name" content="{{ .context.Site.Params.mydomain }}">

Der Title aus der hugo.toml.

meta property=“og:url”

<meta property="og:url" content="{{ .href }}">

Die URL der aktuellen Webseite, einschließlich einer eventuellen Erweiterung /page/X/. Daraus ergibt sich der gesamte Änderungsaufwand.

meta property=“og:locale” und “og:locale:alternate”

{{ if eq .context.Site.Language.Lang "de" }}
  <meta property="og:locale" content="de_DE">
  <meta property="og:locale:alternate" content="en_GB">
{{ else }}
  <meta property="og:locale" content="en_GB">
  <meta property="og:locale:alternate" content="de_DE">
{{ end }}

og:locale definiert die Sprache der aktuellen Webseite. og:locale:alternate teilt mit, dass für die aktuelle Webseite eine Übersetzung in einer alternativen Sprache vorhanden ist. Da ich nur 2 Sprachen abdecke, reicht mir ein if/else.

meta property=“article:section”

{{ if and (.context.IsPage) (ne .context.Section "") }}<meta property="article:section" content="{{ .context.Section }}">{{ end }}

Wenn die aktuelle Webseite eine Page ist und die .Section kein leerer String ist, dann soll das Meta-Tag in den Head geschrieben werden. Auf meiner Website gibt es nur die Section blog. Damit bei Webseiten die keine Section haben das Meta-Tag nicht in den Head geschrieben wird, erfolgt die Abfrage ob der String in .Section leer ist.

meta property=“article:published_time” und article:modified_time

{{ $iso8601 := "2006-01-02T15:04:05-07:00" }}
{{ with .context.PublishDate }}<meta property="article:published_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>{{ end }}
{{ with .context.Lastmod }}<meta property="article:modified_time" {{ .Format $iso8601 | printf "content=%q" | safeHTMLAttr }}>{{ end }}

Die Übersichtsseiten - Startseite, Tag-Cloud und Tag-Listen - habe kein .PublishDate deshalb die Abfrage. Alle anderen Webseiten haben eventuell noch kein .Lastmod.

meta property=“og:image”

{{ $image := .context.Resources.GetMatch "featured" }}
{{ with $image }}
  <meta property="og:image" content="{{ .Permalink }}">
  <meta property="og:image:type" content="{{ .MediaType }}">
  <meta property="og:image:width" content="{{ .Width }}">
  <meta property="og:image:height" content="{{ .Height }}">
  <meta property="og:image:alt" content="{{ .Title }}">
  <meta property="og:image:secure_url" content="{{ .Permalink }}">
{{ else }}
  <meta property="og:image" content="https://tekki-tipps.de/tekki-tipps-og.png">
  <meta property="og:image:type" content="image/png">
  <meta property="og:image:width" content="1200">
  <meta property="og:image:height" content="630">
    {{ if eq .context.Site.Language.Lang "de" }}
    <meta property="og:image:alt" content="Blog über Hugo, Webdesign, CSS/SCSS, SEO, Tools">
    {{ else }}
    <meta property="og:image:alt" content="Blog about Hugo, web design, CSS/SCSS, SEO, Tools">
    {{ end }}
  <meta property="og:image:secure_url" content="https://tekki-tipps.de/tekki-tipps-og.png">
{{ end }}

Nun wird es etwas komplizierter. Deshalb muss ich etwas mehr erklären. Meine Webseiten sind multilingual in zwei Sprachen. Ich benutze PageBundles wegen der Übersichtlichkeit - alles für eine Webseite, in einem Verzeichnis. Die jeweiligen Bilder werden innerhalb des PageBundle Verzeichnis im img-Verzeichnis gespeichert. Meine Blogbeitragsseiten haben ein Artikelbild und ein als featured bezeichnetes Bild. Alle anderen Webseiten haben kein als featured bezeichnetes Bild.

Im Front Matter für diese Webseite sehen die Einträge wie folgt aus:

resources:
- name: featured
  src: img/featured.png
  title: Customized Open Graph integration
- name: article-img
  src: img/open-graph.png
  title: Customized Open Graph integration

Mit .context.Resources.GetMatch hole ich das als featured bezeichnete Bild aus dem PageBundle und speicher es in der Variablen $image. Wenn ein featured Bild vorhanden ist, gebe ich die entsprechenden Meta-Tags für dieses Bild aus.

Wenn kein featured Bild vorhanden ist, gebe ich die direkte URL für ein “Ersatzbild” an. Dieses Bild habe ich im static Verzeichnis auf der obersten Ebene gespeichert. Das “Ersatzbild” wird für Webseiten außerhalb meiner Blogbeitragsseiten verwendet.

Die Bilder sollten eine Auflösung von 1200px X 600px haben, damit auch auf hochauflösenden Bildschirmen ein gutes Bild angezeigt wird. Siehe dazu auch den oben genannten Facebook Link.

Das Open Source Social Network Mastodon benutzt die Open Graph meta Tags für die automatische Erstellung einer Link-Box. Dafür müssen natürlich auf der Zielseite entsprechende Open Graph meta Tags vorhanden sein. Für die erste in einem Toot angegebene URL wird in dieser Link-Box ein Thumbnail des og:image erstellt. Der og:title, og:site_name und die og:url werden ebenfalls benutzt. Das ganze sieht dann so aus:

Mastodon toot link-box
Mastodon Toot mit Open Graph Link-Box.

Wenn in einem Mastodon Toot ein Bild eingefügt wird, wird die Link-Box nicht erstellt. Das hat mich am Anfang total irritiert, weil ich gedacht habe das ich etwas falsch mache. Die Mastodon Dokumentation erwähnt dies mit keinem Wort.

meta property=“og:image:type”

Ich benutze Bilddateien vom Typ png und jpg. png sind von der Größe her kleiner, wenn ein großer Teil des Bildes die gleichen Farbanteile enthält. Der Typ wird durch .MediaType automatisch festgestellt.

meta property=“og:image:width”

Mit .Width wird die Breite des Bildes festgestellt.

meta property=“og:image:height”

Mit .Height wird die Höhe des Bildes festgestellt.

meta property=“og:image:alt”

Bei den Blogbeiträgen entnehme ich den alternativen Text des Bildes aus dem Resource Title. Für alle anderen Webseiten, mit dem allgemeinen Bild, speicher ich meinen alt-Text manuell. Auch hier die Abfrage nach der Sprache und da ich nur 2 Sprachen verwende ein if/else Konstrukt.

meta property=“og:image:secure_url”

Heutzutage gibt es ja kaum noch Websites die kein SSL-Zertifikat haben. Das Meta-Tag kommt noch aus einer anderen Zeit. Sollte aber angegeben werden. Auch hier übergebe ich den .Permalink oder die direkte URL des “Ersatzbildes”.

Kontrolle der Open Graph Parameter

Im head des HTML-Sourcecodes kann man die entsprechenden Meta-Tags anschauen. Da ich die Open Graph Meta-Tags erst im nachhinein erstellt habe, ist es sehr aufwändig dies für jeden Beitrag und jeder Sprache zu kontrollieren.

Auch dafür gibt es eine Lösung - Browser Extensions. Für Safari habe ich gar keine Extension gefunden. Für Edge gibt eine Textlösung, aber der relevante Teil muss gescrollt werden. Da kann man sich dann auch den HTML-Sourcecode anschauen.

Für Firefox und Chrome gibt es entsprechende Extension. Das für Firefox finde ich hübscher und setze es auch ein:

Open Graph blog post extension
Firefox Browser Extension - Anzeige für diesen Blogbeitrag.
Open Graph website extension
Firefox Browser Extension - Anzeige für alle anderen Webseiten außer Blogbeiträgen.

Fazit

Ich habe keinen Facebook Account und kann deshalb die Weitergabe von Links innerhalb von Facebook nicht kontrollieren. Die Weitergabe meiner Kontaktadressen ist mir zu wertvoll um sie gezwungenermaßen mit Facebook zu teilen. Andere sehen dies anders und für diese Menschen habe ich die OG Meta-Tags auch integriert. Seit kurzem bin ich auf - Mastodon - und nutze dort die Open Graph Meta-Tags selber.

Linkliste zu diesem Beitrag

Update:  |
11 Minuten Lesezeit
0
Dieser Beitrag wurde mit der Hugo-Version 0.115.2 erstellt.

Kommentare werden bei deutscher Spracheinstellung nicht in der englischen Variante der Webseite angezeigt und umgekehrt.

© 2023 - Frank Kunert  -  Ich über mich