Nuxt Modules That Ship: Install, Configure, and Launch

Nuxt ships with a lot of batteries, but module sprawl can stall a launch. After building and selling real products, my rule is simple: pick a small, predictable set of Nuxt modules, wire them with intention, and write just enough glue code to deliver revenue features. This walkthrough shows the exact setup and patterns we use to get a SaaS, an AI tool, or an internal web app live without surprises.
Nuxt modules in practice: what they change
Nuxt modules are installable packs that hook into build and runtime. They can add components and composables, adjust Vite, inject server routes, register plugins, and expose typed options. The win is focus: each module removes a slice of plumbing you would otherwise rewrite.
- Build time: tweak Vite, transform code, auto-import components and composables, generate types, or alias paths.
- Runtime: register plugins, server routes, middleware, and runtime config your app consumes while running.
Concrete use cases that consistently pay off:
- Styling and UI: Tailwind CSS for design system tokens and utility classes; Nuxt Image for optimized images with smart formats.
- State and utilities: Pinia for predictable stores; VueUse for browser and reactivity helpers you would otherwise hand-roll.
- Internationalization: @nuxtjs/i18n for locale routing, lazy-loaded messages, and language detection.
- Auth and security: an auth module or custom middleware with typed session handling and server-only secrets.
- DX: Devtools, auto imports, and type helpers that reduce footguns in large projects.
If the question is how to build and sell an AI tool online, the answer is rarely “add more modules.” It is “pick three to five modules that remove obvious toil, then ship a thin slice end to end.”
Set up a lean stack: install and configure
1) Create a fresh Nuxt app
npx nuxi@latest init my-app
cd my-app
pnpm install # or npm install / yarn2) Install only what you will use this week
A dependable starter set:
- UI and images: @nuxtjs/tailwindcss, @nuxt/image-edge
- State and utilities: @pinia/nuxt, @vueuse/nuxt
- i18n: @nuxtjs/i18n (only if you plan multiple languages now)
pnpm add -D @nuxtjs/tailwindcss @nuxt/image-edge @pinia/nuxt @vueuse/nuxt @nuxtjs/i18n3) Register modules in nuxt.config with tight options
Keep options close to the module. Make defaults explicit so upgrades do not surprise you.
// nuxt.config.ts
export default defineNuxtConfig({
modules: [
'@nuxtjs/tailwindcss',
'@nuxt/image-edge',
'@pinia/nuxt',
'@vueuse/nuxt',
'@nuxtjs/i18n'
],
image: {
formats: ['webp'],
provider: 'ipx'
},
i18n: {
locales: [
{ code: 'en', iso: 'en-US', file: 'en.json', name: 'English' },
{ code: 'es', iso: 'es-ES', file: 'es.json', name: 'Español' }
],
defaultLocale: 'en',
lazy: true,
langDir: 'locales',
detectBrowserLanguage: { useCookie: true, cookieKey: 'i18n_redirected' }
}
})4) Put secrets in runtimeConfig and fail fast
Never hardcode provider keys. Use runtimeConfig and your deployment platform’s env vars. Add a startup assertion so a missing key fails locally before it fails in prod.
// nuxt.config.ts
export default defineNuxtConfig({
runtimeConfig: {
public: {
appName: 'My App'
},
stripeSecret: process.env.STRIPE_SECRET_KEY
}
})// server/plugins/guard-runtime.ts
export default defineNitroPlugin(() => {
const { stripeSecret } = useRuntimeConfig()
if (!stripeSecret) {
console.error('Missing STRIPE_SECRET_KEY')
throw new Error('Invalid server configuration')
}
})Turn modules into SaaS features
Map each feature to one or two modules plus a little glue. Resist stacking overlapping packages.
Authentication and protected pages
- Use route middleware for access rules. Annotate public routes with
meta.public = true. - Keep the user in a single source of truth, such as a Pinia store fed by a server endpoint.
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware((to) => {
const user = useState('user').value
const isPublic = to.meta.public === true
if (!user && !isPublic) return navigateTo('/login')
})Payments and subscriptions
- Store provider keys in runtimeConfig. Never expose them to the client.
- Handle webhooks on the server to update subscriptions, invoices, and entitlements.
// server/api/webhooks/payment.post.ts
export default defineEventHandler(async (event) => {
const sig = getHeader(event, 'stripe-signature')
// Verify signature, parse event, update user subscription
return { received: true }
})Internationalization in the UI
- Drive copy from JSON, grouped by feature to avoid duplication.
- Add a simple locale switcher. Keep flags out of it; use language codes.
<template>
<select v-model="$i18n.locale" aria-label="Language">
<option value="en">EN</option>
<option value="es">ES</option>
</select>
</template>Admin area
- Protect routes with role checks in middleware and on the server.
- Use server-side filtering and pagination for large tables to keep TTFB low.
Case study: captions feature in a week
Suppose you are building a small AI tool that burns captions onto short videos. Keep the stack minimal: file uploads, a queue or cron job for processing, a page to preview and download the result, and i18n so captions can be localized. For expectations and UI flow, study a clear, production-ready workflow like this article on how to add subtitles to video online. Mirror the clarity in your editor and export steps while you build your own pipeline behind the scenes.
If you prefer to skip scaffolding and start from a proven base, our Nuxt SaaS boilerplate (shipahe.ad) includes protected pages, multiple authentication providers, an admin area, billing with checkout flows, a locale switcher, transactional email, a preconfigured database and ORM, S3-compatible file storage, analytics, SEO helpers, AI chat and generation with switchable models, scheduled cron jobs, and a landing page. You focus on your domain logic; the wiring is done.
When a tiny custom module beats copy-paste
When behavior repeats across apps, a small custom module keeps code local and configurable. This example adds a server endpoint and exposes a public runtime setting.
// modules/guarded-headers/module.ts import { defineNuxtModule, addServerHandler } from '@nuxt/kit'
export default defineNuxtModule({ meta: { name: 'guarded-headers' }, defaults: { allow: 'content-security-policy' }, setup(options, nuxt) { addServerHandler({ route: '/api/_headers', handler: resolve('./runtime/headers'), }) nuxt.options.runtimeConfig.public.allowedHeaders = options.allow } })
// modules/guarded-headers/runtime/headers.ts
export default defineEventHandler(() => {
const cfg = useRuntimeConfig()
return { allowed: cfg.public.allowedHeaders }
})// nuxt.config.ts
export default defineNuxtConfig({
modules: [
// other modules
'./modules/guarded-headers'
],
guardedHeaders: {
allow: ['content-security-policy', 'x-frame-options']
}
})This pattern shows how to add a server route from a module, pass options with sane defaults, and surface a typed public config without scattering it through the app.
Operate, monitor, and prune
- Run
pnpm devwith the console visible. Fix SSR and deprecation warnings immediately; they compound during upgrades. - Track usage. If your starter includes analytics, review which routes get traffic before you add features or libraries.
- Profile bundle size. Use a Vite visualizer and remove modules with heavy client payloads that do not move key metrics.
- Audit environment variables in staging first. Many runtime features silently degrade with missing keys.
- Pin versions. Upgrade Nuxt, Vite, TypeScript, and modules in small steps, reading changelogs as you go.
Common pitfalls
- Module order surprises: modules that patch Vite or auto imports may need to load earlier.
- Server vs client confusion: do not call browser APIs in server plugins; use
process.clientchecks or client-only plugins. - Duplicate functionality: pick one image solution, one state library, one i18n solution.
- Environment drift: add startup checks to fail fast when keys are missing in production.
Key takeaways
- Pick a short list of Nuxt modules tied to outcomes, not a wishlist.
- Keep configuration close to each module and put secrets in
runtimeConfig. - Use route middleware and server endpoints to enforce auth and billing rules.
- Write tiny custom modules for repeatable glue code across projects.
- Measure usage and bundle size, then prune. Shipping small wins.
Whether you assemble your own Vue Nuxt starter template or start from a Nuxt boilerplate to move fast, modules are the difference between hobby code and a maintainable product. Used well, they turn an idea into a working SaaS in days, not months.
FAQ
What are nuxt modules in simple terms?
They are installable packages that extend Nuxt at build time and runtime. Modules can add plugins, routes, config, and tooling so you write less boilerplate.
How many nuxt modules should I start with?
Start with 3 to 6 essentials tied to outcomes like styling, state, images, and i18n. Add more only when a real need appears in your roadmap.
Do nuxt modules work in serverless deployments?
Yes, as long as the module’s server features are compatible with Nitro. Keep secrets in runtimeConfig and rely on server routes for secure tasks.
Should I write a custom module or a plugin?
If you only need client or server runtime code in one app, a plugin is fine. If you need build-time hooks, config, or to reuse code across apps, write a module.
How do I protect pages with auth using Nuxt?
Create route middleware that checks for a user state and redirects unauthenticated users to login. Keep credentials and tokens off the client.
Recommended resources
Ready to ship your SaaS?
Nuxt Middleware: A Practical Guide for SaaS and AI Apps
Use Nuxt 3 middleware to guard auth, subscriptions, locales, admin, and analytics. Concrete patterns, code, and pitfalls for SaaS and AI apps.
Nuxt multi-tenant SaaS architecture patterns that scale
A practical guide to nuxt multi-tenant SaaS design. Learn viable routing, shared-DB isolation, billing, and testing patterns that scale without surprises.