This part of the Nuxt tutorial focuses on middleware — handlers that can be invoked automatically before rendering a given page on the frontend or before processing data on the server side.
/app/middleware
Client-side middleware files live in the /app/middleware folder, from which Nuxt automatically loads them.
Middleware runs during navigation (routing). For a file to be recognized as middleware, it must export default the defineNuxtRouteMiddleware method. The handler receives to (destination) and from (origin) route objects. The syntax looks like this:
export default defineNuxtRouteMiddleware((to, from) => {
// logic
})
The type of to and from params comes from Vue Router and is called RouteLocationNormalizedGeneric. Through these objects you can access all key navigation info — URL, query parameters (?), hash (#), etc. Inside the function you can also use any composables and util functions.
Attaching middleware
Once you have a handler, you need to tell the framework which page(s) it should run for. Typically, this is done in a page’s <script setup> block via the definePageMeta macro. A brief side-note on “macros”: they look like ordinary functions, but are special compile-time constructs — essentially keywords — that the Nuxt compiler understands and replaces with actual JS code.
Here’s an example of assigning middleware to a specific page:
<script setup lang="ts">
definePageMeta({
middleware: 'foo'
})
</script>
This ensures the middleware defined in /app/middleware/foo.ts runs before the page renders.
You can also attach multiple middleware handlers by passing an array instead of a single string:
<script setup lang="ts">
definePageMeta({
middleware: ['foo1', 'foo2']
})
</script>
It’s even possible to define “inline” middleware directly within definePageMeta, but I believe this tends to obscure things since you have to remember it’s there.
If you want a handler to run on every page, writing definePageMeta on each page would quickly get tedious. Instead, you can create a “global” middleware by adding the .global suffix to the filename (quite similar to defining HTTP method in API endpoints). While /app/middleware/foo.ts must be explicitly referenced, /app/middleware/foo.global.ts executes automatically on routing to any page.
Since Nuxt 3.11, you can also configure middleware centrally in nuxt.config.ts via routeRules (client-side only, not for the Nitro server routes).
Example use case
A typical use case is authorization — checking whether the current user may view the page. If not, redirect them to login. Use the Nuxt helper navigateTo and be sure to return it:
export default defineNuxtRouteMiddleware((to, from) => {
if (!userCanView(to.path)) {
return navigateTo('/login')
}
})
function userCanView(page: string): boolean {
// access control logic
return true
}
If you don't want to redirect, just let the function end without returning anything. Nuxt will then continue and navigate to the page.
Other uses include audit logging or initializing resources required by the target page.
Final note on order: Global middleware runs first (alphabetically), followed by page-specific middleware (in the order listed for the page). Keep this in mind if ordering matters.
/server/middleware
There’s a similar mechanism for the Nuxt server. Files go into /server/middleware folder, where they are automatically registered and executed.
Since to and from aren’t meaningful here, server middleware is defined differently:
export default defineEventHandler((event) => {
// logic
})
The event parameter contains the JS server runtime context and the incoming HTTP request data (event.node.req).
Unlike client-side middleware, server middleware runs before every API request. It executes before any API endpoint starts handling the request. It shouldn’t return a value, and should at most augment the incoming request rather than changing original data.
Case Study
In a project we've built at work, client middleware is used, for example, to:
- verify login status and redirect unauthenticated users to the login page
- set the window title based on the current URL
Demo Project
You can find the reference implementation here:
nuxt-middleware @ GitHub
Four middleware functions are defined:
-
/app/middleware/allRoutes.global.ts— runs before every navigation; logs origin and destination to the console -
/app/middleware/onlySecond.ts— runs only before loading the/secondpage; logs an explanation to the console -
/app/middleware/onlyThird.ts— runs only before loading the/thirdpage; logs an explanation to the console; configured vianuxt.config.ts -
/server/middleware/server.ts— runs before processing each API request; logs the URL (on the server, not in the browser)
The simple client lets you switch between links and observe console output. Triggering an API call is done by clicking the buttons on /first or /second.
Summary
Middleware is great for implementing logic that should run BEFORE server API processing or client page rendering. Client middleware can be bound to specific pages or configured to run globally before any page load. Server middleware always runs before every API call.
By now we’ve covered a large part of Nuxt’s fundamentals. We’ve intentionally spent little time on the underlying JavaScript framework, Vue.js, on which everything is built. We’ve touched on components and composables without going deep. While we won’t match the depth of the official Vue.js documentation, we’ll address the essentials in the forthcomming Vue.js intermezzo.
Top comments (0)