Volltextsuche in Nuxt-Content-Seiten mit Fuse.js umsetzen
AI generated
{ }
Nuxt Content · Suche · Fuse.js
Volltextsuche in Nuxt Content mit Fuse.js
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.

14 Min. Lesezeit Fuse.js · Fuzzy Search Nuxt Content Suche

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.

11. FAQ: Fuse.js-Suche in Nuxt Content: Das Wichtigste auf einen Blick

1Muss ich für Fuse.js einen eigenen Backend-Dienst betreiben?
Nein, Fuse.js läuft vollständig im Browser des Nutzers. Der Suchindex wird entweder zur Build-Zeit als JSON generiert oder über die bestehende Nuxt-Content-Query-API geladen, ein separater Suchserver ist nicht nötig.
2Wie groß darf der Suchindex maximal werden, bevor Fuse.js langsam wird?
Bis zu einigen Tausend Einträgen bleibt Fuse.js auf durchschnittlicher Hardware angenehm schnell. Ab etwa zehntausend Einträgen wird die Suchzeit pro Tastenanschlag spürbar, besonders auf schwächeren Mobilgeräten.
3Kann ich mit Fuse.js auch nach mehreren Begriffen gleichzeitig suchen?
Ja, Fuse.js unterstützt eine erweiterte Suchsyntax mit logischen Operatoren wie UND und ODER, die über die useExtendedSearch-Option aktiviert werden kann, falls die einfache Fuzzy-Suche nicht ausreicht.
4Wie unterscheidet sich Fuse.js von einer einfachen JavaScript-Filterfunktion?
Eine einfache Filterfunktion findet nur exakte Teilzeichenketten, während Fuse.js Tippfehler und abweichende Schreibweisen toleriert und Ergebnisse zusätzlich nach einem berechneten Relevanz-Score sortiert.
5Wie sollte ich den Suchindex bei einem sehr großen Blog mit Tausenden Artikeln aufbauen?
In solchen Fällen lohnt es sich, den Index bewusst schlank zu halten, nur die wichtigsten Felder einzubeziehen und gegebenenfalls eine Paginierung oder Kategorie-Vorfilterung vorzuschalten, bevor Fuse.js über die verbleibende Teilmenge sucht.
6Kann Fuse.js auch Bilder oder andere Nicht-Text-Inhalte durchsuchen?
Nein, Fuse.js arbeitet ausschließlich mit Textfeldern. Bilder müssten über begleitende Textbeschreibungen wie Alt-Texte oder Bildunterschriften indirekt durchsuchbar gemacht werden.
7Wie gehe ich mit Sonderzeichen oder Umlauten in Suchbegriffen um?
Fuse.js verarbeitet Unicode-Zeichen wie Umlaute standardmäßig korrekt. Bei Bedarf kann vor dem Indexaufbau zusätzlich eine Normalisierung, etwa das Vereinheitlichen von Groß- und Kleinschreibung, vorgenommen werden.
8Ab welcher Projektgröße lohnt sich der Wechsel zu Algolia?
Ein Wechsel lohnt sich meist erst, wenn entweder die Eintragszahl im hohen fünfstelligen Bereich liegt, facettierte Filterung über viele Attribute benötigt wird oder ein Suchteam mit dediziertem Relevanz-Tuning zur Verfügung steht.
9Kann ich Fuse.js auch für eine reine Produktsuche statt Blog-Content nutzen?
Ja, Fuse.js ist inhaltlich agnostisch und funktioniert genauso gut für Produktdaten, solange diese als flaches JSON-Array mit den relevanten durchsuchbaren Feldern vorliegen.
10Beeinflusst eine große Fuse.js-Suche die Core Web Vitals der Seite?
Der Suchindex selbst wird meist erst bei Interaktion mit der Suche nachgeladen und beeinflusst dadurch kaum die initialen Ladezeiten. Wird der Index jedoch ungefiltert beim ersten Seitenaufruf geladen, kann er die Interaktivitätsmetriken spürbar verschlechtern.