Wann app-weite Initialisierung, wann wiederverwendbare Logik gefragt ist
Nuxt Plugins und Composables lösen auf den ersten Blick ähnliche Aufgaben, weil beide Code außerhalb einer einzelnen Komponente unterbringen. Tatsächlich verfolgen sie unterschiedliche Ziele: ein Plugin initialisiert einmalig etwas für die gesamte Anwendung, während ein Composable wiederverwendbare, komponierbare Logik bereitstellt, die in beliebig vielen Komponenten unabhängig voneinander genutzt wird.
Inhaltsverzeichnis
- 1. Der Unterschied im Kern: einmalige Initialisierung vs. wiederverwendbare Logik
- 2. Wann ein Nuxt Plugin die richtige Wahl ist
- 3. Eine Drittanbieter-Bibliothek per Plugin registrieren
- 4. Wann ein Composable die richtige Wahl ist
- 5. provide/inject als Brücke zwischen Plugin und Composable
- 6. Häufige Fehlentscheidung: Logik fälschlich als Plugin implementiert
- 7. Client-only, server-only und universelle Plugins
- 8. Testbarkeit: Composables gegenüber Plugins
- 9. Eine kurze Entscheidungshilfe für den Alltag
- 10. Zusammenfassung
- 11. FAQ
1. Der Unterschied im Kern: einmalige Initialisierung vs. wiederverwendbare Logik
Ein Nuxt Plugin läuft genau einmal beim Start der Anwendung, unabhängig davon, wie viele Komponenten später tatsächlich davon Gebrauch machen. Es bekommt Zugriff auf den Nuxt-App-Kontext und kann darüber globale Vue-Funktionalität wie app.use, app.component oder app.directive registrieren, oder es kann Werte über provide bereitstellen, die dann in der gesamten Anwendung verfügbar sind, ohne dass jede Komponente sie einzeln importieren muss.
Ein Composable dagegen ist eine Funktion, die bei jedem Aufruf einen neuen, unabhängigen Ausschnitt reaktiver Logik erzeugt. Ruft eine Komponente useCounter() auf, bekommt sie ihren eigenen Zähler-Zustand, unabhängig davon, ob eine andere Komponente an anderer Stelle dieselbe Funktion ebenfalls aufruft. Diese Isolation pro Aufruf ist der zentrale Unterschied zu einem Plugin, das per Definition nur einmal ausgeführt wird und dessen Ergebnis von allen Verbrauchern geteilt wird.
2. Wann ein Nuxt Plugin die richtige Wahl ist
Ein Plugin passt immer dann, wenn etwas für die gesamte Anwendung genau einmal passieren soll, bevor die erste Komponente überhaupt rendert. Typische Beispiele sind das Registrieren einer globalen Vue-Direktive, das Einbinden einer Drittanbieter-Bibliothek, die selbst einen globalen Zustand oder eine globale Konfiguration verwaltet, oder das Setzen von Interceptors auf einer HTTP-Client-Instanz, die in der gesamten Anwendung dieselbe Basis-Konfiguration teilen soll.
Auch das Bereitstellen einer einzigen, applikationsweit geteilten Instanz eines Dienstes, etwa eines WebSocket-Clients oder eines Analytics-Trackers, gehört klar in ein Plugin, weil es keinen Sinn ergeben würde, bei jedem Komponenten-Mount eine neue Verbindung oder einen neuen Tracker aufzubauen. Das Plugin erzeugt die Instanz genau einmal und stellt sie über provide für alle Komponenten bereit, die sie über inject oder ein begleitendes Composable abrufen können.
3. Eine Drittanbieter-Bibliothek per Plugin registrieren
Das klassische Beispiel ist eine Bibliothek wie ein Toast-Benachrichtigungssystem oder eine Chart-Bibliothek, die als Vue-Plugin ausgeliefert wird und selbst app.use erwartet. In einem Nuxt-Plugin unter app/plugins wird die Vue-App-Instanz aus dem nuxtApp-Kontext entnommen und die Drittanbieter-Bibliothek genau einmal registriert, wonach sie in jeder Komponente ganz normal über ihre eigene, von der Bibliothek dokumentierte API genutzt werden kann.
Wichtig ist dabei, dass Nuxt Plugin-Dateien anhand ihres Dateinamens automatisch client- oder serverseitig ausgeführt werden, sofern man das Suffix .client.ts oder .server.ts verwendet. Eine Bibliothek, die auf window oder document zugreift, muss zwingend als Client-Plugin markiert werden, da sie sonst während des serverseitigen Renderings mit einem Fehler abbricht, weil diese globalen Objekte auf dem Server schlicht nicht existieren.
// app/plugins/toast.client.ts
import ToastLibrary from 'some-toast-library'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(ToastLibrary, {
position: 'top-right',
duration: 4000,
})
})
4. Wann ein Composable die richtige Wahl ist
Ein Composable passt immer dann, wenn Logik in mehreren Komponenten wiederverwendet werden soll, jede Verwendung aber ihren eigenen, unabhängigen Zustand haben soll. Ein useLocalStorage-Composable etwa, das einen Wert mit dem localStorage synchronisiert, soll bei jedem Aufruf mit einem anderen Schlüssel einen unabhängigen ref erzeugen, nicht einen einzigen, applikationsweit geteilten Zustand, der von allen Aufrufern gleichzeitig gelesen und geschrieben wird.
Auch reine Hilfsfunktionen ohne Nuxt-spezifische Abhängigkeiten, etwa eine useDebounce- oder useMousePosition-Funktion, gehören als Composable in das app/composables-Verzeichnis, wo Nuxt sie automatisch importiert, ohne dass man einen expliziten import-Pfad in jeder Komponente schreiben muss. Composables sind damit das Werkzeug der Wahl für alles, was man sich als eine parametrisierbare, komponierbare Einheit reaktiver Logik vorstellen kann, die in unterschiedlichen Komponenten unterschiedlich konfiguriert wird.
5. provide/inject als Brücke zwischen Plugin und Composable
In der Praxis werden Plugin und Composable häufig gemeinsam eingesetzt: das Plugin erzeugt einmalig eine geteilte Instanz und stellt sie über nuxtApp.provide bereit, während ein begleitendes Composable diese Instanz per useNuxtApp() oder inject abruft und dabei eine sauber typisierte, komfortable API darum herum baut. Auf diese Weise muss keine Komponente direkt mit dem rohen inject-Aufruf oder dem Nuxt-App-Kontext arbeiten, sondern ruft stattdessen einfach useAnalytics() oder useWebsocket() auf.
Dieses Muster trennt sauber zwischen dem Wo und Wie der einmaligen Initialisierung, die im Plugin lebt, und der Art, wie Komponenten auf das Ergebnis zugreifen, die im Composable lebt. Ändert sich später die zugrunde liegende Bibliothek oder deren Konfiguration, muss nur das Plugin angepasst werden, während die Composable-Schnittstelle für alle bereits bestehenden Aufrufstellen unverändert bleiben kann, was Refactorings deutlich risikoärmer macht.
// app/plugins/analytics.client.ts
import { createAnalyticsClient } from 'analytics-sdk'
export default defineNuxtPlugin((nuxtApp) => {
const analytics = createAnalyticsClient({ apiKey: useRuntimeConfig().public.analyticsKey })
nuxtApp.provide('analytics', analytics)
})
// app/composables/useAnalytics.ts
export function useAnalytics() {
const { $analytics } = useNuxtApp()
return {
trackEvent: (name: string, payload?: Record<string, unknown>) =>
$analytics.track(name, payload),
}
}
6. Häufige Fehlentscheidung: Logik fälschlich als Plugin implementiert
Ein wiederkehrender Fehler ist, eine an sich wiederverwendbare Logik, etwa das Verwalten eines Formular-Zustands oder das Abfragen einer API mit Ladezustand, direkt in einem Plugin zu implementieren, nur weil dort ebenfalls Zugriff auf den Nuxt-App-Kontext besteht. Das Ergebnis ist meist ein einziger, applikationsweit geteilter Zustand, obwohl eigentlich jede Komponente, die diese Logik nutzt, ihre eigene unabhängige Instanz haben sollte, etwa weil zwei verschiedene Formulare auf derselben Seite sonst denselben Ladezustand teilen würden, obwohl sie völlig unabhängige Anfragen auslösen.
Der umgekehrte Fehler kommt seltener vor, ist aber ebenso problematisch: eine Bibliothek, die tatsächlich nur einmal app-weit registriert werden muss, in ein Composable zu packen, das bei jedem Komponenten-Mount erneut versucht, dieselbe globale Registrierung durchzuführen. Bei manchen Bibliotheken führt das lediglich zu unnötiger doppelter Arbeit, bei anderen, etwa bei der Registrierung eines globalen Event-Listeners ohne Deduplizierung, kann es zu handfesten Bugs wie mehrfach ausgelösten Events führen.
7. Client-only, server-only und universelle Plugins
Nuxt unterscheidet über die Dateinamens-Konvention zwischen Plugins, die auf beiden Seiten laufen, solchen, die ausschließlich im Browser laufen sollen (.client.ts), und solchen, die ausschließlich auf dem Server laufen sollen (.server.ts). Diese Unterscheidung ist bei der Entscheidung zwischen Plugin und Composable ein zusätzliches Argument für das Plugin, weil ein Composable diese Umgebungstrennung nicht von sich aus mitbringt und man den entsprechenden Check, etwa über import.meta.client, manuell im Composable-Code ergänzen müsste.
Bei einer Bibliothek, die zwingend eine Browser-API wie window.localStorage oder eine Canvas-Zeichenfläche benötigt, ist ein .client.ts-Plugin daher meist die robustere Wahl gegenüber einem Composable, das bei jedem Aufruf prüfen müsste, ob es gerade auf dem Server oder im Browser läuft. Composables, die tatsächlich sowohl serverseitig als auch clientseitig sinnvoll funktionieren, etwa reine Datentransformationen ohne Browser-Abhängigkeit, profitieren dagegen nicht von dieser Unterscheidung und bleiben bewusst universell einsetzbar.
8. Testbarkeit: Composables gegenüber Plugins
Composables lassen sich in aller Regel deutlich einfacher isoliert testen als Plugins, weil sie als reine Funktionen aufgerufen und ihre Rückgabewerte direkt in einem Unit-Test geprüft werden können, notfalls mit einem minimalen Vue-Testkontext über withSetup oder eine ähnliche Testhilfe. Ein Plugin dagegen ist tief mit dem Nuxt-App-Lifecycle verzahnt und lässt sich meist nur sinnvoll in einem Integrations- oder End-to-End-Test prüfen, der eine vollständige Nuxt-App-Instanz hochfährt.
Diese Testbarkeits-Überlegung ist ein weiteres praktisches Argument dafür, so viel Logik wie möglich in Composables zu bündeln und Plugins bewusst schlank zu halten, im Idealfall nur mit der eigentlichen Registrierung einer Bibliothek oder Instanz, während jede darüber hinausgehende Logik, wie das Formatieren von Werten oder das Zusammenführen mehrerer Datenquellen, in ein separates, unabhängig testbares Composable ausgelagert wird.
9. Eine kurze Entscheidungshilfe für den Alltag
Wer unsicher ist, kann sich an einer einfachen Frage orientieren: Soll dieser Code genau einmal beim Start der Anwendung passieren und danach von allen geteilt werden, oder soll jede Komponente, die ihn nutzt, ihre eigene unabhängige Instanz bekommen? Im ersten Fall gehört der Code in ein Plugin, im zweiten Fall in ein Composable. Bei geteilten Instanzen, auf die mehrere Komponenten zugreifen sollen, ist die Kombination aus einem schlanken Plugin, das nur registriert und bereitstellt, und einem begleitenden Composable, das eine komfortable API darum baut, in der Praxis fast immer die wartbarste Lösung.
Diese einfache Unterscheidung nach Einmaligkeit gegenüber Wiederverwendbarkeit löst die meisten Zweifelsfälle zuverlässig auf und verhindert sowohl das Aufblasen von Plugins mit eigentlich komponierbarer Logik als auch das versehentliche Vervielfachen von Instanzen, die eigentlich nur einmal existieren sollten. Wer diese Grundregel im Team konsequent anwendet, vermeidet die meisten der oben beschriebenen Fehlentscheidungen von vornherein, ohne dass es dafür eine komplizierte Checkliste braucht.
| Kriterium | Nuxt Plugin | Composable | Faustregel |
|---|---|---|---|
| Ausführungshäufigkeit | Genau einmal beim App-Start | Bei jedem Aufruf neu | Einmalig -> Plugin, wiederholt -> Composable |
| Zustand | Applikationsweit geteilt | Pro Aufruf unabhängig | Geteilter Zustand spricht für Plugin |
| Typisches Beispiel | Drittanbieter-Bibliothek per app.use registrieren | useDebounce, useLocalStorage, useFetch | Registrierung vs. wiederverwendbare Logik |
| Testbarkeit | Nur mit vollem Nuxt-Kontext sinnvoll testbar | Isoliert als reine Funktion testbar | Mehr Logik in Composables erleichtert Tests |
| Umgebungssteuerung | Über Dateinamens-Suffix .client.ts / .server.ts | Manuell über import.meta.client prüfen | Browser-API-Abhängigkeit spricht für Plugin |
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
Nuxt Plugins vs. Composables: Das Wichtigste auf einen Blick
Plugin
Einmalige, app-weite Initialisierung, etwa das Registrieren einer Bibliothek
Composable
Wiederverwendbare Logik mit eigenem Zustand pro Aufruf
Brücke
Plugin stellt per provide bereit, Composable liest per useNuxtApp
Häufiger Fehler
Komponentenspezifische Logik fälschlich als geteiltes Plugin implementiert