Skip to content
创建第一个 Nuxt 项目
环境准备:Node.js 与 npm
Nuxt 4 对 Node.js 的版本要求是 22.x 或更新版本,推荐使用 active LTS 版本。如果系统中还没有 Node.js,可以通过二进制安装包或系统包管理器安装。安装完成后用 node -v 和 npm -v 验证。
需要在多个 Node.js 版本之间切换时,NVM 比系统包管理器更灵活。它通过 bash 脚本管理多个 Node.js 和 npm 的并行安装,切换版本不需要反复卸载重装。NVM 不支持 Windows,Windows 环境下可以使用 nvm-windows。
bash
# 安装 nvm 后,安装最新稳定版
nvm install stable
# 设为默认版本,新终端自动使用
nvm alias default stable
# 确认当前版本
node -vnvm install stable 会拉取 Node.js 当前稳定版并安装到 NVM 的版本目录中。nvm alias default stable 将该版本写入默认配置,此后每次打开终端时 NVM 会自动切换到该版本,不需要手动执行 nvm use。
Windows 环境下开发时可能遇到两个问题:一是 HMR(Hot Module Replacement)偶尔变慢,可以尝试用 WSL 运行开发环境;二是通过 localhost:3000 访问时加载缓慢,改用 127.0.0.1:3000 通常能明显提速。这两个问题并非必然出现,但遇到时可以优先排查。
使用 nuxi init 初始化项目
Nuxt 通过 create-nuxt 创建项目,底层调用 nuxi 的 init 命令:
bash
npm create nuxt@latest <project-name>包管理器不限于 npm。yarn create nuxt、pnpm create nuxt@latest、bun create nuxt@latest、deno -A npm:create-nuxt@latest 均支持,行为完全一致——在指定目录下生成一个包含基础结构的 Nuxt starter 项目。
如果要创建的是 Nuxt 模块的 starter 而非应用项目,需要加 -t module:
bash
npm create nuxt -- -t module my-module这将生成包含 src、playground、测试与发布脚本的模块模板。本篇聚焦应用项目,模块开发不在范围之内。
安装依赖并启动开发服务器
项目生成后进入目录,安装依赖:
bash
cd <project-name>
npm install依赖安装完成后启动开发服务器:
bash
npm run dev这个命令实际执行的是 nuxi dev。终端会输出类似如下信息:
Nuxt 4.x.x with Nitro x.x.x
➜ Local: http://localhost:3000/浏览器打开这个地址就能看到 Nuxt 的默认欢迎页。此时对项目文件的任何修改都会触发 HMR,页面即时更新,不需要手动刷新——Vite 的 HMR 机制会保留组件状态,只替换修改过的模块。
项目结构:约定即架构
根目录文件速览
初始化后的项目根目录结构大致如下:
project-name/
├── .nuxt/ # 构建时自动生成,不提交到版本控制
├── assets/ # 静态资源,构建时由 Vite 处理
├── components/ # 组件目录,按需自动导入
├── layouts/ # 布局组件
├── pages/ # 页面组件,驱动文件系统路由
├── public/ # 静态文件,直接映射到根路径
├── server/ # 服务端 API 与中间件
├── app.vue # 应用根组件
├── nuxt.config.ts # 运行时配置
├── package.json
└── tsconfig.json.nuxt/ 是 nuxi dev 或 nuxi build 时自动生成的目录,包含类型声明、自动导入映射、路由配置等。该目录不需要手动编辑,也不应该提交到 Git。
nuxt.config.ts 是唯一建议打开的配置文件,但只在需要覆盖默认行为时再改。刚上手时,默认配置已足够。
pages/ 与 layouts/ 的定位
pages/ 目录中每一个 .vue 文件自动对应一个 URL 路径。这是文件系统路由的核心约定——文件在哪里、叫什么名字,直接决定它出现在哪个路径上,不需要手动注册路由。
layouts/ 存放布局组件。默认布局文件是 default.vue,页面组件会渲染在该布局的 <NuxtPage /> 位置。其他布局可以在页面的 <script setup> 中用 definePageMeta 指定:
vue
<script setup>
definePageMeta({
layout: 'admin'
})
</script>这样该页面就会使用 layouts/admin.vue 而不是 default.vue。如果没有匹配的布局,回退到 default.vue。
其他关键目录与文件
components/ 下的组件会自动按需导入,不需要在每个页面里手动 import。假设 components/ 下有 Hello.vue,在任意页面的 template 里直接写 <Hello /> 就能使用。这个自动导入基于目录扫描和代码生成,.nuxt/ 下的类型声明会同步更新,保证 IDE 的类型提示。
server/ 目录用于定义 API 端点和服务端中间件。文件结构与 pages/ 类似,server/api/hello.ts 对应 /api/hello。这部分属于全栈能力范畴,本篇不展开。
app.vue 是应用的根组件,Nuxt 用它包裹所有页面。通常这里只放 <NuxtPage /> 或加上全局导航栏。如果项目不需要全局组件,app.vue 可以完全由布局接管。
从文件到路由:构建多页面应用
静态路由
在 pages/ 下创建文件就等于创建路由。假设需要三个页面:
pages/
├── index.vue → /
├── about.vue → /about
└── contact.vue → /contact每个文件导出一个 Vue 组件。index.vue 是根路径的默认页面:
vue
<!-- pages/index.vue -->
<template>
<h1>首页</h1>
</template>about.vue 的内容类似:
vue
<!-- pages/about.vue -->
<template>
<h1>关于我们</h1>
</template>此时访问 /、/about、/contact 就能看到对应页面,不需要写任何路由配置。Nuxt 在构建或开发启动时扫描 pages/ 目录,根据文件路径和文件名自动生成 Vue Router 路由表。
动态路由
动态路由通过文件名中的方括号来定义。文件名使用 [id].vue,则 /user/123、/user/abc 都会匹配到这个页面,123 和 abc 作为路由参数传入。
pages/
└── user/
└── [id].vue → /user/:id在页面组件中通过 useRoute 获取参数:
vue
<!-- pages/user/[id].vue -->
<template>
<p>用户 ID: {{ route.params.id }}</p>
</template>
<script setup>
const route = useRoute()
</script>访问 /user/42 时页面显示 "用户 ID: 42"。useRoute 返回当前路由信息,params 对象中的键名与文件名中的参数名一致。useRoute 是 Vue Router 提供的组合式 API,Nuxt 自动导入,不需要手动引入。
路由与页面的对应关系
嵌套路由同样靠目录结构实现。pages/user/index.vue 对应 /user,pages/user/[id].vue 对应 /user/:id。pages/user/settings/profile.vue 对应 /user/settings/profile。
pages/
└── user/
├── index.vue → /user
├── [id].vue → /user/:id
└── settings/
└── profile.vue → /user/settings/profile路由生成规则与文件系统一一映射,没有隐藏的中间层。要查看实际生成的路由,可以打开 .nuxt/types/routes.d.ts 查看自动生成的类型声明,或者查看 .nuxt/pages.mjs 中的路由表。
用 NuxtLink 串联页面
基本用法
页面之间跳转使用 <NuxtLink> 而不是原生的 <a> 标签。NuxtLink 基于 Vue Router 的 <RouterLink> 扩展,行为上与 RouterLink 兼容,但增加了预加载能力。
vue
<template>
<nav>
<NuxtLink to="/">首页</NuxtLink>
<NuxtLink to="/about">关于</NuxtLink>
<NuxtLink to="/user/42">用户 42</NuxtLink>
</nav>
</template>示例中的三个链接分别指向根路径、静态路由和动态路由。组件内部渲染出来仍然是 <a> 标签,但点击时不会触发整页刷新——走的是客户端路由切换,仅替换页面内容区域。
to 属性与 RouterLink 的参数格式一致,可以传字符串路径,也可以传路由对象:
vue
<NuxtLink :to="{ name: 'user-id', params: { id: '99' } }">
用户 99
</NuxtLink>这里 name: 'user-id' 对应文件系统路由中 pages/user/[id].vue 生成的路由名称,Nuxt 会根据文件路径自动生成路由名称,规则是将路径中的 / 替换为 -,方括号内的参数名保留。
active 状态与样式绑定
当前路由匹配时,NuxtLink 会自动在元素上追加 .router-link-active 和 .router-link-exact-active 两个类名。.router-link-active 在路径前缀匹配时添加,.router-link-exact-active 只在完全匹配时添加。
利用这个特性可以直接在 style 中定义激活态的样式:
vue
<style scoped>
.router-link-exact-active {
font-weight: bold;
color: #00c58e;
}
</style>这段样式会让当前页面对应的链接变为加粗的绿色文字。由于使用了 scoped,样式只作用于当前组件内的链接。
也可以用 active-class 和 exact-active-class 自定义类名,或者通过 :active-style 直接绑定内联样式:
vue
<NuxtLink
to="/about"
active-class="current"
exact-active-class="current-exact"
>
关于
</NuxtLink>预加载行为
NuxtLink 的预加载是它跟普通 RouterLink 的主要区别。当链接进入视口时,Nuxt 会自动预加载目标页面的 JS 分包和关键数据——不需要等用户点击才开始加载。
这个机制在开发阶段不容易察觉,因为 dev 模式下所有资源本来就是按需懒加载的。执行 nuxi build 再 nuxi preview,打开浏览器开发者工具的 Network 面板,滚动页面时能观察到预加载请求被触发。
预加载默认开启。要关闭某个链接的预加载,加上 no-prefetch:
vue
<NuxtLink to="/heavy-page" :prefetch="false">
不预加载
</NuxtLink>也可以通过 nuxt.config.ts 全局控制行为,但多数情况下默认策略已经够用。预加载会在链接进入浏览器视口时触发,利用的是 Intersection Observer API。
NuxtPage:页面容器的核心作用
在布局中放置 NuxtPage
打开 layouts/default.vue,核心结构是 <NuxtPage /> 搭配导航组件:
vue
<!-- layouts/default.vue -->
<template>
<div>
<nav>
<NuxtLink to="/">首页</NuxtLink>
<NuxtLink to="/about">关于</NuxtLink>
</nav>
<main>
<NuxtPage />
</main>
</div>
</template>导航放在 <NuxtPage /> 外面,所有使用这个布局的页面都会带上这个导航栏。页面组件被渲染在 <NuxtPage /> 的位置——<NuxtPage /> 本质上是一个占位组件,Nuxt 在运行时将当前路由匹配到的页面组件填入此处。
NuxtPage 的自动渲染机制
<NuxtPage> 本身不需要传任何 props,它自动读取当前路由并渲染对应的页面组件。整个流程是:
- URL 变化触发路由匹配
- Nuxt 根据当前路由查找对应的布局(默认
default.vue,页面可通过definePageMeta覆盖) - 布局中的
<NuxtPage>渲染匹配到的页面组件 - 如果定义了
pageTransition,在切换时播放过渡动画
开发者不需要关心中间的调度细节。只需要知道 <NuxtPage> 放在哪个位置、页面组件就出现在哪个位置;布局用什么文件、<NuxtPage> 就读取哪个文件。
布局与页面切换的配合
不同页面可以指定不同布局。在页面组件中配置:
vue
<!-- pages/admin.vue -->
<script setup>
definePageMeta({
layout: 'admin'
})
</script>
<template>
<h1>管理后台</h1>
</template>这样 /admin 会使用 layouts/admin.vue 而不是 default.vue。布局切换时,NuxtPage 的内容替换与布局的切换是同步完成的——Nuxt 在路由切换时同时解析页面组件和布局组件,确保它们作为一对匹配的单元进行渲染和过渡。
布局文件不强制使用 <NuxtPage />。如果某个布局是独立页面(比如登录页),完全可以不放 <NuxtPage />,直接在布局里写完整内容。但这种情况该组件更应该放在 pages/ 下而非 layouts/ 下,保持语义清晰:layouts/ 是页面容器的定义,pages/ 是页面内容的定义。
开发、构建与预览
nuxi dev:开发模式与热更新
nuxi dev 启动的是 Vite 开发服务器,端口默认 3000。开发模式提供了几个关键能力:
- 文件热替换(HMR):修改
.vue、.ts、.css文件后浏览器即时反映变化,组件状态保留。Vite 的 HMR 通过 WebSocket 推送模块更新,只替换变更的模块而不是整页刷新。 - 自动导入的实时感知:在
components/新增文件马上可用,不需要重启服务器。Nuxt 监听目录变化并重新生成导入映射。 - 类型声明实时生成:
.nuxt/下的类型文件随项目结构变化而更新,保证 IDE 的类型提示始终与实际目录结构同步。
HMR 失效的情况偶尔会出现,比如改了 nuxt.config.ts 的配置或修改了路由参数结构。碰到页面没反应,先看终端有没有报错,再尝试手动刷新。nuxt.config.ts 的变更需要重启开发服务器才能生效。
nuxi build:正式构建
开发完成后的构建阶段:
bash
npm run build这个命令执行 nuxi build,分为两步:Vite 构建客户端 bundle,Nitro 构建服务端部分。产物输出到 .output/ 目录。
构建过程会做代码分割、Tree Shaking、资源压缩。文件系统路由在这个阶段被编译为静态路由表,动态路由对应的页面按需分 chunk。Vite 负责将 Vue 组件、TypeScript、CSS 等编译为浏览器可直接运行的 JavaScript 和 CSS 文件;Nitro 负责服务端逻辑的打包和优化。
.output/ 目录的结构完整性很重要,不要手动移动或修改里面的文件。部署时基于这个目录进行操作。
nuxi preview:本地预览
构建完成后,用 nuxi preview 在本地启动一个部署环境的预览服务器:
bash
npm run preview这个命令加载 .output/ 下的构建产物,运行方式与实际部署的服务器一致。preview 模式不带 HMR,没有自动导入的实时更新——它是用来验证构建结果的,不是用来开发的。
preview 的主要用途是检查 SSG 页面的静态生成结果、确认预加载行为、排查构建产物中的问题。由于 preview 加载的是经过压缩和优化的正式构建产物,其行为与开发模式有明显差异,某些在 dev 模式下不明显的性能问题或资源加载问题可能会暴露出来。
三种命令的职责边界
| 命令 | 用途 | HMR | 构建产物 | 适用场景 |
|---|---|---|---|---|
nuxi dev | 启动开发服务器 | 有 | 无 | 编写代码阶段 |
nuxi build | 生成正式构建 | 无 | .output/ | 部署前构建 |
nuxi preview | 本地预览构建产物 | 无 | 加载 .output/ | 构建后验证 |
日常开发只用 dev。代码写完准备上线时执行 build,接着用 preview 在本地验证产物没有异常,然后提交部署。三个命令的职责边界清晰,不存在跨阶段的混用场景。preview 不能替代 dev 进行开发,dev 的产物不能用于部署。
参考链接
- Nuxt 4 环境要求与安装方式:Nuxt Official Installation
- Node.js 安装方式与版本验证:MDN Web Docs - 设置 Node.js 开发环境
- NVM 管理 Node.js 版本:Google Cloud Documentation - 设置 Node.js 开发环境
- 创建 Nuxt 项目的命令与选项:Nuxt Official Installation
- 模块模板创建命令:Nuxt Modules Author Guide v4
参考链接
- [1] https://nuxt.com/docs/4.x/getting-started/installation
- [3] https://nuxt.com/docs/4.x/guide/modules/getting-started
- [4] https://developer.mozilla.org/zh-CN/docs/Learn_web_development/Extensions/Server-side/Express_Nodejs/development_environment
- [5] https://docs.cloud.google.com/nodejs/docs/setup?hl=zh-cn
