Wie Module wirklich funktionieren und wie du sie für dein Team nutzbar machst
Nuxt-Module sind das Werkzeug, mit dem sich Build-Prozess, Runtime und sogar die Entwicklerwerkzeuge eines Nuxt-Projekts erweitern lassen. In diesem Artikel bauen wir Schritt für Schritt ein eigenes Modul, das einen Hook in den Build-Prozess einklinkt, typsichere Optionen mitbringt und am Ende ein eigenes Tab in den Nuxt DevTools registriert.
Inhaltsverzeichnis
- 1. Was ein Nuxt-Modul eigentlich ist
- 2. Der Unterschied zwischen Modul und Plugin
- 3. Die defineNuxtModule() API im Detail
- 4. Hooks in den Build-Prozess einklinken
- 5. Modul-Optionen mit Schema und Typsicherheit
- 6. Ein eigenes DevTools-Tab registrieren
- 7. DevTools-Tab-Inhalte: iframe oder eingebettete Vue-Komponente
- 8. Runtime-Config und Laufzeitverhalten steuern
- 9. Modul veröffentlichen und pflegen
- 10. Zusammenfassung
- 11. FAQ
1. Was ein Nuxt-Modul eigentlich ist
Ein Nuxt-Modul ist Code, der beim Start des Build-Vorgangs beziehungsweise beim Hochfahren des Dev-Servers einmalig in Node.js ausgeführt wird und dabei die Projektkonfiguration selbst verändern kann. Ein Modul kann Komponenten automatisch registrieren, Server-Routen hinzufügen, die Vite- oder Webpack-Konfiguration anpassen, eigene Composables bereitstellen oder Templates generieren, die zur Build-Zeit ins Projekt einfließen. Es hat vollen Zugriff auf das Nuxt-Objekt und damit auf sämtliche Optionen, die auch in der nuxt.config stehen würden, nur eben programmatisch statt statisch.
Das gesamte Nuxt-Ökosystem beruht auf genau dieser Mechanik. Module wie nuxt/image, nuxt/content oder nuxt/devtools sind selbst nichts anderes als ganz normale Module, die dieselbe öffentliche API nutzen, die auch für projektinterne Module zur Verfügung steht. Wer versteht, wie ein eigenes kleines Modul aufgebaut ist, versteht damit gleichzeitig, wie die großen Community-Module unter der Haube funktionieren, und kann deren Quellcode als Referenz für eigene Lösungen lesen.
2. Der Unterschied zwischen Modul und Plugin
Ein Plugin wird über defineNuxtPlugin definiert und läuft bei jedem Start der Anwendung, je nach Konfiguration im Client, im Server oder in beiden Kontexten. Es hat Zugriff auf die Vue-App-Instanz sowie auf den Nuxt-Kontext und wird typischerweise genutzt, um globale Composables bereitzustellen, Drittanbieter-Bibliotheken zu initialisieren oder Direktiven zu registrieren. Ein Plugin ist also Laufzeit-Code, der Teil des ausgelieferten Bundles wird.
Ein Modul dagegen läuft ausschließlich einmalig in Node während des Build-Vorgangs und ist selbst niemals Teil des an den Browser ausgelieferten Codes. Es konfiguriert das Projekt und kann dabei durchaus selbst ein Plugin registrieren, das dann zur Laufzeit aktiv wird. Module sind damit Meta-Werkzeuge auf Projektebene, Plugins sind Laufzeit-Bausteine innerhalb der fertigen Anwendung, und beide Konzepte ergänzen sich häufig in ein und demselben Feature.
3. Die defineNuxtModule() API im Detail
defineNuxtModule kommt aus dem Paket @nuxt/kit und nimmt ein Objekt mit drei zentralen Feldern entgegen: meta mit Name, configKey und Kompatibilitätsangabe, defaults mit den Standardwerten der Modul-Optionen, sowie die setup-Funktion, die als zweiten Parameter das Nuxt-Objekt erhält. Innerhalb von setup stehen Nuxt-Kit-Hilfsfunktionen wie addPlugin, addComponent, addImportsDir oder extendPages zur Verfügung, die typische Aufgaben kapseln, damit man nicht selbst manuell in die Nuxt-Konfiguration eingreifen muss.
Auf Konsumentenseite reicht es, das Modul im modules-Array der nuxt.config einzutragen und optional ein Options-Objekt unter dem in meta.configKey angegebenen Schlüssel zu hinterlegen. Der folgende Ausschnitt zeigt ein minimales, aber vollständiges Modul, das ein Runtime-Plugin registriert, einen Composable per addImports bereitstellt und beim Start des Builds eine Konsolenausgabe erzeugt, sofern die Option enabled nicht auf false gesetzt wurde.
import { defineNuxtModule, addPlugin, addImports, createResolver } from '@nuxt/kit'
export interface ModuleOptions {
enabled: boolean
panelTitle: string
}
export default defineNuxtModule<ModuleOptions>({
meta: {
name: 'my-debug-module',
configKey: 'myDebugModule',
compatibility: { nuxt: '^3.0.0' }
},
defaults: {
enabled: true,
panelTitle: 'Projekt-Debug'
},
setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)
if (!options.enabled) {
return
}
addPlugin(resolve('./runtime/plugin'))
addImports({
name: 'useProjectDebug',
from: resolve('./runtime/composables/useProjectDebug')
})
nuxt.hook('build:before', () => {
console.log(`[${options.panelTitle}] Build startet mit Debug-Modul`)
})
}
})
4. Hooks in den Build-Prozess einklinken
Nuxt basiert intern auf der Bibliothek hookable und stellt darüber ein umfangreiches, typisiertes Hook-System bereit. Über nuxt.hook('name', callback) lässt sich an vielen Stellen im Lebenszyklus eingreifen, etwa build:before und build:done rund um den eigentlichen Build, pages:extend um die automatisch erkannten Seiten zu verändern, vite:extendConfig beziehungsweise webpack:config um die jeweilige Bundler-Konfiguration anzupassen, sowie close, wenn der Prozess beendet wird. Alle Callbacks dürfen asynchron sein, Nuxt wartet automatisch auf das zurückgegebene Promise, bevor der nächste Schritt beginnt.
In der Praxis eignen sich Hooks zum Beispiel dafür, vor dem eigentlichen Build eine generierte Datei zu schreiben, zur Laufzeit ermittelte Routen in pages:extend nachzutragen, oder in vite:extendConfig einen zusätzlichen Alias für einen internen Pfad zu definieren. Weil die komplette Liste der verfügbaren Hooks Teil des @nuxt/schema-Pakets ist, lohnt sich bei jedem neuen Nuxt-Minor-Release ein Blick in das Changelog, da gelegentlich neue Hooks dazukommen oder bestehende präzisiert werden.
5. Modul-Optionen mit Schema und Typsicherheit
Das generische Typargument von defineNuxtModule sorgt dafür, dass die Optionen innerhalb der setup-Funktion vollständig typisiert sind und der Nutzer des Moduls in der nuxt.config Autovervollständigung für alle verfügbaren Felder bekommt. Die in defaults hinterlegten Werte werden automatisch mit den vom Nutzer angegebenen Werten zusammengeführt, sodass innerhalb von setup bereits ein vollständiges Optionsobjekt vorliegt, ohne dass man selbst prüfen müsste, ob ein Feld gesetzt wurde oder nicht.
Für Optionen, die über reine Build-Zeit-Konfiguration hinausgehen und auch zur Laufzeit gebraucht werden, reicht die einfache Übernahme in defaults allein nicht aus, dafür gibt es addTemplate und runtimeConfig, die im nächsten Abschnitt genauer betrachtet werden. Wichtig ist an dieser Stelle vor allem die Unterscheidung: Werte, die nur den Build selbst steuern, bleiben in den Modul-Optionen, Werte, die auch im laufenden Server- oder Client-Code gebraucht werden, müssen explizit in die Runtime-Config übertragen werden.
6. Ein eigenes DevTools-Tab registrieren
Die Nuxt DevTools bieten einen eigenen Hook namens devtools:customTabs, über den ein Modul ein zusätzliches Tab-Objekt mit Titel, eindeutiger ID, Icon und einer View-Definition registriert. Die View kann entweder vom Typ iframe sein und auf eine beliebige URL verweisen, oder vom Typ launch, um einen externen Prozess zu starten. Das DevTools-Kit übernimmt anschließend die komplette Darstellung innerhalb der bereits bestehenden DevTools-Oberfläche, sodass sich das eigene Tab nahtlos neben den mitgelieferten Tabs für Pages, Components oder Modules einreiht.
Ein realistisches Beispiel wäre ein Tab, das während der Entwicklung alle aktiven Feature-Flags des Projekts anzeigt, inklusive der Möglichkeit, sie testweise umzuschalten, ohne den Code selbst anzufassen. Dazu registriert das Modul zusätzlich zur Tab-Definition eine eigene, nur in der Entwicklung aktive Server-Route, welche die Feature-Flags als kleine HTML-Oberfläche ausliefert und die im DevTools-Tab per iframe eingebettet wird.
7. DevTools-Tab-Inhalte: iframe oder eingebettete Vue-Komponente
Für den Inhalt eines DevTools-Tabs gibt es grundsätzlich zwei Wege. Die iframe-Variante zeigt eine vom Modul selbst per addServerHandler ausgelieferte Route in einem isolierten Frame an, ist technisch am einfachsten umzusetzen und völlig unabhängig vom Rest der DevTools-Oberfläche. Die Alternative ist eine tief integrierte Vue-Komponente, die über das DevTools-RPC direkt mit dem laufenden Nuxt-Client kommuniziert und dadurch etwa Live-Daten aus dem Browser-State anzeigen kann, ohne den Umweg über eine eigene HTTP-Route.
Für den Einstieg empfiehlt sich die iframe-Variante, da sie mit deutlich weniger Code auskommt und keine Kenntnis der internen DevTools-RPC-Mechanik voraussetzt. Viele Community-Module beginnen genau so und wechseln erst später, wenn tatsächlich eine engere Verzahnung mit dem Client-State gebraucht wird, zur komponentenbasierten Variante. Für ein internes Projekt-Tool reicht die iframe-Lösung in aller Regel vollständig aus.
8. Runtime-Config und Laufzeitverhalten steuern
Damit Werte aus der Modul-Konfiguration auch im laufenden Server- oder Client-Code verfügbar sind, schreibt die setup-Funktion sie explizit in nuxt.options.runtimeConfig beziehungsweise, falls auch der Browser darauf zugreifen soll, in nuxt.options.runtimeConfig.public. Beide Objekte sind anschließend über den Composable useRuntimeConfig() erreichbar, wobei alles unter public tatsächlich im ausgelieferten Client-Bundle landet und damit für jeden einsehbar ist, während der Rest ausschließlich serverseitig bleibt.
Für ein reines Entwickler-Werkzeug wie das hier beschriebene Debug-Modul ist zusätzlich wichtig, die Registrierung von DevTools-Tab und Debug-Routen an das Flag nuxt.options.dev zu koppeln. So bleibt die gesamte Funktionalität zuverlässig auf die lokale Entwicklung beschränkt und es gelangt kein zusätzlicher Code oder eine zusätzliche Route in den Produktions-Build, selbst wenn das Modul versehentlich in der Produktionskonfiguration verbleibt.
9. Modul veröffentlichen und pflegen
Für ein Modul, das mehrere Projekte oder Teams teilen sollen, empfiehlt sich eine eigene Paketstruktur mit module.ts als Einstiegspunkt, gebaut mit unbuild und mit @nuxt/kit als Peer-Dependency statt als normale Abhängigkeit, damit keine doppelte Nuxt-Kit-Version im Projekt landet. Für Tests bietet @nuxt/test-utils eine Umgebung, in der sich ein Modul gegen ein minimales Fixture-Projekt starten und prüfen lässt, ohne ein vollständiges Beispielprojekt von Hand pflegen zu müssen.
Bei der Versionierung lohnt es sich, die compatibility-Angabe in meta realistisch zu halten und bei jedem größeren Nuxt-Release zu prüfen, ob genutzte Hooks weiterhin stabil funktionieren, da sich Details zwischen Minor-Versionen gelegentlich ändern können. Wird das Modul öffentlich auf npm veröffentlicht, lohnt sich zusätzlich ein Eintrag im offiziellen Nuxt-Modul-Verzeichnis, was die Auffindbarkeit deutlich erhöht und gleichzeitig Community-Feedback zu Kompatibilitätsproblemen einbringt.
| Merkmal | Modul | Plugin | Layer |
|---|---|---|---|
| Ausführungszeitpunkt | Build-/Dev-Server-Start (Node) | App-Boot, Client und/oder Server | Wird beim Start als Basis-Config gemerged |
| Zugriff | Nuxt-Kit-Utilities, volle Nuxt-Config | Vue-App-Instanz, Nuxt-Context, Composables | Komplette Projektstruktur (pages, components) |
| Typischer Zweck | Build-Prozess erweitern, Tools integrieren | Globale Composables/Direktiven bereitstellen | Wiederverwendbare Projekt-Basis (Theme, Preset) |
| Registrierung | modules-Array in nuxt.config | plugins-Array oder Auto-Scan | extends-Array in nuxt.config |
| Beispiel | @nuxt/devtools, @nuxt/image | VueQueryPlugin, Sentry-Init | Firmenweites Design-System als Basis-Layer |
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
Eigene Nuxt-Module und DevTools-Tabs: Das Wichtigste auf einen Blick
API
defineNuxtModule() aus @nuxt/kit mit setup(options, nuxt).
Ausführung
Einmalig beim Build- bzw. Dev-Server-Start in Node.js.
DevTools
Registrierung über den Hook devtools:customTabs.
Empfehlung
Für den Einstieg iframe-basierte Tabs, später Vue-Komponenten.