Skip to main content
With great power comes great responsibility. While modules are powerful, here are some best practices to keep in mind while authoring modules to keep applications performant and developer experience great.

Handle Async Setup

As we’ve seen, Nuxt modules can be asynchronous. For example, you may want to develop a module that needs fetching some API or calling an async function. However, be careful with asynchronous behaviors as Nuxt will wait for your module to setup before going to the next module and starting the development server, build process, etc. Prefer deferring time-consuming logic to Nuxt hooks.
If your module takes more than 1 second to setup, Nuxt will emit a warning about it.

Prefix Your Exports

Nuxt modules should provide an explicit prefix for any exposed configuration, plugin, API, composable, component, or server route to avoid conflicts with other modules, Nuxt internals, or user-defined code. Ideally, prefix them with your module’s name. For example, if your module is called nuxt-foo:
TypeAvoidPrefer
Components<Button>, <Modal><FooButton>, <FooModal>
ComposablesuseData(), useModal()useFooData(), useFooModal()
Server routes/api/track, /api/data/api/_foo/track, /api/_foo/data

Server Routes

This is particularly important for server routes, where common paths like /api/auth, /api/login, or /api/user are very likely already used by the application. Use a unique prefix based on your module name:
  • /api/_foo/... (using underscore prefix)
  • /_foo/... (for non-API routes)

Use Lifecycle Hooks

When your module needs to perform one-time setup tasks (like generating configuration files, setting up databases, or installing dependencies), use lifecycle hooks instead of running the logic in your main setup function.
import { addServerHandler, defineNuxtModule } from 'nuxt/kit'
import semver from 'semver'

export default defineNuxtModule({
  meta: {
    name: 'my-database-module',
    version: '1.0.0',
  },
  async onInstall (nuxt) {
    // One-time setup: create database schema, generate config files, etc.
    await generateDatabaseConfig(nuxt.options.rootDir)
  },
  async onUpgrade (nuxt, options, previousVersion) {
    // Handle version-specific migrations
    if (semver.lt(previousVersion, '1.0.0')) {
      await migrateLegacyData()
    }
  },
  setup (options, nuxt) {
    // Regular setup logic that runs on every build
    addServerHandler({ /* ... */ })
  },
})
This pattern prevents unnecessary work on every build and provides a better developer experience.

Be TypeScript Friendly

Nuxt has first-class TypeScript integration for the best developer experience. Exposing types and using TypeScript to develop modules benefits users even when not using TypeScript directly.

Provide Type Definitions

  • Export TypeScript types for your module options
  • Add type declarations for composables and utilities
  • Augment Nuxt types when adding new configuration options

Use ESM Syntax

Nuxt relies on native ESM. Use modern ES module syntax in your modules:
// Use this
import { defineNuxtModule } from '@nuxt/kit'
export default defineNuxtModule({ /* ... */ })

// Not this
const { defineNuxtModule } = require('@nuxt/kit')
module.exports = defineNuxtModule({ /* ... */ })

Document Your Module

Consider documenting module usage in the readme file:
  • Why use this module? - Explain the problem it solves
  • How to use this module? - Provide clear installation and usage instructions
  • What does this module do? - Describe the features and functionality
Linking to the integration website and documentation is always a good idea.

Documentation Structure

Your README should include:
  1. Installation - How to install and configure the module
  2. Usage - Basic examples and common use cases
  3. Configuration - Available options and their defaults
  4. API Reference - Exported composables, components, and utilities
  5. Examples - Real-world usage scenarios

Provide a Demo

It’s a good practice to make a minimal reproduction with your module and StackBlitz that you add to your module readme. This not only provides potential users of your module a quick and easy way to experiment with the module but also an easy way for them to build minimal reproductions they can send you when they encounter issues.

Stay Version Agnostic

Nuxt, Nuxt Kit, and other new toolings are made to have both forward and backward compatibility in mind. Please use “X for Nuxt” instead of “X for Nuxt 3” to avoid fragmentation in the ecosystem and prefer using meta.compatibility to set Nuxt version constraints.
export default defineNuxtModule({
  meta: {
    name: 'my-module',
    compatibility: {
      nuxt: '>=3.0.0'
    }
  }
})

Follow Starter Conventions

The module starter comes with a default set of tools and configurations (e.g. ESLint configuration). If you plan on open-sourcing your module, sticking with those defaults ensures your module shares a consistent coding style with other community modules out there, making it easier for others to contribute.

Conventions Include

  • ESLint - For code quality and consistency
  • TypeScript - For type safety
  • Vitest - For testing
  • Conventional Commits - For changelog generation

Summary

By following these best practices, you’ll create modules that are:
  • Performant - Fast setup and minimal overhead
  • Compatible - Works well with other modules and user code
  • Maintainable - Easy to update and extend
  • User-friendly - Well-documented and easy to use

Module Recipes

Learn practical patterns for module development