Entwicklung mit Hugo

Mir wurde vor kurzen gezeigt, wie einfach Webseiten bzw. Blogs mit Hugo erstellt werden können. Daher habe ich mich nun daran gesetzt zu prüfen ob und wie ich meine Webseite und vielleicht auch meinen Blog umstellen kann und ob es Sinn macht.

Vorbereitung

Zur Entwicklung benötigt man die Hugo-Software, die man entsprechend der Dokumentation herunterladen und installieren muss.

Zum editieren der Seite verwende ich Visual Studio Code. Diesen habe ich um folgende Plugins erweitert:

  • Better TOML: Bessere Darstellung der Konfigurationsdatei
  • markdownlint: Syntax-Prüfung für die Markdown-Dateien
  • Hugo Language and Syntax Support: Syntax-Highligting in den HTML-Dateien, sowie Unterstützung mit Abkürzungen

Sonst wird nur noch der Browser seiner eigenen Wahl benötigt. Dann kann es schon losgehen mit der Erstellung der Webseite.

Hierfür habe ich mit die Guides, angefangen beim Quick Start-Guide, angeschaut. Zusätzlich sollte man sich die verlinkten Videos mit anschauen, diese finde ich, sind sehr gut gemacht und erklären das wichtigste kurz und knapp.

Auf eine Einführung in Hugo wird verzichtet, da die Dokumentation hierfür mehr als ausreichend ist. Hier wird nun explizit auf die Erstellung des Themes eingegangen.

Für Markdown gibt es von meiner Seite aus 2 gute Anlaufstellen:

Konzept

Zuerst war zu definieren, welches Theme verwendet wird. Da ich hier leider nichts für mich passendes gefunden habe, war recht schnell klar, dass ich mein eigenes Layout entwerfen muss. Hierfür gibt es 2 Möglichkeiten, entweder man entwickelt das Layout direkt im layouts-Ordner der Webseite oder man erstellt ein eigenens Theme im themes-Ordner. Ich habe mich hier für ein eigenes Theme entschieden, welches auch in einem eigenen Repository gepflegt wird.

Basis-Theme

Zuerst musste ich mich entscheiden, wie ich die ganze Seite aufbaue, und welche Grundbausteine ich verwenden möchte. Da ich in meinen letzten Web-Projekten mit Bootstrap gut zurecht gekommen bin, verwende ich diese nun als Basis. Zusätzlich verwende ich noch die feather-icons für die Symbole im Menü.

Zuerst wird nun das Theme erstellt, dafür kann der folgende Befehl verwendet werden:

hugo new theme <theme-name>

Die zusätzlichen Komponenten von Bootstrap und feather wurden unter assets in einem eigenen Ordner abgelegt, um eine Aktualisierung der Komponenten zu vereinfachen. Nebendran dann ncoh einen Ordner für die eigenen Komponenten und es kann mit der Hauptstruktur begonnen werden.

Die Hauptstruktur wird in der layouts/_default/baseof.html definiert. Den Aufbau mit den geteilten Partials hab ich so übernommen und an meine Bedürfnisse angepasst und erweitert. ZUm einen hab ich die Webseite grob in 5 Bereiche aufgeteilt:

|        Kopfzeile        |
|-------------------------|
|       Topbereich        |
|----------------|--------|
|     Inhalt     | Seiten |
|                | leiste |
|-------------------------|
|        Fußzeile         |

Für jeden der Hauptbereiche wurde ein Block definiert, damit der Inhalt dieser für jede der Ansichten ein eigener Inhalt definiert werden kann. Die Kopf und Fußzeilen werden zentrall für alle gleich gehalten und als Partials definiert worden. Zusätzlich habe ich noch weitere partials definiert, um gleiche bzw. ähnliche Bereich einmalig definieren zu können und sie an verschiedenen Stellen wiederzuverwenden, dazu gehören die Widgets, die Seitenanzeige oder die Taxonomy-Auflistungen.

Zusätzlich wurde die Darstellung für die Listenansicht in layouts/_default/list.html und die Seitenansicht in layouts/_default/single.html definiert. Da habe ich mich eher an die Standard-ansicht für die Webseite gehalten, da die Ansicht für den Blog anderes und später noch eigenständig definiert wird.

Bei den Standard-Seite habe ich darauf geschaut, dass man alle notwendigen Informationen über die Parameter in der Konfigurationsdatei übergeben kann. Dabei hab ich noch versucht darauf zu achten, die vorhandenen Standard-Parameter sinnvoll weiterzuverwenden und nur wo es notwendig war neue Parameter zu definieren. Alle vorhandenen Parameter und eine kurze Erläuterung dazu, findet man in der README.md im Theme.

Für die Syntax-Highlighting in der Webseite habe ich in der Seite mein eigenes CSS erstellen lassen und unter static/css/.._syntax.css abgelegt. Zusätzlich hab ich dies in der Configurationsdatei unter custom_css eingetragen. In meinen Fall hab ich mich für die Dracula-Version entschieden:

hugo gen chromastyle --style=dracula > static/css/dracula_styles.css

Webseite

Bei der Webseite war es erstmal relativ einfach, da diese vorher mit einem CMS erstellt wurde, konnten die meisten Seiten direkt übernommen werden. Die weiteren Seiten wie das Impressum und der Datenschutz, sowie die Informationen über mich musste ich neu eingeben, da diese nun direkt über die Konfiguration definiert werden. Aber zwei Probleme waren noch offen.

Das erste Problem war der Download des Work Scheduler. Hierbei gab es zwei Punkte zu bedenken. Der erste Punkt war, dass ich die Setups nicht direkt im Repository der Webseite mit hochladen wollte. Der zweite Punkt war, dass ich zum Download selbst noch ein paar zusätzliche Meta-Informationen benötigte. Daher hab ich mich dazu entschieden, die Informationen in einer Json-Datei im data-Ordner abzulegen. Zusätzlich habe ich noch einen Shortcode download-feed geschrieben, dem man nur den Namen der Datei übergibt und daraus den kompletten Inhalt für die Webseite erstellt.

Das zweite Problem war, dass für den Work Scheduler eine URL definiert ist, mit der die Software ermittelt, ob eine neue Version zum Download bereitsteht. Das Problem an dieser Url ist aber, dass der Zugriff auf einen Ordner stattgefunden hat und dieser als Rückgabewert einen angepassten RSS-Feed liefern musste. Dies ist nur leider so nicht direkt mit Hugo möglich. Nach längeren probieren hab ich mich nun dafür entschieden, dass ich über eine 2te Datei und einen eigenen Typ feed mir die index.xml erstellen lasse. Dafür war es zuerst notwendig ein eigenes Layout für Produkte zu definieren. Diese konnte dann aber wieder entfernt werden, als ich den Parameter hide im Theme eingebaut habe. Damit können nun komplette Seite aus dem RSS und den Listenansichten ausgeblendet werden.

Damit die index.xml nun noch richtig angesprochen wurde, musste ich in der Webserver-Konfiguration, diese Datei als Standardzugriff einstellen. Somit waren alle Daten über die gleichen Urls zugreifbar wie auf der alten Webseite.

Der Blog

Beim Blog war die Aufteilung schon etwas schwieriger, da ich hier schon einige Artikel hatte. Diese wollte ich nicht direkt in einem Unterordner ablegen und einfach durchnummerieren. Daher hab ich mich hier für die Jahres-Unterordner entschieden. Da diese auch ein Teil der URL sein sollten (so war es im alten Blog), musste entsprechen die _index.md-Datei erzeugt werden. Dadurch waren zwar die URLs wieder richtig definiert und eine übersicht über die Artikel in einem Jahr war auch möglich, aber in der Übersicht des Blogs sah man nur die Jahresordner.

Zum ersten Problem mit der Übersicht, musst erstmal ein eigenes list.html definiert werden. In diesem musste nun die Artikel für die Anzeige selbst ermittelt werden auf die Seiten verteilt werden. Hierfür benötigt man zuerst die Definition von Paginator. Dann wird dieser entsprechend durchgearbeitet. Hier ein Code-Ausschnitt:

{{ $pag := .Paginate (where .Site.RegularPages "Type" "blog").ByPublishDate.Reverse }}
{{ range $pag.Pages }}
  ...
{{ end }}

Dies führte nur dann zu einem 2ten Problem, dass die Übersicht der Jahre auch alle Artikel anzeigten. Diese Problem war etwas trickreicher zu lösen. Und zwar wurde hierfür definiert, dass die _index.md-Dateien von einem anderen Typ sein mussten (hierfür gibt es den archive-archtype). Somit konnte man den oberen Code erweitern, dass nur im Falle der Blog-Seite alle Artikel beachtet werden und sonst nur die Elemente die unterhalb liegen:

{{/* Paginator initialisieren, in der Reihenfolge die man sich im Blog wünscht */}}
{{ if eq .Type "blog" }}
  {{/* Der 1te Weg soll alle Blog-Einträge finden*/}}
  {{ $pag := .Paginate (where .Site.RegularPages "Type" "blog").ByPublishDate.Reverse }}
{{ else }}
  {{/* Der 2te Weg ist über die Archive-Gruppierung */}}
  {{ $pag := .Paginate .Pages.ByPublishDate.Reverse }}
{{ end }}
{{/* Der Paginator wurde in den If-Blöcken zwar erstellt, aber die Variable ist auserhalb nicht sichtbar, daher hier explizit holen. 
  Dieser kann nach dem ersten erstellen auch nicht mehr verändert werden */}}
{{ $pag := .Paginator }}
{{ range $pag.Pages }}
  ...
{{ end }}

Wichtig herbei ist nochmal zu erwähnen, das der Paginator nur einmal pro Seite erzeugt werden kann und dann dieser für jeden weiteren Aufruf gleich bleibt.

Zusätzlich habe ich die Darstellung beim Blog überschrieben. Bei anzeigen eines Artikels wollte ich in der Seitenleiste nicht die Information über den Autor sehen, sondern das Inhaltsverzeichnis des Artikels. Damit ist eine einfachere Navigation im Artikel möglich. Bei der Listenansicht wollte ich mehreren Sachen geändert haben. Angefangen hat es damit, dass ich mehr Information zum Artikel sehen wollte, z.B. den Autor und das Datum der Freigabe. Zusätzlich sollte in der Seitenleiste die Übersicht über die Kategorien, die Tags und die Jahresarchive einen Platz finden. Die Tags sollten dann noch in einer Schlagwortwolke dargestellt werden. Und als letztes habe ich mir noch überlegt, dass man einen Artikel als “Aufhänger” markieren können soll, damit dieser immer am Anfang dargestellt wird. Hierfür hab ich den Parameter featured in den Seiten gefügt, und es wird immer der aktuellste Artikel mit dem gesetzen Parameter angezeigt.

Bei den KnowHow Beiträgen gab es keine größeren Probleme, da hier nur ausgewählte Beitrage erstellt werden und dadurch die Anzahl überschaubar bleibt. Daher werden diese direkt im eigenen Unterordner definiert und mit einer forlaufenden Nummer gepräfixt. Nur der Style im Theme musste hier etwas angepasst werden. Beim anzeigen eines Artikels sollte das Inhaltsverzeichnis in der Seitenleiste angezeigt werden. Und bei der Übersichts wollte ich die Auflistung der skills in der Seitenleiste sehen.

Spezifischere Anpassungen

Render-Hooks

Mit den Render-Hooks kann in den Render-Prozess eingegriffen werden, und eigene Anpassungen eingebaut werden. Aktuell warte ich hierbei darauf, dass es einen Hook für Tabellen gibt, dann kann ich das Styling der Tabellen direkt darüber definieren und muss nicht mehr die Shortcodes verwenden.

Ebenso kann damit auch eigene Blöcke definiert werden. Diese würde man dann über einen codeblock mit eigenen Typ definieren. Dieser wird im Artikel dann in dieser Art angewandt (Die ` müssen noch richtig gedreht werden!):

´´´quote { linewdith="3px" linecolor="red" }
hier der Quote text
´´´

Das Layout für die Generierung liegt dann unter layouts\_default\_markup\render-codeblock-quote.html

{{ $width := .Attributes.linewidth }}
{{ $color := .Attributes.linecolor }}
<blockquote style="border-left {{ with $width }}{{. | default 3px}}{{end}} solid {{ with $color }}{{. | #ccc}}{{end}};" >
  {{ .Inner | .Page.RenderString }}
</blockquote>

Fazit

Mit der Art wie man mit Hugo arbeitet bin ich sehr zufrieden. Damit hab ich nun mehrere Probleme auf einmal behoben. Zum einen entfällt eine komplette Angriffsfläche, weil kein Script-Code mehr ausgeführt wird. Das Editieren an anderen Rechner als Zuhause, hab ich fast nie genutzt, somit wird es mir auch nicht fehlen. Und das ich nun meine Texte in Markdown direkt editieren kann find ich sehr gut. Mit dem Online-Editor von Wordpress war ich schon seit langem nicht sehr zufrieden, erst recht da einiges einfach anders formatiert wurde, als man es wollte.

Sehr wichtig dabei ist mir auch noch, dass ich nun immer eine Sicherung der Inhalte habe. Die Sicherungs-ZIP die ich jeden Tag zugeschickt bekommen habe, war nicht unbedingt verdrauenswürdig, da ich eigentlich immer einen Test-Import durchführen gemüsst hätte um zu sehen ob es funktioniert. Mit dem Weg bei Hugo bin ich mir zu 100% sicher.

Bei der Webseite würde ich das Problem mit dem Feed aktuell anders lösen. Und zwar würde ich an dieser Stelle über die eine .htaccess-Datei gehen und dort eine Umleitung auf den neuen Feed einbauen. Und in der nächsten Version dann die URL, auf die neue, umstellen.

Weitere Befehle

Anderer Typ beim erstellen

Um beim erstellen einer Daten vom Standard-Typ abzuweichen, kann dieser mit dem Parameter –kind angegeben werden. Dies wird z.B. verwendet um Index-Seiten für die Jahresordner zu definieren:

hugo new --kind archive content/blog/2023/_index.md

Hierbei ist darauf zu achten, dass der angegebene Name mit dem Dateinamen unter archetypes übereinstimmt.

Webseite sprint direkt zur Änderung

Der Hugo-Server kann so gestartet werden, dass wenn die Webseite einmal offen ist der Browser immer zur Seite mit den letzten Änderungen hinspringt. Hierfür muss der Parameter –navigateToChanged mit angegeben werden.

hugo -D server --naviagteToChanged

Ausgabe minimieren

Beim erstellen der produktiven Seiten, kann man noch etwas mehr performance aus der Seite herauslocken. Dafür sollten die erzeugten Seite so klein wie möglich sein. Dies ereicht man, in dem man beim erstellen noch den Parameter –minify mit angibt. Dabei werden alle unnötigen Leerzeichen entfernt. Ich empfehlt das nur für die Produktivumgebung, da beim testen der HTML-Code doch immer mal wieder untersucht werden muss und dies in der Minify-Version nicht sehr angenehm und langwierig ist.