Testing & Publishing
E2E testing, best practices, and publishing modules.
E2E Testing Setup
npm install -D @nuxt/test-utils vitest// vitest.config.ts
import { defineVitestConfig } from '@nuxt/test-utils/config'
export default defineVitestConfig({
test: { environment: 'nuxt' }
})Test Fixtures
Create a minimal Nuxt app that uses your module:
// test/fixtures/basic/nuxt.config.ts
import MyModule from '../../../src/module'
export default defineNuxtConfig({
modules: [MyModule],
myModule: { enabled: true }
})<!-- test/fixtures/basic/pages/index.vue -->
<template>
<MyButton>Click me</MyButton>
</template>Writing Tests
import { fileURLToPath } from 'node:url'
import { $fetch, setup } from '@nuxt/test-utils/e2e'
// test/basic.test.ts
import { describe, expect, it } from 'vitest'
describe('basic', async () => {
await setup({
rootDir: fileURLToPath(new URL('./fixtures/basic', import.meta.url))
})
it('renders component', async () => {
const html = await $fetch('/')
expect(html).toContain('Click me')
})
it('api works', async () => {
const data = await $fetch('/api/_my-module/status')
expect(data).toEqual({ status: 'ok' })
})
})Manual Testing
# In module directory
npm pack
# In test project
npm install /path/to/my-module-1.0.0.tgzBest Practices
Async Setup
Keep setup fast. Use debug: { modules: true } to inspect module setup timing. Nuxt's slow-setup warning threshold depends on the release, so check the supported version when interpreting it.
// This fetch delays module setup
async setup(options, nuxt) {
const data = await fetchRemoteConfig() // Slow!
}
// This moves the wait to ready, which Nuxt also awaits
setup(options, nuxt) {
nuxt.hook('ready', async () => {
const data = await fetchRemoteConfig()
})
}Choose the lifecycle phase that needs the result. Moving awaited work to ready does not remove it from initialization. To reduce startup work, avoid unnecessary I/O and repeated computation, then check overall startup as well as module setup timing.
Prefix All Exports
Avoid naming conflicts:
| Type | Wrong | Right |
|---|---|---|
| Components | <Button> |
<FooButton> |
| Composables | useData() |
useFooData() |
| Server routes | /api/track |
/api/_foo/track |
| Plugins | $helper |
$fooHelper |
Lifecycle Hooks
For installation and upgrade tasks:
export default defineNuxtModule({
meta: { name: 'my-module', version: '2.0.0' },
async onInstall(nuxt) {
await generateInitialConfig(nuxt.options.rootDir)
},
async onUpgrade(nuxt, options, previousVersion) {
if (semver.lt(previousVersion, '2.0.0')) {
await migrateFromV1()
}
}
})Nuxt stores lifecycle state in the project's .nuxtrc, under meta.name with meta.version as the recorded version. Missing state can make installation work run again, so initialization and migrations must tolerate repeat execution. Keep repeatable generated build output in normal setup or template generation. Lifecycle errors are logged without stopping the build; do not assume a failed required migration prevents setup from continuing.
TypeScript + ESM Only
// Always export typed options
// ESM only - no CommonJS
import { something } from 'package'
export interface ModuleOptions {
apiKey: string
debug?: boolean
} // Right
const { something } = require('package') // WrongError Messages
setup(options, nuxt) {
if (!options.apiKey) {
throw new Error('[my-module] `apiKey` option is required')
}
}Releasing
Two-step: local bump → CI publish. CI must pass before tag push.
Setup
pnpm add -D bumpp{
"scripts": {
"release": "bumpp && git push --follow-tags"
}
}Flow
pnpm release # Prompts version, commits, tags, pushes
# → CI release.yml triggers on v* tag → npm publish + GitHub releaseCommit Conventions
| Prefix | Bump |
|---|---|
feat: |
minor |
fix:, chore:, docs: |
patch |
feat!: or BREAKING CHANGE: |
major |
CI Workflows
Three workflows for complete CI/CD:
| File | Trigger | Purpose |
|---|---|---|
ci.yml |
push/PR | lint, typecheck, test |
pkg.yml |
push/PR | preview packages via pkg-pr-new |
release.yml |
tag v* |
npm publish + GitHub release |
Copy templates from: ci-workflows.md
Publishing
Naming Conventions
| Scope | Example | Description |
|---|---|---|
@nuxtjs/ |
@nuxtjs/tailwindcss |
Community modules (nuxt-modules org) |
nuxt- |
nuxt-my-module |
Third-party modules |
@org/ |
@myorg/nuxt-auth |
Organization scoped |
Documentation Checklist
- Why - What problem does this solve?
- Installation - How to install and configure?
- Usage - Basic examples
- Options - All config options with types
- Demo - StackBlitz link
Version Compatibility
meta: {
compatibility: { nuxt: '>=3.0.0' }
}Use "X for Nuxt" naming, not "X for Nuxt 3" — let meta.compatibility handle versions.