Here are some advanced patterns for authoring modules, including hooks, templates, and type augmentation.
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 { addPlugin, createResolver, 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`)
})
},
})
close hook is available for this.import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
nuxt.hook('close', async (nuxt) => {
// Your custom code here
})
},
})
Modules can also define and call their own hooks, which is a powerful pattern for making your module extensible.
If you expect other modules to be able to subscribe to your module's hooks, you should call them in the modules:done hook. This ensures that all other modules have had a chance to be set up and register their listeners to your hook during their own setup function.
// my-module/module.ts
import { defineNuxtModule } from '@nuxt/kit'
export interface ModuleHooks {
'my-module:custom-hook': (payload: { foo: string }) => void
}
export default defineNuxtModule({
setup (options, nuxt) {
// Call your hook in `modules:done`
nuxt.hook('modules:done', async () => {
const payload = { foo: 'bar' }
await nuxt.callHook('my-module:custom-hook', payload)
})
},
})
If you need to add a virtual file that can be imported into the user's app, you can use the addTemplate utility.
import { addTemplate, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// The file is added to Nuxt's internal virtual file system and can be imported from '#build/my-module-feature.mjs'
addTemplate({
filename: 'my-module-feature.mjs',
getContents: () => 'export const myModuleFeature = () => "hello world !"',
})
},
})
For the server, you should use the addServerTemplate utility instead.
import { addServerTemplate, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
// The file is added to Nitro's virtual file system and can be imported in the server code from 'my-server-module.mjs'
addServerTemplate({
filename: 'my-server-module.mjs',
getContents: () => 'export const myServerModule = () => "hello world !"',
})
},
})
If you need to update your templates/virtual files, you can leverage the updateTemplates utility like this:
nuxt.hook('builder:watch', (event, path) => {
if (path.includes('my-module-feature.config')) {
// This will reload the template that you registered
updateTemplates({ filter: t => t.filename === 'my-module-feature.mjs' })
}
})
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.
If your module should augment types handled by Nuxt, you can use addTypeTemplate to perform this operation:
import { addTemplate, addTypeTemplate, defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({
setup (options, nuxt) {
addTypeTemplate({
filename: 'types/my-module.d.ts',
getContents: () => `// Generated by my-module
interface MyModuleNitroRules {
myModule?: { foo: 'bar' }
}
declare module 'nitropack/types' {
interface NitroRouteRules extends MyModuleNitroRules {}
interface NitroRouteConfig extends MyModuleNitroRules {}
}
export {}`,
})
},
})
If you need more granular control, you can use the prepare:types hook to register a callback that will inject your types.
const template = addTemplate({ /* template options */ })
nuxt.hook('prepare:types', ({ references }) => {
references.push({ path: template.dst })
})
There are multiple ways to extend the TypeScript configuration of the user's project from your module.
The simplest way is to modify the Nuxt configuration directly like this:
// extend tsconfig.app.json
nuxt.options.typescript.tsConfig.include ??= []
nuxt.options.typescript.tsConfig.include.push(resolve('./augments.d.ts'))
// extend tsconfig.shared.json
nuxt.options.typescript.sharedTsConfig.include ??= []
nuxt.options.typescript.sharedTsConfig.include.push(resolve('./augments.d.ts'))
// extend tsconfig.node.json
nuxt.options.typescript.nodeTsConfig.include ??= []
nuxt.options.typescript.nodeTsConfig.include.push(resolve('./augments.d.ts'))
// extend tsconfig.server.json
nuxt.options.nitro.typescript ??= {}
nuxt.options.nitro.typescript.tsConfig ??= {}
nuxt.options.nitro.typescript.tsConfig.include ??= []
nuxt.options.nitro.typescript.tsConfig.include.push(resolve('./augments.d.ts'))
Alternatively, you can use the prepare:types and nitro:prepare:types hooks to extend the TypeScript references for specific type contexts, or modify the TypeScript configuration similar to the example above.
nuxt.hook('prepare:types', ({ references, sharedReferences, nodeReferences }) => {
// extend app context
references.push({ path: resolve('./augments.d.ts') })
// extend shared context
sharedReferences.push({ path: resolve('./augments.d.ts') })
// extend node context
nodeReferences.push({ path: resolve('./augments.d.ts') })
})
nuxt.hook('nitro:prepare:types', ({ references }) => {
// extend server context
references.push({ path: resolve('./augments.d.ts') })
})
exclude option in tsconfig.json.Nuxt automatically includes your module's directories in the appropriate type contexts. To augment types from your module, all you need to do is place the type declaration file in the appropriate directory based on the augmented type context. Alternatively, you can extend the TypeScript configuration to augment from an arbitrary location.
my-module/runtime/ - app type context (except for the runtime/server directory)my-module/runtime/server/ - server type contextmy-module/ - node type context (except for the runtime/ and runtime/server directories)-| my-module/ # node type context
---| runtime/ # app type context
------| augments.app.d.ts
------| server/ # server type context
---------| augments.server.d.ts
---| module.ts
---| augments.node.d.ts