Nachdem ich vor ein paar Tagen mein neues Wiki online stellte und komplett zufrieden war, schaute ich auch mal auf der Website pagespeed.web.dev nach, wie viele Punkte das Wiki bekommt.
Schock: Es waren nur 52 Punkte (mobile Ansicht) und 71 Punkte (Desktop). Das ist weit weniger, als bei meinem alten Wiki mit 74 Punkten (mobil) und 98 Punkten (Desktop).
Da das verwendete Theme mit dem Namen “Docsy” von Google stammt (das habe ich erst nach der Entscheidung dafür bemerkt), nahm ich an, dass auch die Punktezahl bei dem Test entsprechend hoch sein sollte.
Auch die Website des Themes erreicht dort nur 48 Punkte (mobil) und immerhin 96 Punkte (Desktop).
Unschön gemachte Offlinesuche
Dann haben wir (Vri und ich) viel ausprobiert und haben herausgefunden, dass es hauptsächlich an der verwendeten Offlinesuche des Themes liegt. Schaltet man diese ab, dann steigt das Ergebnis auf 89 Punkte (mobile Ansicht) und 100 Punkte (Desktop).
Der Grund: Die Index-Datei für die Offlinesuche wird bei jedem Seitenaufruf automatisch abgerufen und ausgewertet. Bei einer ungepackten Größe von ca. 800 KiB der json-Datei ist das nicht unerheblich.
Unschönes CSS
Aber auch ein “Preload” eines CSS ist nicht gerade schön gemacht. Dieses bewirkt, dass es relativ lange dauert, bevor überhaupt Inhalte im Browser angezeigt werden.
Was nun?
Mir ist grundsätzlich egal, wie viele Menschen auf meinen Webseiten gucken und ich habe deshalb dazu auch keine Auswertungen. Mir ist jedoch wichtig, dass man die Inhalte findet, wenn man danach sucht. Und meines Wissens verweisen Suchmaschinen weniger gerne auf Webseiten, die relativ langsam sind. (Stimmt dieses alte Halbwissen überhaupt noch?)
Abgesehen davon soll das neue Wiki auch auf älterer Hardware (Desktop und Smartphone) und langsamen Verbindungen gut funktionieren.
Optimierungen
CSS ohne Preload
Um das Preload aus dem CSS herauszubekommen, kopiert man die Datei themes/docsy/layouts/partials/head-css.html
nach layouts/partials/head-css.html
und ändert die unteren Zeilen von:
<link rel="preload" href="{{ $css.RelPermalink }}" as="style">
<link href="{{ $css.RelPermalink }}" rel="stylesheet" integrity="{{ $css.Data.integrity }}">
Nach:
<link href="{{ $css.RelPermalink }}" rel="stylesheet">
<link href="{{ $css.RelPermalink }}" rel="stylesheet" integrity="{{ $css.Data.integrity }}">
integrity=
gelöscht.Suche nur auf bestimmten Seiten
Eine Möglichkeit ist es, die eingebaute Offlinesuche des Themes nicht mehr auf allen Seiten im Wiki anzuzeigen, sondern nur auf einer eigenen Suchseite.
Ich bin kein Profi bei sowas, habe mir den Quelltext des Themes angeschaut und keine Stelle gefunden, das zu tun und diese Möglichkeit daher verworfen. Aber schlaue Menschen können hier bestimmt mehr und könnten das für sich so umsetzen. Daher sei diese Möglichkeit genannt.
Andere Implementierung für die Suche
Eine andere Möglichkeit ist, die eingebaute Suchfunktion zu deaktivieren und selbst eine zu implementieren. Ich habe mich für die Methode entschieden, die in diesem Gist beschrieben wird. (In der Hugo-Dokumentation wird u. a. darauf verwiesen.
Der Code im Gist steht unter der MIT-Lizenz zur Verfügung, siehe hier.
Zu erstellende Dateien und Verzeichnisse
In die Datei
config.toml
:[outputs] home = [ "HTML", "JSON" ]
In die Datei
content/de/search/_index.md
--- title: "Suche" sitemap: priority : 0.1 layout: "search" weight: 20 menu: main: weight: 50 ---
In die Datei
layouts/_default/index.json
:{{- $.Scratch.Add "index" slice -}} {{- range .Site.RegularPages -}} {{- $.Scratch.Add "index" (dict "title" .Title "tags" .Params.tags "categories" .Params.categories "contents" .Plain "permalink" .Permalink) -}} {{- end -}} {{- $.Scratch.Get "index" | jsonify -}}
Beim nächsten Bau der Website wird die Datei /index.json
automatisch erstellt.
In die Datei
layouts/_default/search.html
(abgeändert vom Original, da es nicht funktioniert hat):{{ define "main" }} <script src="/js/offsearch/jquery.min.js"></script> <script src="/js/offsearch/fuse.min.js"></script> <script src="/js/offsearch/jquery.mark.min.js"></script> <script src="/js/offsearch/offsearch.js"></script> <section class="offsearch"> <noscript><p id="offsearch-noscript">Diese Suchfunktion benötigt JavaScript.</p></noscript> <div class="offsearch" > <form action="{{ "search" | absURL }}"> <input id="search-query" name="s" placeholder="Wiki durchsuchen…" aria-label="Search" /> </form> <div id="search-results"></div> </div> </section> <!-- this template is sucked in by search.js and appended to the search-results div above. So editing here will adjust style --> <script id="search-result-template" type="text/x-js-template"> <div id="summary-${key}"> <h4><a href="${link}">${title}</a></h4> <p>${snippet}</p> ${ isset tags }<p>Tags: ${tags}</p>${ end } ${ isset categories }<p>Categories: ${categories}</p>${ end } </div> </script> {{ end }}
Ich habe das Verzeichnis
static/js/offsearch/
erstellt, wo später die notwendigen JavaScript-Dateien bereitgestellt werden.JavaScript selbst hosten: Ich habe die in der Originaldatei
search.html
eingebundenen Dateien ins Verzeichnisstatic/js/offsearch/
heruntergeladen, diese entsprechend lokal eingebunden und hoste sie nun selbst.JavaScript für die Suchfunktion Diese Datei kommt nach
static/js/offsearch/offsearch.js
:summaryInclude=60; var fuseOptions = { shouldSort: true, includeMatches: true, threshold: 0.0, tokenize:true, location: 0, distance: 100, maxPatternLength: 32, minMatchCharLength: 1, keys: [ {name:"title",weight:0.8}, {name:"contents",weight:0.5}, {name:"tags",weight:0.3}, {name:"categories",weight:0.3} ] }; var searchQuery = param("s"); if(searchQuery){ $("#search-query").val(searchQuery); executeSearch(searchQuery); }else { $('#search-results').append("<p>Please enter a word or phrase above</p>"); } function executeSearch(searchQuery){ $.getJSON( "/index.json", function( data ) { var pages = data; var fuse = new Fuse(pages, fuseOptions); var result = fuse.search(searchQuery); console.log({"matches":result}); if(result.length > 0){ populateResults(result); }else{ $('#search-results').append("<p>No matches found</p>"); } }); } function populateResults(result){ $.each(result,function(key,value){ var contents= value.item.contents; var snippet = ""; var snippetHighlights=[]; var tags =[]; if( fuseOptions.tokenize ){ snippetHighlights.push(searchQuery); }else{ $.each(value.matches,function(matchKey,mvalue){ if(mvalue.key == "tags" || mvalue.key == "categories" ){ snippetHighlights.push(mvalue.value); }else if(mvalue.key == "contents"){ start = mvalue.indices[0][0]-summaryInclude>0?mvalue.indices[0][0]-summaryInclude:0; end = mvalue.indices[0][1]+summaryInclude<contents.length?mvalue.indices[0][1]+summaryInclude:contents.length; snippet += contents.substring(start,end); snippetHighlights.push(mvalue.value.substring(mvalue.indices[0][0],mvalue.indices[0][1]-mvalue.indices[0][0]+1)); } }); } if(snippet.length<1){ snippet += contents.substring(0,summaryInclude*2); } //pull template from hugo templarte definition var templateDefinition = $('#search-result-template').html(); //replace values var output = render(templateDefinition,{key:key,title:value.item.title,link:value.item.permalink,tags:value.item.tags,categories:value.item.categories,snippet:snippet}); $('#search-results').append(output); $.each(snippetHighlights,function(snipkey,snipvalue){ $("#summary-"+key).mark(snipvalue); }); }); } function param(name) { return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' '); } function render(templateString, data) { var conditionalMatches,conditionalPattern,copy; conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g; //since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop copy = templateString; while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) { if(data[conditionalMatches[1]]){ //valid key, remove conditionals, leave contents. copy = copy.replace(conditionalMatches[0],conditionalMatches[2]); }else{ //not valid, remove entire section copy = copy.replace(conditionalMatches[0],''); } } templateString = copy; //now any conditionals removed we can do simple substitution var key, find, re; for (key in data) { find = '\\$\\{\\s*' + key + '\\s*\\}'; re = new RegExp(find, 'g'); templateString = templateString.replace(re, data[key]); } return templateString; } /* MIT License Copyright 2022 Edward A. Webbinaro Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
Noch etwas CSS für die neue Suchfunktion
Dieses CSS kommt in die Datei assets/css/_styles_project.scss
:
.offsearch form {margin-bottom: 40px;}
.offsearch #search-query {width: 100%; padding: 10px;}
.offsearch #search-results p {hyphens: auto;}
Ergebnis – Suchfunktion auf einer einzelnen Seite
Die Suche findet man jetzt unter wiki.natenom.com/search/ und diese Seite ist immer rechts oben verlinkt.
Für die Zukunft ist geplant, diese als Suchfeld in der Navigation einzufügen.
Ich bin sehr zufrieden mit der neuen Suche. Im Vergleich zu der Suche im Blog ist sie extrem schnell und zeigt die Ergebnisse auch schön an.
Deshalb werde ich diese Suche in den nächsten Tagen vermutlich auch in meinem Blog einbauen. Dort dauert die Suche aktuell sehr lange und verursacht sehr viel CPU-Last, obwohl dort als auch im neuen Wiki fuse.js genutzt wird.
Bei der Punktezahl erreicht das Wiki jetzt in 89 Punkte (mobil) (statt zuvor 52) und 100 Punkte (Desktop) (statt zuvor 71).
Sidebar
Ursprünglich hatte ich die Sidebar links (Seitenverzeichnis) so eingestellt, dass man jeden Bereich einzeln aufklappen/zuklappen konnte. Dadurch wurden aber im fertigen HTML ungefähr 2000 Zeilen nur für die Sidebar eingefügt. Übersichtlicher wurde es dadurch aber nicht.
Deshalb habe ich die Sidebar jetzt so eingestellt, dass aufklappen/einklappen nicht mehr möglich ist. Es ist immer nur der aktuelle Bereich geöffnet.
Hier gibt es vom Theme mehrere Einstellungsmöglichkeiten im Bereich params
in der Datei config.toml
:
ui.ul_show
– Gibt an, bis zu welcher Anzahl die Unterebenen des aktuellen Bereichs geöffnet dargestellt werden.- Hier mit
ui.ul_show = 1
- Hier mit
ui.ul_show = 2
- Hier mit
sidebar_menu_compact
– Man kann auch alles immer offen einstellen. Aber das eignet sich wirklich nur für sehr kleine Websites:
Auch auf die generierten HTML-Dateien wirkt sich das aus. Circa 2000 Zeilen werden benötigt, wenn die Sidebar aufklappbar ist und “nur” noch ca. 1400, wenn die Sidebar nur im aktuellen Bereich geöffnet ist. (Ich hätte erwartet, dass es im letzteren Fall bei sehr kleinen Bereichen wie “Fotografie” nur ein paar Dutzend Zeilen wären.)
Zeit zum Rendern des Wiki
Beim Durchstöbern der Einstellungsmöglichkeiten zur Sidebar (siehe oben) habe ich auch die sidebar_cache_limit
gefunden.
Aus der Dokumentation:
On large sites (default: > 2000 pages) the section menu is not generated for each page, but cached for the whole section. The HTML classes for marking the active menu item (and menu path) are then set using JS. You can adjust the limit for activating the cached section menu with the optional parameter .ui.sidebar_cache_limit.
In meinem Wiki gibt es aktuell 1333 Seiten und ich habe das Limit testweise auf 500 eingestellt:
sidebar_cache_limit = 500
Die Zeit, um mein gesamtes Wiki zu rendern verringert sich damit von circa 12 Sekunden auf circa 2 Sekunden.
Der Nachteil ist jedoch, dass die übergeordneten Seiten nicht mehr angezeigt werden, sobald man zu tief in den Unterebenen eines Bereichs ist.
Damit ich nicht in Zukunft von diesem Mechanismus überrascht werde, sobald das Wiki über 2000 Seiten hat, habe ich den Wert auf 5000 eingestellt.
Swap Fonts
Ich wurde darauf hingewiesen, dass in bei der Einbindung der selbst gehosteten Schriften im CSS noch einstellen kann, dass lokal vorhandene Systemschriften im Browser verwendet werden, solange die vom Server bereitgestellten Schriften noch nicht heruntergeladen wurden. Dadurch werden Inhalte früher dargestellt.
Pro @font-face {
-Eintrag in der Datei assets/scss/_variables_project.scss
kommt diese Zeile hinzu: 1
font-display: swap;
Gar keine systemfremden Schriften
Es ist auch möglich, die Verwendung der eingebundenen Schriften komplett zu unterbinden. Dazu trägt man in der Datei assets/scss/_variables_project.scss
ein:
$td-enable-google-fonts: false;
Mir gefällt die verwendete Schriftart des Themes, daher nutze ich nicht die Möglichkeit zur Abschaltung.
Galerie hinzugefügt
Ich habe zum Wiki-Theme noch die hugo-shortcode-gallery hinzugefügt, die ich auch bereits hier im Blog verwende.
cd themes
git submodule add https://github.com/mfg92/hugo-shortcode-gallery.git
Änderungen in der Datei config.toml
:
- Aus
theme = "docsy"
wirdtheme = [ "docsy", "hugo-shortcode-gallery" ]
Dazu kommen diese Einträge in den Bereich params
(die ich so auch im Blog verwende):
galleryShowExif = true
gallerylastRow = "nojustify"
gallerythumbnailResizeOptions = "450x450 q85 Lanczos"
gallerythumbnailHoverEffect = "enlarge"
galleryloadJQuery = true
galleryrowHeight = "150"
gallerysortOrder = "desc"
gallerypreviewType = "none"
Eine Beispielgalerie kann man hier ansehen: wiki.natenom.com/docs/sauerbraten/maps/kleinestadt/.
Kommentare
Bisher gibt es hier keine Kommentare.
Kommentar oder Anmerkung für diesen Blogbeitrag
Öffentlicher Kommentar per E-Mail: Hier klicken
Nicht öffentliche Anmerkung per E-Mail: Hier klicken
Sonstige Kontaktaufnahme: Kontakt