├Ś
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