What are plugins in Nuxt?
Nuxt plugins are files in the plugins/ directory that run once during app initialization, registering Vue plugins, utilities, or global helpers before any component mounts.
Theory
TL;DR
- Think of plugins as airport ground crew: they set up services (auth, notifications, API clients) before the app starts, so every page has access without asking
- Plugins run once at startup; composables run per component; middleware runs per route
- Use a plugin when you need a Vue integration (
vueApp.use()) or a global injectable (provide) - Suffix
.client.tsfor browser-only code,.server.tsfor SSR-only code - Nuxt sorts plugins alphabetically; numeric prefixes (
01.auth.ts) control order
Quick example
// plugins/toast.client.ts - runs only in the browser
import Toast from 'vue-toastification'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Toast, { timeout: 3000 })
// Toast is now registered globally on the Vue app
})
// In any component or page:
const { $toast } = useNuxtApp()
$toast.success('User saved!')One registration, available everywhere. No imports in individual components.
Key difference
Plugins execute during Nuxt's startup phase, before hydration. The Vue app gets the plugin before any component tree is built. Composables, by contrast, run on demand inside component setup functions. Put per-component logic in a plugin and you bloat every page load. Call a composable at startup time like a plugin and you hit context errors.
When to use
- Vue plugin integration (
vue-toastification,pinia,i18n) -> plugin withvueApp.use() - Global API client available everywhere -> plugin with
provide: { api } - Reusable stateless logic -> composable
- Route-specific logic -> middleware
- Browser-only code (analytics, localStorage) ->
plugin.client.ts - Server-only code (database connections) ->
plugin.server.ts
How Nuxt loads plugins
Nuxt scans plugins/ at build time and registers each file automatically. At startup, it executes them sequentially in alphabetical order. Client plugins run after hydration (post-mount), server plugins run per request inside the Nitro engine. The provide object from each plugin gets merged into useNuxtApp() context via Vue's app.provide(). That's why $toast or $api appears on useNuxtApp() without any explicit import in your component.
Nuxt 3.10+ also supports parallel: true inside defineNuxtPlugin for plugins with no dependencies on each other. On large apps this cuts startup time noticeably.
Common mistakes
1. Missing .client.ts suffix for browser-only code
// Wrong: plugins/analytics.ts
// Runs on SSR AND client = double tracking, crashes on window references
// Fix: plugins/analytics.client.ts
export default defineNuxtPlugin(() => {
window.analytics.init() // window is safe here
})2. Wrong plugin order for dependencies
// Wrong: plugins/api.ts loads before plugins/auth.ts
// api.ts tries to read an auth token that doesn't exist yet
// Fix: rename to 01.auth.ts and 02.api.ts
// Nuxt sorts alphabetically, numeric prefixes winThis is the most common silent bug I see in Nuxt repos: auth and API loaded in the wrong order because no one added numeric prefixes until something broke in production.
3. Forgetting to return provide
// Wrong:
export default defineNuxtPlugin(() => {
const api = { getUser: (id) => $fetch(`/api/users/${id}`) }
// api is defined but never exposed to the app
})
// Fix:
export default defineNuxtPlugin(() => {
const api = { getUser: (id) => $fetch(`/api/users/${id}`) }
return { provide: { api } }
})
// Now: const { $api } = useNuxtApp() works in any component4. Data fetching directly inside plugin initialization
Avoid fetch calls inside plugin setup code. They run outside the component lifecycle, so useAsyncData and useFetch are not available. Move data fetching to composables where they have access to the proper request context.
Real-world usage
vue-toastification-> global toast notifications in e-commerce checkoutsPinia-> store setup in SaaS dashboards (common in nuxtjs/starter repos)Supabase-> auth client available app-wide in CMS and content sitesDay.js-> date formatting utility injected globally in blogging platformsSentry-> error tracking initialized once for the whole app
Follow-up questions
Q: How does Nuxt determine plugin execution order?
A: Alphabetically by filename. Prefix with numbers to control it: 01.auth.ts runs before 02.api.ts.
Q: What is the difference between provide and vueApp.use()?
A: vueApp.use() registers a Vue plugin (directives, components, global install logic). provide injects a value into Nuxt's context, accessible via useNuxtApp(). You often use both in the same plugin.
Q: Can a Nuxt plugin be async?
A: Yes. Pass async (nuxtApp) => { ... } to defineNuxtPlugin. Nuxt awaits async plugins before rendering, so keep them fast or every request will wait on them.
Q: How do plugins handle SSR hydration mismatches?
A: Use .client.ts for plugins that touch browser APIs. For shared state between server and client, use useState() inside the plugin rather than a plain variable.
Q: What is parallel: true and when do you use it?
A: Added in Nuxt 3.10. Lets a plugin run in parallel with others instead of waiting for the previous one to finish. Use it for plugins with no dependencies on each other. Reduces startup time on apps with many plugins.
Examples
Registering a Vue plugin
// plugins/toast.client.ts
import Toast from 'vue-toastification'
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Toast, { timeout: 3000 })
})
// pages/index.vue
const { $toast } = useNuxtApp()
async function saveUser(data) {
await $fetch('/api/user', { method: 'POST', body: data })
$toast.success('Saved!')
}One import, one registration, available in every page and component. This covers the majority of plugin use cases.
Global API client with provide
// plugins/supabase.ts
import { createClient } from '@supabase/supabase-js'
export default defineNuxtPlugin(() => {
const config = useRuntimeConfig()
const client = createClient(
config.public.supabaseUrl,
config.public.supabaseKey
)
return {
provide: {
supabase: client
}
}
})
// components/Login.vue
const { $supabase } = useNuxtApp()
async function login(email, password) {
const { data, error } = await $supabase.auth.signInWithPassword({
email,
password
})
if (error) console.error(error.message)
}The Supabase client initializes once, reuses its connection pool, and is available everywhere without re-importing. Standard pattern in NuxtContent-based CMS starters.
Short Answer
Interview readyA concise answer to help you respond confidently on this topic during an interview.