Release·  

Nuxt 4.5

Nuxt 4.5 是我们相当长一段时间以来最大的一次发布。Vite 8、由 Rsbuild 驱动的 Rspack 2、实验性的 SSR 流式传输、稳定的错误代码系统、新的 useLayout 组合式函数、命名视图,以及为 Nuxt 5 做的大量基础工作。
Daniel Roe

Daniel Roe

@danielroe.dev

Nuxt 4.5 是一个重磅版本。本次发布为构建层带来了三项重大升级(Vite 8、Rspack 2,以及由 Rsbuild 驱动、专为 Rspack 构建器打造的全新流水线),还包括实验性的 SSR 流式传输模式、稳定的错误代码系统、一些新的组合式函数和约定,以及大量让我们更接近 Nuxt 5 的基础工作。

这里有很多内容,所以先去拿杯咖啡吧。☕️

📣 一些新闻

为 Nuxt 5 做准备

这个版本相当大一部分内容都是为 Nuxt 5 做的(希望如此)不可见的底层工作。我们已经切换到几个核心依赖的最新主要版本(unhead v3、unctx v3,以及 Vite 8),把框架自身的构建切换到了 tsdown,并引入了稳定的 nuxt/* 构建输出契约以及开发导出,使得 Nuxt monorepo 中的类型检查无需构建步骤即可工作(#35463#35605)。

这些工作中的很大一部分是在内部缩小 v4 和 v5 之间的差距,这样迁移就能尽可能“无聊”(?)。

如果你想测试 Nuxt v4 的一些破坏性变更,你现在就可以通过 future.compatibilityVersion: 5 选择启用。随着升级指南内容陆续更新,请留意其中的详细信息。

随着 Nuxt v4.5 的发布,我们团队的重点将转向稳定 Nuxt v5,并创建兼容性工具,以尽可能让升级过程顺畅。

Nuxt 3 生命周期结束

Nuxt 3 将于 2026 年 7 月 31 日 结束生命周期,因此这将是我们发布的最后几个 3.x 版本之一。如果你仍在使用 v3,现在是迁移过去的好时机。大多数用户告诉我们,从 v3 升级到 v4 非常顺利,而且我们一直在持续更新升级指南

除了 v4.5.0 之外,我们还将为 3.x 版本线发布一个维护补丁(v3.21.9),将本次发布中兼容的 bug 修复和较小改进回移过去。这里的重点项目(Vite 8、Rspack 2、unhead v3、unctx v3)都是主要升级,并且只保留在 v4 中,因此随着 3.x 接近生命周期结束,它仍将保持稳定。

⚡️ Vite 8

Nuxt 现在运行在 Vite 8 上(#34256)。这带来了更快的冷启动、最新的基于 Rolldown 的内部实现,以及来自 Vite 团队的许多上游改进。

对于大多数应用来说,这是一次无感升级。如果你有自定义的 Vite 插件或配置,建议快速浏览一下 Vite 迁移指南,看看是否有任何内容会影响你。

Vite 8 是一个重大版本升级。如果你直接依赖 Vite(自定义插件、vite.config 调整,或锁定 Vite 版本的生态插件),请在生产环境升级前确保它们兼容。

🦀 Rspack 2 和 Rsbuild

如果你使用 Rspack 构建器,这个版本是一次重大升级。我们已经迁移到 Rspack 2#34929),它更快、更轻量,并且基于 @rsbuild/core 重新构建了该构建器(#35489)。

对外的接口保持不变。你仍然可以通过 builder: 'rspack' 来启用它,现有的 rspack:* 钩子也会继续生效:

nuxt.config.ts
export default defineNuxtConfig({
  builder: 'rspack',
})

不过在底层,很多东西都已发生了有益的变化:

  • 开发服务器现在通过 Rsbuild 以 middleware 模式运行,取代了 webpack-dev-middlewarewebpack-hot-middleware#35575)。
  • 我们使用了一个 Rspack 专用的 Vue loader,以正确处理 SSR 的 scoped-style id,并实现更严格的 ESM 解析(#35566)。
这是为一流的 Rsbuild 支持打下的基础。我们目前仍保留构建器名称 rspack,以免造成任何破坏,但底层内部现在已经完全是 Rsbuild 了。

🌊 实验性 SSR 流式传输

这是我尤其兴奋的一个功能。现在你可以启用 SSR 流式传输,从而大幅提升首字节时间(Time to First Byte,#34411)。Nuxt 不再把整个渲染后的页面缓冲完再一次性发送,而是会立即刷出 HTML 外壳(你的 <head>、样式、预加载提示和入口脚本),然后在 Vue 渲染时继续流式发送 body。

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    ssrStreaming: true,
  },
})

对于机器人和爬虫,流式传输会自动禁用,因此搜索引擎仍然会收到完整渲染后的 HTML。你可以调整哪些用户代理会被视为爬虫,并且可以让单个路由不使用流式传输:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    ssrStreaming: {
      botRegex: /googlebot|bingbot|my-internal-crawler/i,
    },
  },
  routeRules: {
    '/no-stream/**': { streaming: false },
  },
})

在开启之前,有一件事值得先了解。由于流式传输会在第一个字节发出时就提交 HTTP 状态和响应头,任何在渲染开始后才修改响应的操作——比如在 <script setup> 里调用 setResponseStatus(),或者在中间件期间写入 cookie 等——都无法传递到客户端。Nuxt 已经帮你处理了常见情况:带有 redirectcacheisrswrnoScriptsssr: false 规则的路由会自动回退到缓冲式渲染器;在开发环境中,我们还会输出警告并注明任何被丢弃的修改,这样就不会出现静默失败。

SSR 流式传输是实验性的,默认关闭。对于 TTFB 很重要的内容型路由,它可能非常有价值,但在大范围上线前,请先用你的应用测试它对响应修改逻辑(状态码、响应头、cookie)的影响。实验性功能文档 详细说明了回退规则和注意事项。
Docs > Guide > Going Further > Experimental Features#ssrstreaming 中查看详情

🩺 稳定错误代码

这一点我真的很高兴。Nuxt 现在有了一个 稳定的错误代码系统#35429),使用的是 nostics。在构建期间和运行时产生的警告与错误现在都会带有一个稳定的代码(例如 NUXT_E1001NUXT_B5001)、一个关于它为什么发生的简短说明,以及一个可以尝试的具体修复方案。

每个代码都可以用于 grep 搜索并且可加入书签,而那些需要超过一行修复的错误会直接链接到专门的文档页面。比如,经典的“在 Nuxt 上下文之外调用了 composable”现在会显示为 NUXT_E1001,并在内联中给出原因/修复说明,同时附带一个 docs page,解释上下文规则以及如何使用 runWithContext()

为了保持生产环境输出精简,详细的原因/修复文本会在生产构建中被移除,只保留稳定的代码。

这是 Nuxt 各处错误信息显著改进的基础,在接下来的几个版本中,我们也会继续把现有的警告和错误迁移到这个系统上。🔥

🎨 useLayout 组合式函数

现在新增了一个 useLayout 组合式函数,用于读取当前路由已解析出的布局(#35623)。此前,还没有一种简洁、响应式的方式能让你在组件内部询问“这个页面正在使用哪个布局?”。

app/components/LayoutBadge.vue
<script setup lang="ts">
const layout = useLayout()
</script>

<template>
  <span>当前布局:{{ layout }}</span>
</template>

它返回一个只读的计算属性 ref,因此当你进行路由导航,或者路由规则和 definePageMeta 更改了已解析的布局时,它都会保持同步。

Docs > API > Composables > Use Layout 中查看详情

🪟 命名视图

Nuxt 现在通过文件命名约定支持 命名视图 (#35123)。如果父页面渲染了多个 <NuxtPage> 插槽,你可以为每个插槽指定一个名称,并使用 name@view.vue 约定为其提供一个同级页面文件:

目录结构
-| pages/
---| parent/
-----| child.vue
-----| child@sidebar.vue
---| parent.vue
pages/parent.vue
<template>
  <div>
    <NuxtPage />
    <aside>
      <NuxtPage name="sidebar" />
    </aside>
  </div>
</template>

导航到 /parent/child 时,会将 child.vue 渲染到默认插槽,并将 child@sidebar.vue 渲染到 sidebar 插槽。实际上,这在 Vue Router 中已经可以实现很久了;这个版本只是将其接入了 Nuxt 基于文件的路由系统。

definePageMeta 只会从默认路由文件中读取,并且不支持按视图设置渲染模式(父页面的模式会应用于默认视图)。
Docs > Guide > Directory Structure > App > Pages#named Views V45 中查看详情

🚦 useFetchuseAsyncDataenabled 选项

你现在可以使用响应式的 enabled 选项来控制数据获取(#33260)。当 enabledfalse 时,所有执行都会被阻止(初始获取、execute/refresh 以及 watch 触发),如果你在请求进行中把它从 true 切换为 false,正在进行的请求会被取消,同时不会清空你现有的 data

app/pages/search.vue
<script setup lang="ts">
const query = ref('')

const { data } = await useFetch('/api/search', {
  query: { q: query },
  // 仅在用户输入内容后才开始获取
  enabled: () => query.value.length > 2,
})
</script>

这非常适合依赖型或条件型查询,因为你不希望在满足某个前置条件之前发起请求。它与 getter 或 ref 配合得很好,因此会保持响应式。

Docs > API > Composables > Use Async Data 中查看详情

当你将 <NuxtLink>custom 属性一起使用时,Nuxt 不再为你自动挂载预取处理器,因为它无法知道你的标记结构是如何组织的。为了让这更易用,插槽现在会暴露出你手动接入预取所需的一切 (#34539):

<template>
  <NuxtLink
    v-slot="{ href, navigate, prefetch, prefetched, shouldPrefetch }"
    to="/about"
    custom
  >
    <a
      :href="href"
      :class="{ 'is-prefetched': prefetched }"
      @click="navigate"
      @pointerenter="shouldPrefetch('interaction') && prefetch()"
      @focus="shouldPrefetch('interaction') && prefetch()"
    >
      关于页面
    </a>
  </NuxtLink>
</template>

你可以使用 prefetch 来触发预取,使用 prefetched 来判断它是否已经发生过(非常适合用于 prefetched 类名),并使用 shouldPrefetch 来遵循用户的连接状态和配置。

Docs > API > Components > Nuxt Link 中查看详情

⚡️ 预取时转发 preload 提示

我们还想邀请你试试另一个功能。当你预取一个带有 payload 提取的路由链接时,Nuxt 已经会为目标页面预先加载数据和代码块。借助新的可选项 experimental.prefetchPreloadTags#35144),它还会把目标页面的 <link rel="preload">modulepreload 提示(无论是页面通过 useHead 设置的,还是通过 @nuxt/image<NuxtImg preload> 等模块设置的)转发到当前文档中,并降级为 rel="prefetch",这样它们就不会与用户当前正在查看的资源竞争。

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    prefetchPreloadTags: true,
  },
})

这样做的实际效果是:下一页中较重的首屏资源(比如主视觉图片、关键脚本)会在用户仍停留在当前页面时就开始下载,因此页面切换会感觉非常迅速。由于我们还在收集反馈,它默认是关闭的,所以请试试看,并告诉我们它在你的应用中表现如何。

🌐 import.meta.envName

已解析的 Nuxt 环境名称现在可以在运行时通过 import.meta.envName 获取,适用于 Vite 和 webpack/Rspack 构建(#34844)。这是由 --envName 设置的值(或解析后的默认值),因此你可以在应用代码中根据它进行分支判断:

if (import.meta.envName === 'staging') {
  // 启用仅限 staging 的行为
}
Docs > API > Advanced > Import Meta 中查看详情

🔭 SSR 事件的追踪通道

Nuxt 现在会为其服务器端子系统发布 diagnostics-channel 追踪信息(#35191)。它不带任何预设立场:我们按照 untracing 的命名约定,发出 nuxt.rendernuxt.islandnuxt.datanuxt.plugin 通道,你可以在此基础上构建 OpenTelemetry(或任何其他方案)。它可在 Node、Deno、Bun 和 Cloudflare Workers 中工作。

nuxt.config.ts
export default defineNuxtConfig({
  // 开启 Nuxt 自带的通道
  tracingChannel: true,
})

你也可以按粒度单独启用它。在 Nuxt v5 中,还会有额外的 Nitro 级通道:

nuxt.config.ts
export default defineNuxtConfig({
  tracingChannel: {
    nuxt: true,
  },
})
通道名称、负载结构和选项键在 untracing 注册表逐步稳定期间,仍可能发生变化。

📦 依赖升级

unhead v3

Nuxt 的 head 管理现在运行在 unhead v3 上(#34793)。它更小,内部使用同步引擎,并且开箱即用地为 useHead 提供了更好的类型安全性。这也解锁了 SSR 流式传输。

unhead v3 为 useHead 引入了类型收窄,如果你一直依赖较宽松的 v2 类型,这可能是一个破坏性的类型变更。运行时行为对绝大多数应用来说是兼容的,而且不再支持 promise 输入(在 v2 中已弃用)。如果你在升级后遇到类型错误,它们几乎总是真正的收紧,而不是回归。

unctx v3

我们已经迁移到 unctx v3#35541),它解决了一类长期存在的异步上下文问题(#33644)。这也是可组合函数上下文可靠性工作的组成部分,该工作将持续到 v5。

以及更多

我们还将 magic-string 更新到了 v1,将 Babel 更新到了 v8,并在各处引入了最新的 Rolldown。运行 nuxt upgrade --dedupe(见下文)是最简单的方式,可以干净地把这些更新一并拉取进来。

🛠️ Nuxt CLI

此版本整合了来自 @nuxt/cli v3.36 和 v3.37 的一些改进:

  • nuxt module remove:用于卸载模块并清理其配置 (nuxt/cli#1306),是 nuxt module add 的自然搭配。
  • 非交互式 nuxt init:用于脚本和 CI (nuxt/cli#1341),并且现在会尊重模板自身的包管理器,而不是提示选择 (nuxt/cli#1330)。
  • 类型检查引导:如果缺少 vue-tsctypescriptnuxt typecheck 现在会主动为你提供安装它们的选项 (nuxt/cli#1316)。
  • 类型检查的 Golar 支持nuxt typecheck 现在可以使用 Golar 作为 vue-tsc 的替代方案 (nuxt/cli#1362)。如果已安装它(或存在 golar.config.* 文件),它会自动被采用;你也可以通过 --checker=vue-tsc--checker=golar 强制指定检查器:
    nuxt typecheck --checker=golar
    
  • 支持层感知的开发服务器:开发服务器现在会在本地 layer 的 nuxt.config 文件发生变化时重新加载 (nuxt/cli#1345)】【。

🧩 TypeScript 插件和命名布局插槽

Nuxt 的实验性 TypeScript 插件由 @dxup/nuxt 提供支持,此次发布捆绑了一个较新版本。如果你还没尝试过,开启 experimental.typescriptPlugin 后会为你带来一系列编辑器便利:重命名自动导入的组件时,会同步更新所有用法;此外,还支持对 glob 导入、Nitro 路由、definePageMetaruntimeConfig 和类型化路由名称进行“跳转到定义”。

捆绑版本中的新功能是一个可选的 运行时 特性:命名布局插槽 (KazariEX/dxup#20)。你可以在页面中编写顶层的命名插槽模板,并将其转发到当前活动布局中对应的命名插槽。这样,页面就可以向布局的插槽注入内容,而这通常是基于文件的布局所不支持的。

将它与插件一起启用:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    typescriptPlugin: true,
  },
  dxup: {
    features: {
      namedLayoutSlots: true,
    },
  },
})

然后布局就可以暴露命名插槽:

layouts/center.vue
<template>
  <slot />
  <slot name="side" one="one" />
</template>

任何使用该布局的页面都可以填充这些插槽:

pages/about.vue
<script setup lang="ts">
definePageMeta({ layout: 'center' })
</script>

<template>
  <template #side="{ one }">
    这个 "{{ one }}" 来自布局插槽。
  </template>
  <div>关于页面</div>
</template>
Docs > Guide > Going Further > Experimental Features#typescriptplugin 中查看详情

🔥 性能与可靠性

一如既往,为了让 Nuxt 更快、更稳定,我们投入了大量工作。

你今天就可以选择启用的是 共享文件监视器 (#35143)。Vite 已经运行了一个基于 chokidar 的监视器,因此 Nuxt 可以直接复用它,而不是再启动第二个 – 更少的内存占用,更少的文件句柄。该功能会在 compatibilityVersion: 5 时成为默认值,但你现在就可以开启它,并告诉我们使用体验如何:

nuxt.config.ts
export default defineNuxtConfig({
  experimental: {
    watcher: 'builder',
  },
})

还有一些不需要主动开启的性能改进也同样值得关注:

  • 更快的开发启动 – 仅用于开发的 Nitro 和 pages 工作现在会被延后并简化处理 (#35381, #35383)。
  • 更精简的生产构建 – 当你不使用 islands 时,island-renderer chunk 会被完全跳过 (#35456),并且插件处理也会在生产构建中被 tree-shake 掉 (#35278)。
  • 改进的 island 哈希 – 更稳定的 island 和 key 哈希处理,并且现在已暴露 getIslandHashhashKey (#35583)。
  • 并发和 I/O 方面的提升,覆盖 Kit 路径解析和 Nitro 内联样式 (#35511, #35514)。

还有一些不错的易用性修复:

  • "$fetch" 现在会在用户代码中自动导入,这修复了 Rolldown 输出格式下顶层 "$fetch.create" 的边缘情况 (#35581)。
  • Nuxt 现在会在 builder 环境中遵循系统的 HTTP_PROXY / HTTPS_PROXY 环境变量 (#35183)。
  • defineNuxtComponent 在 JSX 中的 HMR 现在可以正常工作了 (#35620)。

⚠️ 升级前提醒

如上所述,此版本包含三个主要依赖项的大幅升级。对于大多数应用来说,它们是透明的,但仍值得稍作留意:

  • Vite 8 – 请根据 Vite 迁移指南 检查自定义 Vite 插件和配置。
  • Rspack 2 – 如果你使用 builder: 'rspack',其内部现在基于 Rsbuild,因此自定义 Rspack 配置可能需要重新审查。
  • unhead v3 – 由于 useHead 的类型约束更严格,可能会出现破坏性的 类型 变更。

⬆️ 升级

我们建议通过运行以下命令进行升级:

npx nuxt upgrade --dedupe

# 或者,如果你仍然停留在 3.x 版本线上
npx nuxt@latest upgrade --dedupe --channel=v3

这将去重你的锁定文件,并帮助确保你获取到 Nuxt 依赖的其他依赖项的更新,尤其是在 unjs 生态系统中(由于这次的重大版本提升,这一点比平时更重要)。

如果你是从较旧的版本升级,请查看我们的 升级指南

👉 完整发布说明

阅读 Nuxt v4.5.0 的完整发布说明。
阅读 Nuxt v3.21.9 的完整发布说明。

感谢参与此次发布的众多贡献者。此次发布意义重大,没有你们就不会有这一切。💚