Clientseitige Fuzzy-Search ohne externen Suchdienst
Fuse.js ist eine leichtgewichtige JavaScript-Bibliothek für clientseitige Fuzzy-Search, die sich sehr gut mit dem statisch generierten Content-Index von Nuxt Content kombinieren lässt, ohne dass ein externer Suchdienst wie Algolia betrieben oder bezahlt werden muss. Wer eine überschaubare Anzahl an Content-Seiten hat, kann damit eine tippfehlertolerante Suche direkt im Browser umsetzen, inklusive Gewichtung von Titel gegenüber Fließtext und ohne zusätzliche Serverinfrastruktur.
Inhaltsverzeichnis
- 1. Warum Fuse.js für clientseitige Suche in Nuxt Content passt
- 2. Suchindex aus generiertem Content-Bestand bauen
- 3. Feldgewichtung: Titel höher priorisieren als Fließtext
- 4. Threshold und Fuzzy-Toleranz richtig einstellen
- 5. Performance bei wachsender Seitenzahl im Blick behalten
- 6. Debouncing und Ergebnisdarstellung für eine flüssige Nutzererfahrung
- 7. Alternative zu einer externen Suchlösung wie Algolia
- 8. Mehrsprachige Inhalte in der Suche berücksichtigen
- 9. Aktualität des Suchindex bei neuen oder geänderten Inhalten
- 10. Zusammenfassung
- 11. FAQ
1. Warum Fuse.js für clientseitige Suche in Nuxt Content passt
Fuse.js implementiert eine Fuzzy-Search-Logik, die auch bei leicht abweichender Schreibweise, Tippfehlern oder unvollständigen Suchbegriffen noch relevante Treffer findet, anders als eine simple Substring-Suche, die nur exakte Teilzeichenketten erkennt. Die gesamte Suche läuft dabei im Browser des Nutzers, ohne dass eine Anfrage an einen Server geschickt werden muss, was für statisch generierte Nuxt-Content-Seiten besonders gut passt, da der komplette Inhalt ohnehin schon zur Build-Zeit feststeht.
Diese Kombination eignet sich vor allem für Projekte mit einer überschaubaren, aber nicht trivialen Menge an Seiten, etwa einen Firmenblog, eine Dokumentation oder eine Wissensdatenbank mit einigen Hundert Einträgen. Für solche Projekte lohnt sich der Betrieb eines dedizierten Suchdienstes selten, während eine reine Substring-Suche schnell an ihre Grenzen stößt, sobald Nutzer nicht exakt den im Text vorkommenden Begriff eingeben.
2. Suchindex aus generiertem Content-Bestand bauen
Der erste Schritt besteht darin, aus allen Nuxt-Content-Seiten ein flaches JSON-Array zu erzeugen, das genau die Felder enthält, nach denen später gesucht werden soll, typischerweise Titel, eine kurze Beschreibung und ein Auszug aus dem Fließtext. Dieser Index lässt sich entweder zur Build-Zeit über ein eigenes Nitro-Plugin generieren oder direkt über die Content-Query-API von Nuxt Content abrufen und im Client zwischenspeichern.
Wichtig ist, den Index bewusst schlank zu halten und nicht den kompletten Markdown-Inhalt jeder Seite hineinzupacken, da dieser Index vollständig an den Client ausgeliefert wird und seine Größe direkt die Ladezeit der Suchfunktion beeinflusst. Ein Auszug von wenigen hundert Zeichen aus dem Fließtext reicht in der Regel aus, um relevante Treffer zu erkennen, ohne den Index unnötig aufzublähen.
// composables/useSearchIndex.ts
import Fuse from 'fuse.js'
interface SearchEntry {
title: string
description: string
excerpt: string
path: string
}
export function useSearchIndex() {
const { data: entries } = useAsyncData<SearchEntry[]>('search-index', () =>
queryContent('/blog').only(['title', 'description', 'excerpt', '_path']).find()
)
const fuse = computed(() => new Fuse(entries.value ?? [], {
keys: [
{ name: 'title', weight: 0.6 },
{ name: 'description', weight: 0.3 },
{ name: 'excerpt', weight: 0.1 },
],
threshold: 0.35,
ignoreLocation: true,
}))
return { fuse }
}
3. Feldgewichtung: Titel höher priorisieren als Fließtext
Fuse.js erlaubt es, jedem durchsuchten Feld über die keys-Konfiguration ein eigenes Gewicht zwischen 0 und 1 zuzuweisen, wobei höhere Werte stärker in die Berechnung der Relevanz einfließen. In der Praxis hat sich bewährt, den Titel deutlich höher zu gewichten als die Beschreibung und den Fließtext-Auszug, weil ein Treffer im Titel meist thematisch relevanter ist als ein zufälliges Vorkommen eines Suchbegriffs irgendwo im Text.
Eine typische Verteilung könnte etwa 0.6 für den Titel, 0.3 für die Beschreibung und 0.1 für den Fließtext-Auszug vorsehen, wobei sich die exakten Werte am besten durch Ausprobieren mit echten Suchanfragen der eigenen Zielgruppe kalibrieren lassen. Ohne bewusste Gewichtung behandelt Fuse.js alle Felder gleich, was dazu führen kann, dass ein zufälliger Treffer im Fließtext einen viel relevanteren Titeltreffer in der Ergebnisliste überholt.
4. Threshold und Fuzzy-Toleranz richtig einstellen
Der threshold-Parameter steuert, wie tolerant Fuse.js gegenüber Abweichungen zwischen Suchbegriff und Treffer ist, von 0 (nur exakte Übereinstimmung) bis 1 (nahezu beliebige Übereinstimmung). Ein zu niedriger Wert liefert kaum Treffer bei Tippfehlern, während ein zu hoher Wert die Ergebnisliste mit thematisch kaum relevanten Treffern überschwemmt, was die Suche für Nutzer eher verwirrend als hilfreich macht.
Ein Startwert zwischen 0.3 und 0.4 hat sich in vielen Projekten als guter Kompromiss erwiesen, sollte aber anhand echter Nutzeranfragen überprüft werden, sobald genug Daten vorliegen. Zusätzlich hilft die Option ignoreLocation dabei, Treffer unabhängig davon zu finden, an welcher Position im Text der Suchbegriff auftaucht, was besonders bei längeren Fließtext-Auszügen relevant ist.
5. Performance bei wachsender Seitenzahl im Blick behalten
Solange der Suchindex einige hundert bis wenige tausend Einträge umfasst, bleibt Fuse.js auch auf durchschnittlicher Hardware angenehm schnell, da die Fuzzy-Suche komplett im Arbeitsspeicher des Browsers läuft. Mit wachsender Eintragszahl steigt jedoch sowohl die Zeit für den Aufbau des Fuse-Index als auch die Zeit für jede einzelne Suchanfrage, weil Fuse.js intern jeden Eintrag gegen den Suchbegriff bewertet.
Ab einigen Zehntausend Einträgen macht sich das spürbar bemerkbar, besonders auf leistungsschwächeren Mobilgeräten, wo eine Verzögerung von mehreren hundert Millisekunden pro Tastenanschlag bereits als träge wahrgenommen wird. Für solche Größenordnungen lohnt sich entweder eine Debounce-Strategie, die die Suche erst nach einer kurzen Tippschreib-Pause auslöst, oder der Umstieg auf eine serverseitige Suchlösung, die nicht mehr im Client rechnen muss.
6. Debouncing und Ergebnisdarstellung für eine flüssige Nutzererfahrung
Ohne Debouncing würde bei jedem einzelnen Tastenanschlag eine neue Fuse.js-Suche ausgelöst, was bei größeren Indizes zu spürbaren Rucklern in der Eingabe führen kann, weil die Suche den Haupt-Thread des Browsers kurzzeitig blockiert. Eine Verzögerung von 150 bis 250 Millisekunden zwischen letzter Eingabe und tatsächlicher Suchausführung reicht meist aus, um diesen Effekt zu vermeiden, ohne dass sich die Suche für Nutzer träge anfühlt.
Bei der Darstellung der Ergebnisse hilft es zusätzlich, die gefundenen Treffer nach Relevanz-Score zu sortieren, den Fuse.js standardmäßig mitliefert, und optional den Treffer im Text farblich hervorzuheben, um Nutzern sofort zu zeigen, warum ein bestimmtes Ergebnis erschienen ist. Diese kleinen Details entscheiden oft mehr über die gefühlte Suchqualität als die exakte Fuzzy-Logik im Hintergrund.
7. Alternative zu einer externen Suchlösung wie Algolia
Algolia und ähnliche gehostete Suchdienste bieten deutlich ausgefeiltere Relevanz-Algorithmen, typo-tolerante Suche mit maschinellem Lernen, facettierte Filterung und praktisch unbegrenzte Skalierbarkeit, verursachen dafür aber laufende Kosten und eine zusätzliche externe Abhängigkeit, die separat gepflegt und synchron gehalten werden muss. Für kleinere bis mittlere Projekte steht dieser Mehraufwand oft in keinem guten Verhältnis zum tatsächlichen Nutzen.
Fuse.js dagegen kostet nichts, benötigt keinen externen Dienst und lässt sich vollständig innerhalb des bestehenden Nuxt-Deployments betreiben, was Wartung und Datenschutz deutlich vereinfacht, weil keine Suchanfragen an einen Drittanbieter geschickt werden. Der Kompromiss liegt in der begrenzten Skalierbarkeit und den einfacheren Relevanz-Algorithmen, was für die meisten Content-Seiten im niedrigen bis mittleren vierstelligen Bereich an Einträgen aber keine praktische Einschränkung darstellt.
8. Mehrsprachige Inhalte in der Suche berücksichtigen
Wird eine Seite in mehreren Sprachen betrieben, sollte der Suchindex pro Sprache getrennt aufgebaut werden, da eine gemeinsame Fuzzy-Suche über Inhalte unterschiedlicher Sprachen selten sinnvolle Ergebnisse liefert und Nutzer meist ohnehin nur innerhalb ihrer aktuellen Sprachversion suchen wollen. In der Praxis bedeutet das, den Index entweder pro Sprache separat zu generieren oder beim Laden nach dem aktuellen Locale zu filtern, bevor er an Fuse.js übergeben wird.
Diese Trennung verhindert auch, dass ein deutscher Suchbegriff zufällig einen englischen Content-Eintrag mit ähnlicher Zeichenfolge trifft, was ohne saubere Trennung passieren kann, wenn Fuse.js rein auf Zeichenähnlichkeit optimiert und keine sprachliche Bedeutung kennt. Wer die Sprachtrennung von Anfang an mitdenkt, spart sich später eine aufwendige Nachbesserung der Suchlogik.
9. Aktualität des Suchindex bei neuen oder geänderten Inhalten
Da der Suchindex typischerweise zur Build-Zeit aus dem aktuellen Content-Bestand generiert wird, spiegelt er automatisch immer den Stand des letzten Deployments wider, ohne dass eine separate Synchronisation nötig ist, wie es bei einem externen Suchdienst mit eigenem Index oft der Fall wäre. Nach jedem neuen Build steht der aktualisierte Index sofort zur Verfügung, sobald die Seite neu ausgeliefert wird.
Für Projekte mit sehr häufigen Content-Änderungen außerhalb regulärer Deployments, etwa über ein angebundenes CMS mit sofortiger Veröffentlichung, kann es sinnvoll sein, den Index zusätzlich über eine eigene API-Route zur Laufzeit neu zu generieren, statt ausschließlich auf den nächsten Build zu warten. Diese Entscheidung hängt stark davon ab, wie zeitkritisch neue Inhalte in der Suche auffindbar sein müssen.
| Kriterium | Fuse.js (clientseitig) | Algolia (gehostet) | Einfache Substring-Suche |
|---|---|---|---|
| Kosten | Kostenlos, kein Dienst nötig | Laufende Nutzungskosten | Kostenlos |
| Tippfehlertoleranz | Ja, konfigurierbare Fuzzy-Logik | Ja, sehr ausgereift | Nein, nur exakte Teiltreffer |
| Skalierbarkeit | Gut bis wenige Tausend Einträge | Praktisch unbegrenzt | Gut, aber wenig relevant |
| Externe Abhängigkeit | Keine, läuft im Client | Ja, gehosteter Drittdienst | Keine |
| Setup-Aufwand | Gering bis mittel | Mittel bis hoch | Sehr gering |
Mironsoft
Vue-Architektur, Composition API und Nuxt-Performance
Vue-Anwendungen, die mit jedem Feature nicht komplizierter werden?
Wir prüfen bestehende Vue- und Nuxt-Projekte auf unstrukturierte Composables, ungenutzte Reaktivität und aufgeblähte Bundles und bauen daraus eine Architektur, die neue Features aufnimmt, ohne die Codebasis unübersichtlicher zu machen.
Architektur-Review
Composables, State-Management und Komponentenstruktur auf Wartbarkeit prüfen.
Performance-Audit
Reaktivitäts-Overhead, Bundle-Größe und Nuxt-Rendering-Strategie systematisch optimieren.
Nuxt-Integration
SSR/SSG-Setup und API-Anbindung robust und typsicher aufbauen.
10. Zusammenfassung
Fuse.js-Suche in Nuxt Content: Das Wichtigste auf einen Blick
Kernidee
Fuzzy-Search komplett im Browser über einen zur Build-Zeit generierten Content-Index.
Gewichtung
Titel deutlich höher gewichten als Beschreibung und Fließtext-Auszug.
Grenze
Ab einigen Zehntausend Einträgen wird eine serverseitige Lösung sinnvoller.
Alternative
Algolia bietet mehr Skalierbarkeit, aber mit laufenden Kosten und externer Abhängigkeit.