middleware
Nuxt 提供一个可自定义的路由中间件(route middleware)框架,可在整个应用中使用,适合提取你希望在导航到特定路由之前运行的代码。
路由中间件有三种类型:
- 匿名(或内联)路由中间件,直接在页面内定义。
- 命名路由中间件,放置在
app/middleware/
中,使用时会通过异步导入自动加载。 - 全局路由中间件,放置在
app/middleware/
中并带有.global
后缀,会在每次路由变化时运行。
前两种路由中间件可以在 definePageMeta
中定义。
myMiddleware
会变为 my-middleware
。使用方法
路由中间件是导航守卫,会接收当前路由和下一个路由作为参数。
export default defineNuxtRouteMiddleware((to, from) => {
if (to.params.id === '1') {
return abortNavigation()
}
// 在真实应用中你可能不会把每个路由都重定向到 `/`
// 但在重定向前检查 `to.path` 是很重要的,否则可能会造成无限重定向循环
if (to.path !== '/') {
return navigateTo('/')
}
})
Nuxt 提供了两个可以在中间件中直接返回的全局可用辅助函数。
navigateTo
- 重定向到指定路由abortNavigation
- 中止导航,可带可选错误信息。
与 vue-router
的 导航守卫 不同,中间件不会传入第三个 next()
参数,重定向或取消路由是通过从中间件返回一个值来处理的。
可能的返回值有:
- 无返回(简单的
return
或根本不返回) - 不会阻止导航,会继续下一个中间件(如果有)或完成路由导航 return navigateTo('/')
- 重定向到给定路径;若重定向在服务器端发生,则会将重定向状态码设置为302
Foundreturn navigateTo('/', { redirectCode: 301 })
- 重定向到给定路径;若重定向在服务器端发生,则会将重定向状态码设置为301
Moved Permanentlyreturn abortNavigation()
- 停止当前导航return abortNavigation(error)
- 使用错误拒绝当前导航
中间件执行顺序
中间件按以下顺序运行:
- 全局中间件
- 页面定义的中间件顺序(如果使用数组语法声明了多个中间件)
例如,假设你有以下中间件和组件:
-| middleware/
---| analytics.global.ts
---| setup.global.ts
---| auth.ts
<script setup lang="ts">
definePageMeta({
middleware: [
function (to, from) {
// 自定义内联中间件
},
'auth',
],
})
</script>
你可以预期中间件会按以下顺序运行:
analytics.global.ts
setup.global.ts
- 自定义内联中间件
auth.ts
全局中间件的排序
默认情况下,全局中间件按文件名的字母序执行。
但是,有时你可能希望定义一个特定的执行顺序。例如,在上面的场景中,setup.global.ts
可能需要在 analytics.global.ts
之前运行。在这种情况下,我们建议给全局中间件前缀“按字母顺序”的编号。
-| middleware/
---| 01.setup.global.ts
---| 02.analytics.global.ts
---| auth.ts
10.new.global.ts
会排在 2.new.global.ts
之前。这就是示例中在单个数字前加 0
的原因。中间件何时运行
如果你的网站是服务器渲染或生成的,初始页面的中间件会在页面渲染时执行一次,然后在客户端再次执行一次。如果你的中间件需要浏览器环境(例如你有一个生成的站点、对响应进行强缓存,或想从 localStorage 读取值),这可能是需要的。
但是,如果你想避免这种行为,可以这样做:
export default defineNuxtRouteMiddleware((to) => {
// 在服务器上跳过中间件
if (import.meta.server) {
return
}
// 在客户端完全跳过中间件
if (import.meta.client) {
return
}
// 或者仅在初始客户端加载时跳过中间件
const nuxtApp = useNuxtApp()
if (import.meta.client && nuxtApp.isHydrating && nuxtApp.payload.serverRendered) {
return
}
})
即使你在服务器上的中间件中抛出错误并呈现错误页面,该中间件仍会在浏览器中再次运行。
useError
来检查是否正在处理错误。在中间件中访问路由
在中间件中始终使用参数 to
和 from
来访问下一个路由和上一个路由。完全避免在此上下文中使用 useRoute()
组合式函数。
在中间件中不存在“当前路由”的概念,因为中间件可以中止导航或重定向到不同路由。useRoute()
在此上下文中将始终不准确。
useRoute()
的组合式函数,即使中间件中没有直接调用 useRoute()
也会触发此警告。
这会导致与上文相同的问题,因此当这些函数在中间件中使用时,应将路由作为参数传入以重构它们。export default defineNuxtRouteMiddleware((to) => {
// 将路由传递给函数以避免在中间件中调用 `useRoute()`
doSomethingWithRoute(to)
// ❌ 这会输出警告,不推荐这样做
callsRouteInternally()
})
// 将路由作为参数提供,以便在中间件中正确使用
export function doSomethingWithRoute (route = useRoute()) {
// ...
}
// ❌ 这个函数不适合在中间件中使用
export function callsRouteInternally () {
const route = useRoute()
// ...
}
动态添加中间件
可以使用 addRouteMiddleware()
辅助函数手动添加全局或命名路由中间件,例如在插件中。
export default defineNuxtPlugin(() => {
addRouteMiddleware('global-test', () => {
console.log('这个全局中间件在插件中添加,并将在每次路由变化时运行')
}, { global: true })
addRouteMiddleware('named-test', () => {
console.log('这个命名中间件在插件中添加,并会覆盖任何同名的现有中间件')
})
})
示例
-| middleware/
---| auth.ts
在你的页面文件中,可以引用这个路由中间件:
<script setup lang="ts">
definePageMeta({
middleware: ['auth'],
// 或者 middleware: 'auth'
})
</script>
现在,在导航到该页面之前,auth
路由中间件会被运行。
在构建时设置中间件
你也可以不在每个页面使用 definePageMeta
,而是在 pages:extend
钩子中为命名路由中间件添加设置。
import type { NuxtPage } from 'nuxt/schema'
export default defineNuxtConfig({
hooks: {
'pages:extend' (pages) {
function setMiddleware (pages: NuxtPage[]) {
for (const page of pages) {
if (/* 某些条件 */ Math.random() > 0.5) {
page.meta ||= {}
// 注意:这会覆盖页面中 `definePageMeta` 设置的任何中间件
page.meta.middleware = ['named']
}
if (page.children) {
setMiddleware(page.children)
}
}
}
setMiddleware(pages)
},
},
})