Eigene Nuxt-Module schreiben: DevTools-Tab und Build-Hooks in der Praxis
AI generated
{ }
Nuxt 3 · Modul-Entwicklung
Eigene Nuxt-Module schreiben: Von Build-Hooks bis zum eigenen DevTools-Tab
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.

16 Min. Lesezeit Nuxt 3 TypeScript

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.

11. FAQ: Eigene Nuxt-Module und DevTools-Tabs: Das Wichtigste auf einen Blick

1Was ist der Unterschied zwischen defineNuxtModule und defineNuxtPlugin?
defineNuxtModule definiert Code, der einmalig beim Start des Build- oder Dev-Servers in Node läuft und die Projektkonfiguration verändert, während defineNuxtPlugin Code definiert, der bei jedem App-Start im Client und/oder Server ausgeführt wird und typischerweise Composables oder Vue-Plugins registriert.
2Kann ein Modul selbst Plugins registrieren?
Ja, über die Funktion addPlugin aus @nuxt/kit kann ein Modul während seines Setups ein Runtime-Plugin einhängen, das dann bei jedem App-Start ausgeführt wird, das ist ein sehr häufiges Muster in der Modul-Entwicklung.
3Wo läuft der Setup-Code eines Nuxt-Moduls?
Der Setup-Code läuft in Node.js während des Build-Vorgangs beziehungsweise beim Start des Dev-Servers, er hat also keinen Zugriff auf Browser-APIs und wird selbst nicht in das Client-Bundle aufgenommen.
4Wie bekomme ich ein eigenes Tab in den Nuxt DevTools?
Über den Hook devtools:customTabs registrierst du ein Tab-Objekt mit Titel, Icon und einer View, entweder als iframe-Quelle oder als eingebettete Vue-Komponente, das DevTools-Kit übernimmt anschließend die Darstellung.
5Muss ich für die DevTools-Integration eine eigene Server-Route bauen?
Bei der iframe-Variante ja, du registrierst über addServerHandler eine eigene Route, die die Debug-Oberfläche als HTML ausliefert, bei der Vue-Komponenten-Variante bindest du stattdessen eine Komponente direkt ein.
6Welche Build-Hooks stehen mir zur Verfügung?
Nuxt bietet unter anderem build:before, build:done, pages:extend, vite:extendConfig, webpack:config und close, die vollständige und typisierte Liste findet sich im Schema-Paket von Nuxt und kann sich zwischen Minor-Versionen leicht ändern.
7Wie verhindere ich, dass mein Debug-Modul in Produktion aktiv ist?
Du prüfst innerhalb der Setup-Funktion das Flag nuxt.options.dev und registrierst DevTools-Tab sowie Debug-Routen nur, wenn dieses Flag wahr ist, so bleibt die Funktionalität zuverlässig auf die lokale Entwicklung beschränkt.
8Wie übergebe ich Optionen an mein eigenes Modul?
Du definierst ein Interface für ModuleOptions, setzt sinnvolle defaults im Modul und der Nutzer überschreibt einzelne Werte im Objekt, das unter dem in meta.configKey angegebenen Schlüssel in der nuxt.config steht.
9Kann ich Werte aus der Modul-Konfiguration auch zur Laufzeit im Client nutzen?
Ja, dazu schreibst du die gewünschten Werte in nuxt.options.runtimeConfig.public, dort sind sie sowohl im Server- als auch im Client-Code über useRuntimeConfig verfügbar, allerdings dann für jeden im Bundle sichtbar.
10Muss ich mein Modul als separates npm-Paket veröffentlichen?
Nein, für projektinterne Zwecke reicht ein lokales Modul im modules-Ordner des Projekts vollkommen aus, eine Veröffentlichung lohnt sich erst, wenn mehrere Projekte oder Teams das Modul gemeinsam nutzen sollen.