Because modules aren’t part of the application runtime, their options aren’t either. However, in many cases, you might need access to some of these module options within your runtime code. We recommend exposing the needed config using Nuxt’s runtimeConfig.
Note that we use defu to extend the public runtime configuration the user provides instead of overwriting it.You can then access your module options in a plugin, component, the application like any other runtime configuration:
import { useRuntimeConfig } from '@nuxt/kit'const options = useRuntimeConfig().public.myModule
Be careful not to expose any sensitive module configuration on the public runtime config, such as private API keys, as they will end up in the public bundle.
If your module should provide Vue components, you can use the addComponent utility to add them as auto-imports for Nuxt to resolve.
import { addComponent, createResolver, defineNuxtModule } from '@nuxt/kit'export default defineNuxtModule({ setup (options, nuxt) { const resolver = createResolver(import.meta.url) // From the runtime directory addComponent({ name: 'MySuperComponent', // name of the component to be used in vue templates export: 'MySuperComponent', // (optional) if the component is a named (rather than default) export filePath: resolver.resolve('runtime/app/components/MySuperComponent.vue'), }) // From a library addComponent({ name: 'MyAwesomeComponent', export: 'MyAwesomeComponent', filePath: '@vue/awesome-components', }) },})
Alternatively, you can add an entire directory by using addComponentsDir.
It is highly recommended to prefix your exports to avoid conflicts with user code or other modules. See Prefix Your Exports for more details.
Note that all components, pages, composables and other files that would be normally placed in your app/ folder need to be in runtime/app/. This will mean they can be type checked properly.
If your module should provide composables, you can use the addImports utility to add them as auto-imports for Nuxt to resolve.
import { addImports, createResolver, defineNuxtModule } from '@nuxt/kit'export default defineNuxtModule({ setup (options, nuxt) { const resolver = createResolver(import.meta.url) addImports({ name: 'useComposable', // name of the composable to be used as: 'useMyComposable', // optional alias from: resolver.resolve('runtime/app/composables/useComposable'), }) },})
Sometimes, you may need to maintain state consistency between the server and the client. Examples include Nuxt’s built-in useState or useAsyncData composables.When a function is registered, Nuxt’s compiler automatically injects a unique key as an additional argument if the function is called with fewer than the specified number of arguments. This key remains stable between server-side rendering and client hydration.
The injected key is a hash derived from the file path and call location.
Use the keyedComposables option to register your function:
import { addServerHandler, createResolver, defineNuxtModule } from '@nuxt/kit'export default defineNuxtModule({ setup (options, nuxt) { const resolver = createResolver(import.meta.url) addServerHandler({ route: '/api/_my-module/hello/:name', handler: resolver.resolve('./runtime/server/api/hello/[name].get'), }) // Or using a catch all route addServerHandler({ route: '/api/_my-module/files/**:path', handler: resolver.resolve('./runtime/server/api/files/[...path].get'), }) },})
It is highly recommended to prefix your server routes to avoid conflicts with user-defined routes. Common paths like /api/auth, /api/login, or /api/user may already be used by the application.
If your module should provide other kinds of assets, they can also be injected. Here’s a simple example module injecting a stylesheet through Nuxt’s css array.
If your module depends on other modules, you can specify them using the moduleDependencies option. This provides a more robust way to handle module dependencies with version constraints and configuration merging:
import { createResolver, defineNuxtModule } from '@nuxt/kit'const resolver = createResolver(import.meta.url)export default defineNuxtModule({ meta: { name: 'my-module', }, moduleDependencies: { '@nuxtjs/tailwindcss': { // You can specify a version constraint for the module version: '>=6', // Any configuration that should override `nuxt.options` overrides: { exposeConfig: true, }, // Any configuration that should be set defaults: { config: { darkMode: 'class', content: { files: [ resolver.resolve('./runtime/components/**/*.{vue,mjs,ts}'), resolver.resolve('./runtime/*.{mjs,js,ts}'), ], }, }, }, }, }, setup (options, nuxt) { // We can inject our CSS file which includes Tailwind's directives nuxt.options.css.push(resolver.resolve('./runtime/assets/styles.css')) },})
Lifecycle hooks allow you to expand almost every aspect of Nuxt. Modules can hook to them programmatically or through the hooks map in their definition.
import { defineNuxtModule } from '@nuxt/kit'export default defineNuxtModule({ // Hook to the `app:error` hook through the `hooks` map hooks: { 'app:error': (err) => { console.info(`This error happened: ${err}`) }, }, setup (options, nuxt) { // Programmatically hook to the `pages:extend` hook nuxt.hook('pages:extend', (pages) => { console.info(`Discovered ${pages.length} pages`) }) },})
You might also want to add a type declaration to the user’s project (for example, to augment a Nuxt interface or provide a global type of your own). For this, Nuxt provides the addTypeTemplate utility that both writes a template to the disk and adds a reference to it in the generated nuxt.d.ts file.