Cansu Arı Logo
Blog
What is it?

The Difference Between Middleware, Plugins, and Composables

Three terms you hear a lot in Nuxt 3 projects: Middleware, Plugin, and Composable. They operate at different layers but are often confused. Let’s clarify the differences!

  • #Nuxt 3
  • #Vue.js

Skip to content

Different lifecycles, different responsibilities

Nuxt 3 is powerful, but the ecosystem can feel a bit confusing at first—especially three concepts that are often mixed up: Middleware, Plugin, and Composable.
They hook into different parts of the app lifecycle and carry different responsibilities.

In this post, we’ll explain what each does, how they differ, and when to use which—backed by examples.

1) Middleware — Manage page transitions

Middleware integrates with Nuxt’s routing system and runs before a page loads. It’s typically used for authorization, redirect control, or pre-validation.

How it works

Before navigating to a page, middleware runs. If it allows, the page loads; otherwise, you redirect.
// middleware/auth.global.ts
export default defineNuxtRouteMiddleware((to, from) => {
const user = useSupabaseUser()
if (!user.value) return navigateTo('/login')
})

Types

  • Route Middleware: Runs per-page or globally (middleware/).
  • Server Middleware: Runs at the API/server layer (server/middleware/).

When to use

  • Auth / redirect checks.
  • Route-based permission systems.
  • Validating data before the page renders.
Example: “If the user isn’t logged in, redirect to /login.”

2) Plugin — Extend the app

A Plugin adds global features, libraries, or services to your app. It can touch the Vue instance, Nuxt context, or global app level.

How it works

Plugins load at app startup and give you access to the app instance.
// plugins/axios.ts
import axios from 'axios'
export default defineNuxtPlugin((nuxtApp) => {
const api = axios.create({ baseURL: '/api' })
nuxtApp.provide('api', api)
})
Now anywhere in the app:
const { $api } = useNuxtApp()
you can use this service.

When to use

  • Integrating third-party libs (Axios, dayjs, GSAP, etc.).
  • Adding global mixins or helpers.
  • Defining app-wide services.
Example: Define $api or $auth to use everywhere.

3) Composable — Reuse logic

A Composable is a Composition-API helper. It packages repeatable logic into a function you can share across components—think logic module, not component.

How it works

Files live under composables/. This folder is auto-imported in Nuxt 3.
// composables/useCounter.ts
export const useCounter = () => {
const count = ref(0)
const inc = () => count.value++
const dec = () => count.value--
return { count, inc, dec }
}
Then in any component:
const { count, inc } = useCounter()

When to use

  • Sharing the same logic across multiple components.
  • Centralizing LocalStorage, API calls, form validation, etc.
  • Building a more modular, testable architecture.
Example: useAuth(), useTheme(), useForm() are classic composables.

Comparison table

PropertyMiddlewarePluginComposable
When it runsOn route changeOn app loadWhen a component calls it
PurposeRoute controlDefine global servicesShare logic
SSR support
ScopeRoute / ServerApp-wideLocal or global
Locationmiddleware/plugins/composables/

Example scenario

You’re implementing user auth:
  • Access control → Middleware (auth.global.ts)
  • Auth service setup → Plugin (plugins/auth.ts)
  • Reusable login/logout logic → Composable (useAuth.ts)
middleware/auth.global.ts  →  Access control
plugins/auth.ts → $auth service definition
composables/useAuth.ts → Login / logout logic
They work together—without stepping on each other.

Commonly confused cases

  1. Using a plugin inside a composable: Yes—e.g., consume $api inside a composable.
  2. Calling a composable from middleware: Also yes—middleware runs during setup.
  3. Reactive state in a plugin: Possible, but be careful—plugins are global, so shared state can leak.
💡 Pro tip: If you need global state, prefer useState(). It isolates per-request state during SSR.

Conclusion

In Nuxt 3:
  • Middleware → “Check before you go.”
  • Plugin → “Extend the app.” 🔌
  • Composable → “Share logic.”
Use all three together for modular, testable, and readable Nuxt apps. Picking the right tool not only boosts performance, it strengthens your architecture.
All tags
  • Nuxt 3
  • Vue.js