首页
/ Nuxt 迁移指南:从 Nuxt 2 迁移 Plugins 与 Route Middleware

Nuxt 迁移指南:从 Nuxt 2 迁移 Plugins 与 Route Middleware

2026-09-07 20:20:49作者:尤辰城Agatha

本文是 Nuxt 迁移系列中针对 插件(Plugins)路由中间件(Route Middleware) 的专门指南,讲解从 Nuxt 2 升级到 Nuxt 3 时这两类机制在 API 形态、注册方式与运行模型上的全部变化。读完本文,你将掌握:如何用 defineNuxtPlugin 重构旧插件并用 nuxtApp.provide 替代 inject、如何利用 ~/plugins 目录自动注册与 .client/.server 文件后缀、如何用 defineNuxtRouteMiddleware 重构路由守卫并用 navigateTo/abortNavigation 取代 redirect/next(),以及如何把全局中间件迁移到 ~/middleware/*.global.ts。文中代码均以当前仓库(Nuxt 主仓库,内含 docs 迁移指南与全部运行时源码)为基准。

迁移总览:两处「参数签名 + 注册方式」的范式变化

在 Nuxt 2 中,插件与路由中间件都依赖「上下文对象 + 副作用注册」的旧范式:插件回调接收 (ctx, inject) 两个参数,通过在 ctx.app 上注入 $ 前缀方法或读写 Vuex store 来工作;中间件同样接收包含 storeredirect 的上下文对象。Nuxt 3 之后这两套 API 被彻底重构,共同点非常清晰:

关注点 Nuxt 2 Nuxt 3+
插件定义 默认导出 (ctx, inject) => {} 默认导出 defineNuxtPlugin((nuxtApp) => {})
注入方法 inject('name', fn) nuxtApp.provide('name', fn) 或插件 return { provide: {...} }
中间件定义 默认导出 ({ store, redirect }) => {} 默认导出 defineNuxtRouteMiddleware((to, from) => {})
重定向 redirect('/login') return navigateTo('/login')
页面引用中间件 组件选项 middleware: 'auth' definePageMeta({ middleware: 'auth' })
客户端/服务端限定 nuxt.configmode: 'client' / 'server' 文件名后缀 .client.ts / .server.ts
全局中间件 nuxt.config 中声明 ~/middleware/auth.global.ts 文件后缀

下面分两大部分详述迁移步骤与背后的源码依据。

插件(Plugins)迁移

新格式:单一参数 nuxtAppinject 变为 provide

Nuxt 3 的插件回调只接收一个参数 nuxtApp(当前 Nuxt 应用实例,类型为 NuxtApp)。原文档给出了最小对照示例:

export default (ctx, inject) => {
  inject('injected', () => 'my injected function')
}
export default defineNuxtPlugin((nuxtApp) => {
  // 现在在 `nuxtApp.$injected` 上可用
  nuxtApp.provide('injected', () => 'my injected function')

  // 也可以使用这种写法,自带自动类型推导
  return {
    provide: {
      injected: () => 'my injected function',
    },
  }
})

注意两点关键差异:

  1. ctx 对象被移除。Nuxt 2 上下文中的 appstoreroute 等在 Nuxt 3 中不再作为参数注入插件。你应改用对应的组合式函数在插件内获取所需能力——例如需要访问 Vue 应用实例时使用 nuxtApp.vueApp,需要读取路由时可在此调用组合式函数(因为插件运行在 nuxtApp 的 effect scope 内,useRouter()useState() 等均可正常使用)。
  2. 自动的 $ 前缀provide 的键会自动带上 $ 前缀挂载。在源码 packages/nuxt/src/app/nuxt.ts 中可以看到 nuxtApp.provide 的实现:它会同时把值定义为 nuxtApp 自身与 vueApp.config.globalProperties 上的 $ 前缀 getter——因此组件模板中可直接使用 $injected,脚本中也可通过 useNuxtApp().$injected 访问:
nuxtApp.provide = (name: string, value: any) => {
  const $name = '$' + name
  defineGetter(nuxtApp, $name, value)
  defineGetter(nuxtApp.vueApp.config.globalProperties, $name, value)
}

推荐用 return { provide } 以获得自动类型

defineNuxtPlugin 的类型签名会从 provide 返回对象的键推断注入内容。在 packages/nuxt/src/app/nuxt.tsdefineNuxtPlugin 被实现为一个几乎「透明」的包装函数——若传入普通函数则原样返回;若传入对象(含 setupnameenforceparalleldependsOn 等字段)则把对象拍平为带元信息的函数,同时保留 NuxtPluginIndicator 标记,供运行时用 isNuxtPlugin 识别:

export function defineNuxtPlugin<T extends Record<string, unknown>> (plugin: Plugin<T> | ObjectPlugin<T>): Plugin<T> & ObjectPlugin<T> {
  if (typeof plugin === 'function') { return plugin }
  // ... 对象语法插件会被归一化为 setup 函数并附带元信息
}

因此官方推荐优先使用「返回 provide 对象」的第二种写法:类型会由返回值自动推导,useNuxtApp() 的返回类型与组件模板中的 $xxx 都能获得完整补全,无需手写 .d.ts 模块声明。仓库 packages/nuxt/src/app/nuxt.ts 中 Nuxt 自身的运行时也遵循这一约定,例如注入 config 时即使用 nuxtApp.provide(key, provide[key]) 遍历合并用户插件返回的 provide

顺带一提,源码还导出 definePayloadPlugin同文件 L520),它是 defineNuxtPlugin 的类型别名,专用于需要在两端(服务端渲染结果与客户端水合)同步 payload 数据的场景,迁移时若你的 Nuxt 2 插件本就是在 payload 层面工作的,可留意这一 API。

迁移步骤

原文档给出了插件迁移的三步清单,结合目录结构与源码可以展开如下:

1. 用 defineNuxtPlugin 包裹插件。 将所有 app/plugins/ 下(或 nuxt.config 中注册的)插件导出改为 export default defineNuxtPlugin((nuxtApp) => { ... }),并把 inject('name', value) 改写为 nuxtApp.provide('name', value)return { provide: { name: value } }defineNuxtPlugin 是自动导入的(见仓库导入预设 packages/nuxt/src/imports/presets.ts),无需手动 import

2. 删除 nuxt.config plugins 数组中位于 app/plugins/ 目录内的条目。 Nuxt 3 会自动注册该目录下的文件:仅顶层文件以及任何子目录中的 index 文件会被扫描(见目录文档 docs/2.directory-structure/1.app/1.plugins.md)。例如:

-| app/plugins/
---| foo.ts       // 自动注册
---| bar/
-----| baz.ts     // 不会自动注册
-----| index.ts   // 自动注册(取父目录名 bar)

如果需要注册子目录中的非 index 文件,才需在 nuxt.config.tsplugins 数组显式声明,如 plugins: ['~/plugins/bar/baz']

3. 用文件名后缀替代 mode 若你的插件原本通过 mode: 'client' / mode: 'server' 限定执行环境,请删除该配置并把模式写进文件名:~/plugins/my-plugin.client.ts 只在客户端加载,~/plugins/my-plugin.server.ts 只在服务端加载。不写后缀则两端都会执行。若插件同时需要「仅客户端指令 + 服务端空实现」,按目录文档建议分别提供 my-directive.client.tsmy-directive.server.ts

进阶:对象语法与加载顺序(额外补充)

如果你的插件原本依赖注册顺序,迁移后要注意 Nuxt 3 的目录文档新增的规则(这些能力同样影响迁移质量):

  • 数字前缀控制顺序01.myPlugin.ts 先于 02.myOtherPlugin.ts 执行;注意文件按「字符串排序」,两位数请补零(10.x 会排在 2.x 前面)。
  • 对象语法支持 enforce: 'pre' | 'post'parallel: true(不必等待前一个插件完成)、dependsOn: ['plugin-name'](显式声明依赖)以及 hooks(直接注册 app 运行时钩子)。这些元信息会被构建期静态分析(见仓库 packages/nuxt/src/core/plugins/plugin-metadata.ts),所以应在编译期写死,不要在运行时用 import.meta.server 动态生成 enforce
  • 一个用于 Vue 生态插件的迁移示例:在 app/plugins/vue-gtag.client.ts 中通过 nuxtApp.vueApp.use(VueGtag, {...}) 挂载 Vue 插件,并可用 useRouter() 追踪路由变化。

路由中间件(Route Middleware)迁移

新格式:(to, from) 守卫,靠返回值而非 next() 控制导航

路由中间件的核心变化是:不再接收包含 store/redirect 的上下文,而是成为标准的 vue-router 导航守卫,接收 to(目标路由)与 from(来源路由),通过返回值声明式地决定导航结果。原文档的对照示例(典型的登录鉴权场景):

export default function ({ store, redirect }) {
  // 如果用户未认证
  if (!store.state.authenticated) {
    return redirect('/login')
  }
}
export default defineNuxtRouteMiddleware((to, from) => {
  const auth = useState('auth')
  if (!auth.value.authenticated) {
    return navigateTo('/login')
  }
})

上例同时示范了两个迁移要点:

  1. Vuex store.state 不再可注入。全局状态请改用 useState('auth')(在 docs/4.api/2.composables/use-state.md 可查看其用法),或改用 Pinia 并借助 useState 在其上建立 SSR 安全的全局单例;这一思路在迁移系列的配置篇 docs/7.migration/2.configuration.md 中也有对应说明。
  2. 重定向由「调用副作用」改为「返回导航结果」。在 packages/nuxt/src/app/composables/router.ts 中,中间件的类型定义如下——返回值兼容 vue-router 的 NavigationGuard,但官方建议只使用 Nuxt 提供的辅助函数:
export interface RouteMiddleware {
  (to: RouteLocationNormalized, from: RouteLocationNormalized): ReturnType<NavigationGuard>
}

export function defineNuxtRouteMiddleware (middleware: RouteMiddleware): RouteMiddleware {
  return middleware
}

中间件可返回的值及含义(详见中间件目录文档 docs/2.directory-structure/1.app/1.middleware.md):

  • 什么都不返回 —— 不阻断导航,继续执行下一个中间件或完成路由跳转;
  • return navigateTo('/') —— 重定向,服务端发起时为 302 FoundnavigateTo 文档);
  • return navigateTo('/', { redirectCode: 301 }) —— 以 301 Moved Permanently 重定向;
  • return abortNavigation() —— 中止当前导航;
  • return abortNavigation(error) —— 中止导航并携带错误(abortNavigation 文档)。

navigateTo 只是路由辅助函数之一,同类还有 abortNavigationaddRouteMiddleware 等(在仓库中它们与中间件定义同处于 packages/nuxt/src/app/composables/router.ts)。重定向时请留意防止死循环:例如先判断 to.path !== '/login' 再重定向。

页面如何引用中间件:从组件选项到 definePageMeta

与 Nuxt 2 相同,放在 ~/middleware 目录(即 app/middleware/)中的中间件文件会被自动注册,之后即可在页面里按名字引用。区别在于:Nuxt 2 用组件选项 middleware: 'auth',Nuxt 3 用编译宏 definePageMeta

<script setup lang="ts">
definePageMeta({
  middleware: 'auth',
  // 或数组形式,按声明顺序执行:
  // middleware: [function (to, from) { /* 内联匿名中间件 */ }, 'auth'],
})
</script>

注意两点由源码与目录文档共同确认的规则:

  • 中间件名会被规范为 kebab-case:文件 myMiddleware.ts 注册名为 my-middleware
  • 只注册目录顶层文件与子目录的 index 文件,且 middleware/auth/index.ts 会以父目录名 auth 注册。若使用了更深层目录的中间件文件,需通过 definePageMeta 引用对应名称或用 addRouteMiddleware 手动注册。

中间件(含自动导入的 defineNuxtRouteMiddlewarenavigateTo 等)都在 ~/middleware~/pages~/plugins 内自动导入,无需显式 import(见 packages/nuxt/src/imports/presets.ts)。

在中间件内访问路由的注意事项

中间件内应始终通过 to/from 参数访问路由,不要在中间件内部调用 useRoute()——中间件执行期间并不存在确定的「当前路由」,因为导航可能被重定向或中止。若你封装的工具函数内部隐式调用了 useRoute(),也会触发这一隐患。因此最佳实践是把路由作为参数传入辅助函数,例如把逻辑抽到 utils/handle-route.ts 时采用 export function doSomethingWithRoute (route = useRoute()) 的形式。

全局中间件:.global 后缀替代 nuxt.config 声明

原文档的第二条迁移说明指出:原先声明在 nuxt.config 中的全局中间件(例如 router: { middleware: ['auth'] } 风格)应迁移到 ~/middleware 目录,并在文件名上追加 .global 后缀:

-| app/middleware/
---| auth.global.ts       // 每次路由变化都会执行
---| profile.ts           // 具名中间件,仅在页面引用时执行

运行时在 packages/nuxt/src/app/plugins/router.ts(客户端路由插件)中会把构建期产物 #build/middleware 中的全局中间件与运行时通过 addRouteMiddleware 追加的全局中间件合并执行:先全局、后页面声明的具名/内联中间件。源码中的内部数据结构 _middleware: { global: RouteMiddleware[]; named: Record<string, RouteMiddleware> }packages/nuxt/src/app/nuxt.ts)清晰反映了「全局数组 + 具名字典」的存储模型。

关于全局中间件顺序与执行时机的几个迁移要点:

  • 全局中间件默认按文件名字母序执行,可用数字前缀强制排序(如 01.setup.global.ts02.analytics.global.ts,注意字符串排序需补零);
  • SSR/静态生成时,首次进入页面的中间件会在服务端渲染和客户端水合后各执行一次。若中间件依赖浏览器环境(如读 localStorage),可在内部用 import.meta.server / import.meta.clientuseNuxtApp().isHydrating 判断跳过;渲染错误页属于一次全新的页面加载,已注册的全局中间件会再次执行,可用 useError() 感知错误上下文;
  • 想在插件中动态注册中间件(替代以往在 nuxt.configrouter.extendRoutes 中拼装逻辑的做法),可使用 addRouteMiddlewaredocs/4.api/3.utils/add-route-middleware.md):
export default defineNuxtPlugin(() => {
  addRouteMiddleware('global-test', () => {
    console.log('每次路由变化都会运行的全局中间件')
  }, { global: true })
})

迁移步骤

  1. 把中间件改写为 defineNuxtRouteMiddleware((to, from) => {}):将上下文对象参数替换为路由守卫参数,redirect() 换成 return navigateTo(...)store 相关状态改用 useState 等组合式函数,next() 语义替换为按需 returnreturn abortNavigation()
  2. 把全局中间件从 nuxt.config 移到 ~/middleware 目录,并以 .global 后缀结尾,例如 ~/middleware/auth.global.ts
  3. 把页面中引用中间件的组件选项改为 definePageMeta(该宏在编译期被静态分析,只接受字面量数组/字符串,见 packages/schema/src/config/build.ts 中的异步转换配置);同时留意中间件名统一为 kebab-case,并核对 app/middleware/ 的顶层/index 扫描规则。

小结

从 Nuxt 2 升级到 Nuxt 3 的过程中,插件与路由中间件的迁移可以概括为两条主线:插件从「上下文 + inject」走向「nuxtApp 单参数 + provide/返回对象」,并全面依赖 ~/plugins 目录自动注册与 .client/.server 文件后缀来替代手动 mode 配置;路由中间件从「{ store, redirect } 副作用式守卫」走向「(to, from) + 返回值导航」的纯函数式守卫,配合 ~/middleware/*.global.tsdefinePageMeta 完成注册与引用。迁移完成后,可继续阅读目录结构篇的 plugins 目录文档middleware 目录文档,或在源码 packages/nuxt/src/app/nuxt.tspackages/nuxt/src/app/composables/router.tspackages/nuxt/src/app/plugins/router.ts 中追踪这两套机制的完整实现。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.13 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
529
593
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
915
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.35 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
515
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388