React
Route middleware in Nuxt 3: who runs and when
Nuxt 3 route middleware runs before the page: global, named or inline, and uses navigateTo or abortNavigation to decide whether navigation continues.
Key points
- Global middleware with the .global suffix runs on every navigation; named middleware only where definePageMeta lists it.
- Return navigateTo or abortNavigation: middleware that ends without a return lets the navigation through.
- Middleware runs on the server at first render and in the browser after that, so window and localStorage are not available.
In Nuxt 3 route middleware is a function that runs before the page is shown: it reads the route, can stop it or send the user elsewhere, and returns no data to the component.
Three types, three moments
Global middleware lives in middleware/ with the .global suffix and runs on every navigation; named middleware runs only where definePageMeta lists it; middleware written inside definePageMeta covers a single page.
npx nuxi add middleware auth
// middleware/auth.ts
export default defineNuxtRouteMiddleware((to) => {
const { user } = useAuth();
if (!user.value && to.path !== "/login") {
return navigateTo(`/login?next=${to.fullPath}`);
}
});
Inside the page the list is declared in definePageMeta and it is ordered: Nuxt runs the global one first, then the named ones in the given order, and stops at the first that changes the navigation.
definePageMeta({
middleware: ["auth", "subscription"],
});
The context changes between server and browser
On the first request middleware runs on the server, inside the render, where useCookie reads the request cookie. On later navigations it runs in the browser, and direct database access is no longer available.
// middleware/redirects.global.ts
export default defineNuxtRouteMiddleware((to) => {
if (to.path === "/old-shop") {
return navigateTo("/shop", { redirectCode: 301 });
}
});
The return value matters
Middleware that ends without return lets the navigation through. return navigateTo(...) replaces it, return abortNavigation() cancels it: returning the promise is what makes Nuxt wait when the function is async.
| Type | Where it lives | When it runs |
|---|---|---|
| Global | middleware/name.global.ts | Every navigation, before the named ones |
| Named | middleware/name.ts | Only pages that list it in definePageMeta |
| Inline | definePageMeta({ middleware: [...] }) | One page, with no separate file |
The limitation to plan for
Middleware protects pages, not server routes: a call to /api/orders never passes through it and must be checked inside the handler. And on a page with prerender enabled it does not run at all.
Mistakes you will see
- Forgetting
returnbeforenavigateToin async middleware: navigation continues and the redirect arrives late. - Reading
windoworlocalStoragein middleware: neither exists on the server and the render fails. - Putting a database query in global middleware: it runs on every navigation, even when the data is not needed.
- Relying on middleware to protect the API: server routes have a different entry point and no filter.
The components to put behind middleware are ready: PrimeVue and shadcn-vue install as source, and the login block covers the page middleware redirects to. Project folders are in structuring a Nuxt 3 project, client-side guard order in Vue Router navigation guards, the framework comparison in Vue 3 vs React.
Frequently asked questions
Does middleware run on server routes?
No. Route middleware applies to pages: a call to /api/... reaches the handler without passing through it. Repeat the check inside the server route, the only place where the request is visible.
Why does the redirect never fire?
In most cases the return is missing: in async middleware the caller has to await the promise. Write return navigateTo(...) instead of calling the function and carrying on.
Global or named?
Global only for rules that apply to every page, such as language or a domain redirect. Everything else belongs in named middleware, because global middleware pays its cost on each navigation.
Where do I read cookies?
With useCookie, which works on the server and in the browser. document.cookie exists only in the browser and is undefined during server rendering.
Can I stop navigation without a redirect?
Yes, with return abortNavigation(). The current route stays and no error is shown: it suits an action already in progress, not a denied permission.