首页
/ Nuxt 4 文件系统路由实战指南:从页面文件到路由、中间件与校验的完整机制

Nuxt 4 文件系统路由实战指南:从页面文件到路由、中间件与校验的完整机制

2026-09-05 20:36:52作者:伍希望

本篇技术指南围绕 Nuxt 的路由系统展开:文件系统路由如何把 app/pages/ 下的每个页面文件转换为 URL、<NuxtLink> 的导航与自动预取机制、useRoute() 的取值方式、三类路由中间件的编写与加载,以及基于 validate 的路由校验。读完后,你不仅能掌握这些路由能力的日常用法,还能对照仓库源码(pages 模块路由插件NuxtLink 实现)理解其底层调用链,在性能调优、登录保护、非法 URL 拦截等场景下做出准确的技术决策。

文件系统路由:每个页面文件就是一个路由

Nuxt 的核心特性之一是文件系统路由器:app/pages/ 目录中的每一个 Vue 文件都会生成一条对应的 URL(即路由),用于展示该文件的内容。Nuxt 的路由基于 vue-router(文档表述;vue-router 为外部依赖,此处仅作背景说明),并通过为每个页面使用动态导入来实现代码分割(code-splitting),只为请求的路由下发最少的 JavaScript。

命名约定决定了动态路由与嵌套路由的生成方式。以下目录结构:

-| pages/
---| about.vue
---| index.vue
---| posts/
-----| [id].vue

会生成如下的路由表:

{
  "routes": [
    { "path": "/about",   "component": "pages/about.vue" },
    { "path": "/",       "component": "pages/index.vue" },
    { "path": "/posts/:id", "component": "pages/posts/[id].vue" }
  ]
}

从源码结构看,路由的生成由 pages 模块 驱动:模块在 setup 中通过 getLayerDirectories 收集所有层的 app/pages 目录,并用 picomatch 将页面匹配模式(默认 **/*{.vue,...})编译为快速匹配器供文件监听使用;开发模式下通过 createPagesContext 维护一棵持久路由树,在页面文件新增/删除时做增量更新,异常时回退到 resolvePagesRoutes 全量重建(见 module.ts 中 watch 回调)。

关于代码分割:默认开启,慎关

文档明确建议:代码分割默认开启,适用于大多数应用。如果你有特殊原因需要下发单一 bundle,可以在 nuxt.config 中禁用它:

export default defineNuxtConfig({
  vite: {
    $client: {
      build: {
        rolldownOptions: {
          output: {
            codeSplitting: false,
          },
        },
      },
    },
  },
})

官方文档对此给出了非常直接的告诫:单一 bundle 通常会增加首次下载量(即使在慢速连接下也如此),因此只有在实际测量确认它对你的场景有益时才应禁用代码分割。这是一个"先测量、后优化"的典型判断。

页面导航与自动预取:<NuxtLink> 的工作原理

<NuxtLink> 组件负责页面之间的链接跳转。它渲染一个带 href 属性的 <a> 标签,href 指向目标页面的路由;当应用完成水合(hydration)后,页面切换由 JavaScript 更新浏览器 URL 完成,避免整页刷新,并支持动画过渡。

<template>
  <header>
    <nav>
      <ul>
        <li><NuxtLink to="/about">About</NuxtLink></li>
        <li><NuxtLink to="/posts/1">Post 1</NuxtLink></li>
        <li><NuxtLink to="/posts/2">Post 2</NuxtLink></li>
      </ul>
    </nav>
  </header>
</template>

<NuxtLink> 在客户端进入视口(viewport)时,Nuxt 会自动预取目标页面的组件与 payload(针对已生成页面),从而加快导航速度。

NuxtLink 实现 可以看到预取的完整机制:

  • 视口触发shouldPrefetch('visibility') 为真时,组件 onMounted 后在 requestIdleCallback 空闲时段启动一个 IntersectionObserver(见 useObserver),元素进入视口即调用 prefetch() 并取消观察。
  • 交互触发prefetchOn: 'interaction' 时,会绑定 onPointerenter / onFocus 事件触发预取(见 nuxt-link.ts#L585-L588)。
  • 慢网络保护isSlowConnection() 检测 navigator.connection,当 saveData 开启或 effectiveType 为 2G 时跳过预取(见 nuxt-link.ts#L788-L797)。
  • 预取动作prefetch() 会调用 link:prefetch 钩子并执行 preloadRouteComponents 预加载路由组件(见 nuxt-link.ts#L471-L488)。而 prefetch 插件link:prefetch 钩子中进一步预取布局命名中间件(只预取字符串形式的 meta.middleware),并触发组件岛(islands)的 __nuxt_prefetch 钩子——也就是说,预取的不仅仅是页面组件本身。
  • SSR 差异:服务端渲染时内部链接被渲染为静态 <a> 标签(renderStaticInternalLink),不创建 RouterLink 实例、也不挂预取逻辑,保证无 JS 环境下链接依然可用。

组件还暴露了丰富的属性用于控制预取行为,如 prefetchnoPrefetchprefetchOn: 'visibility' | 'interaction'prefetchedClass(预取完成后可用于改变样式)、externaltrailingSlash 等(见 NuxtLinkProps 类型定义)。

路由参数:useRoute() 的底层实现

在页面的 <script setup> 块或组件的 setup() 方法中,可以调用 useRoute() 访问当前路由的详细信息:

<script setup lang="ts">
const route = useRoute()

// 访问 /posts/1 时,route.params.id 为 1
console.log(route.params.id)
</script>

从源码看,useRoute() 返回的是 useNuxtApp()._route(或页面上下文注入的 PageRouteSymbol),见 composables/router.ts。而 _route 的构建在 路由插件 中:它是一个指向 router.currentRoute 的响应式代理对象,通过 Object.defineProperty 把 vue-router 路由对象的所有键(paramsquerypathmeta 等)逐一转发为 getter,并额外暴露 sync() 方法。路由对象在页面导航完成前会被"挂起"(suspend),通常由 <NuxtPage> 内的 Suspense.onResolve 重新同步,以保证异步数据与路由参数的一致性。

路由插件的核心流程同样值得了解(router.ts):

  1. 根据 app.baseURLhashMode 选择 createWebHistory / createWebHashHistory(客户端)或 createMemoryHistory(服务端);
  2. 使用构建期生成的 #build/routes 虚拟模块作为路由表,允许 router.options.ts 通过 routes 钩子二次加工;
  3. 注册 beforeEach 全局守卫执行中间件链、afterEach 处理 404 与错误清理、onError 恢复状态。

路由中间件:匿名、命名与全局三种形态

Nuxt 提供了一套可定制的路由中间件框架,适合抽取那些"在导航到某个路由之前必须执行"的逻辑(鉴权、权限、重定向等)。需要特别注意两个边界(来自官方文档的强调):

  • 路由中间件运行在 Nuxt 应用的 Vue 层。尽管名字相似,它与运行在 Nitro 服务器层的服务端中间件是完全不同的东西;
  • 路由中间件不会为服务器路由(如 /api/*)或其他服务器请求运行。要拦截这些请求,请使用 server middleware

三类路由中间件:

  1. 匿名(内联)中间件:直接定义在使用它的页面中;
  2. 命名中间件:放置在 app/middleware/ 目录,在页面中被引用时通过异步导入自动加载。(注意:中间件名会规范化为 kebab-case,someMiddleware 会变成 some-middleware;构建期生成的 types/middleware.d.ts 中的 MiddlewareKey 联合类型由这些名称推导,见 module.ts#L858-L880。)
  3. 全局中间件:放置在 app/middleware/ 目录,文件名带 .global 后缀,每次路由切换都会自动执行。

auth 中间件保护 /dashboard 页面为例:

export default defineNuxtRouteMiddleware((to, from) => {
  // isAuthenticated() 是一个示例方法,用于验证用户是否已认证
  if (isAuthenticated() === false) {
    return navigateTo('/login')
  }
})
<script setup lang="ts">
definePageMeta({
  middleware: 'auth',
})
</script>

<template>
  <h1>Welcome to your dashboard</h1>
</template>

底层执行链路可以追溯到 router.ts 的 beforeEach 守卫

  • 中间件集合 = 全局中间件(globalMiddleware,来自 #build/middleware)+ 各匹配组件 meta.middleware + routeRules.appMiddleware 声明的中间件;
  • 每个中间件在 nuxtApp.runWithContext 中调用,保证上下文可用;
  • 返回值的语义严格遵循 vue-router 导航守卫约定:返回 false 取消导航(服务端下转为 404 错误),返回路由对象则重定向,抛出/返回 fatal 错误则触发 showError 并把目标 URL 推入 history 以便浏览器返回键正确工作;
  • 若命名中间件找不到实现,开发期会抛出 NUXT_E2004 诊断错误并列出可用的中间件名(见 router.ts#L276-L280)。

另外值得注意:Nuxt 会在应用中间件列表头部自动插入一个名为 validate 的全局中间件(见 module.ts#L489-L499),它就是下一节"路由校验"的执行者。

路由校验:validate 属性与 404 语义

Nuxt 通过 definePageMeta() 中的 validate 属性提供路由校验能力(详见 define-page-meta)。validate 接收 route 作为参数,你可以返回一个布尔值决定该路由是否应由此页面渲染;返回 false 会触发 404 错误;也可以直接返回带 status / statusText 的对象来自定义错误。如果用例更复杂,文档建议使用匿名路由中间件代替。

<script setup lang="ts">
definePageMeta({
  validate (route) {
    // 检查 id 是否由数字组成
    return typeof route.params.id === 'string' && /^\d+$/.test(route.params.id)
  },
})
</script>

校验的实现非常精简,位于 runtime/validate.ts:作为上述自动注入的 validate 全局中间件,它读取 to.meta.validateawait 其结果;true 则放行,否则通过 createError 构造错误——statusresult.status 或默认 404statusTextresult.statusTextPage Not Found: ${to.fullPath},客户端侧标记为 fatal 以中断渲染。这也解释了为什么 validate 支持"返回对象自定义错误":对象上的 status/statusText 字段会被直接透传给错误构造器。

小结:路由系统的分层视图

能力 用户侧 API 源码落点
文件 → 路由生成 app/pages/** 目录约定 pages 模块
路由器创建与守卫 router.options.ts(可选) 路由插件
链接与预取 <NuxtLink> nuxt-link.tsprefetch 插件
路由参数 useRoute() composables/router.ts
中间件链 defineNuxtRouteMiddleware / definePageMeta({ middleware }) router.ts beforeEach
路由校验 definePageMeta({ validate }) runtime/validate.ts

这套设计把"文件系统约定"(生成路由)、"导航守卫"(中间件与校验)与"性能优化"(按路由代码分割 + 视口预取)三者整合在同一条链路上:路由表由构建期扫描生成并注入虚拟模块,运行时插件负责中间件执行与错误处理,<NuxtLink> 则在用户点击之前就把组件、布局和中间件的加载提前完成。理解这条链路后,无论是扩展自定义路由、诊断 404 来源,还是关闭慢网预取,都能快速定位到正确的配置或代码位置。

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