If you are reading this article, it likely means you're already familiar with Nuxt.js. And if that's the case, then you must also be familiar with Vue.js. After all, it's impossible to learn Nuxt without first learning Vue, right?
In Vue.js, an average redirect is often handled with middleware. You typically create a separate JavaScript file containing some logic, attach it to a route as middleware, and ensure that this logic runs whenever someone navigates to that specific page.
For example, someone trying to visit an outdated URL might be redirected to the correct or updated version of the page.
export default function (to, from, next) {
next('/new-url')
}
Nuxt 3 also supports middleware, and there's a whole section in the documentation about it. In a typical Nuxt 3 project, you'll find a middleware folder in the root. Inside this folder, you can create your custom middleware files. To define one, you use a built-in function defineNuxtRouteMiddleware that expects two parameters: "to" for the target route and "from" for the origin route.
Unlike Vue, Nuxt middleware doesn’t require a “next” function. Instead, it provides two helper functions: “navigateTo” to navigate to a new route and “abortNavigation” to abort navigation altogether. That same Vue.js middleware we saw earlier can now look like this in a Nuxt 3 project.
///middleware/redirect-to-new-url.js
export default defineNuxtRouteMiddleware((to, from) => {
return navigateTo('/new-url')
})
Another improvement in Nuxt 3 is the ability to specify whether a redirect is temporary or permanent. This can be especially useful for SEO, as permanent redirects help search engines update their indexed links accordingly.
///middleware/redirect-to-new-url.js
export default defineNuxtRouteMiddleware((to, from) => {
return navigateTo('/new-url', { redirectCode: 301 })
})
You can choose whether a middleware runs globally (for all routes) or only for a specific page. To apply it globally, simply include the word “global” in the file name (redirect-to-new-url.global.js). If you only want it to run for a specific route, you can declare it within that page’s metadata.
<!-- pages/old-url.vue-->
<script setup lang="ts">
definePageMeta({
middleware: 'redirect-to-new-url', //or ['redirect-to-new-url']
});
</script>
This approach works well for in-app redirects based on conditions — such as checking if a user is authenticated or whether a page is enabled.
But what if you want to make a true, permanent redirect from an old URL to a new one? In this case, that old URL doesn’t really belong to your current Nuxt app. You don’t expect any internal navigation to lead there. Instead, these visits usually come from external sources — an old blog post, a social media share, or simply a well-indexed search engine result.
While you could still use Nuxt middleware for this, it requires your app to boot up the full Vue environment before executing the redirect. This isn’t ideal, especially if performance and SEO are important to you.
Luckily, Nuxt 3 provides a better way through its route rules feature. With route rules, you can configure permanent or temporary redirects at the server level — before Vue even loads.
//nuxt.config.js
export default defineNuxtConfig({
routeRules: {
'/old-url': {
redirect: {
statusCode: 301,
to: '/new-url'
}
},
'/another-old-url': {
redirect: '/another-new-url', //this one works too
}
}
})
This means when someone clicks on an outdated link, the browser sends a request to the server, and Nitro immediately responds with a redirect — without loading the Vue app. This makes the whole process faster, more efficient, and ideal for static redirects that don’t require any dynamic logic.
However, if your redirect does depend on some logic — like user-agent detection or IP-based conditions — you’ll need to use server middleware. Yes, in Nuxt 3, there are two types of middleware: those that run inside the Vue app and those that run in Nitro. Server (Nitro) middleware is defined inside a special folder “/server/middleware” and is executed on every request to the server.
//server/middleware/redirect-from-old-to-new-url.js
import { getRequestURL, sendRedirect } from 'h3';
export default defineEventHandler((event) => {
const url = getRequestURL(event);
if (url.pathname === '/old-url') {
return sendRedirect(event, 'https://domain.com/new-url', 301);
}
});
Unfortunately, it can’t be tied to a specific URL, so you’ll need to manually check whether the request matches your target path and handle it accordingly.
There’s one more option: server routes. With server routes, you define logic that matches a specific URL path (“server/routes/old-url.js”). If someone requests that URL, your custom route handler is executed. This lets you manage redirects without the need for conditional checks, making it ideal for cases with multiple static redirects.
//server/routes/old-url.js
import { sendRedirect } from 'h3';
export default defineEventHandler((event) => {
return sendRedirect(event, 'https://domain.com/new-url', 301);
});
Nuxt 3 gives you different ways to handle redirects — inside the app, at the server level, or before the app even loads. Use in-app middleware for conditional redirects, route rules for simple and fast ones, and server routes when you need more control. Pick the method that fits your case best.
If you want to learn how to build fast, SEO-friendly web apps with Nuxt 3 — step by step — 👉 Check out my course here.
Top comments (0)