比 Vue SPA 框架多了哪些
- 路由系统:多页面基于目录实现自动导入
- 支持 SSR:无需单独设置服务器,支持服务端渲染的水合操作
- 自动导入:组件、布局、hooks组合式API自动导入、服务器 API 接口、routes 自动导入
- 数据获取:提供了
useAsyncData、useFetch、还有基于 ofetch 全局挂载的$fetch - 构建工具内置:基于 Vite 的 HMR 和构建
- 服务端和客户端组件区分:
.client.vue、.server.vue - 布局组件:NuxtLayout 组件和组件配置项
- 路由相关:useRoute 获取 route、NuxtLink、NuxtPage、navigateTo 进行导航
SSR 服务端渲染的好处
- 首屏加载更快,优于 SPA
- 更好 SEO,直接返回 HTML
- 低功耗设备表现更好,无需解析 JS 执行
- 更好的阅读性:直接加载 HTML 有利于辅助技术和可访问性
- 更轻松的缓存:在服务端缓存渲染后的页面,后续访问更快
更快首屏加载?
请求时发送完整的渲染页面直接显示 UX 体验更好,慢网设备更佳
- SPA:加载空 HTML > 加载 bundle JS > 执行请求数据 > 客户端渲染,白屏时间长,在小型网站下速度接近 SSR
- SSR:请求数据 > 渲染 HTML > 返回给客户端,客户端解析即可显示,首屏渲染更快,在大型应用网站下更合适,但数据处理过于复杂也会变慢
为什么慢网低端设备 SSR 优于 SPA?
- bundle体积:SPA 需要请求整个 JSBundle 回来执行渲染操作 ,bundle 通常大于单个 html 页面文件
- JS 执行速度:且低端设备的 JS 解析速度通常较慢, html 直接显示的速度会更快
后续页面渲染比较?
后续页面渲染上, SPA 只需请求要渲染的页面文件,而 SSR 需要渲染整个 HTML ,SPA 会优于 SSR
SSR 可以通过 预加载和 HTML cahce 来降低差距
渲染模式与部署
SSG 支持
静态托管,可以在使用 nuxt generate 构建时指定 ssr:false
服务器引擎集成
集成了 Nitro 服务器
- 基于 Rollup 和 Node Worker 处理服务器代码和上下文隔离
- Nitro 把程序和服务器构建到
.output目录 - 构建后的内容经过压缩,不包含没有 polyfill 的 node 模块(服务端部署常用方案)
生产部署
可以部署到 Node / Deno 环境中,支持:
- 静态 SSG
- Severless
- 边缘部署
安装启动
环境版本:>= Node 20
pnpm create nuxt@latest my-nuxt-app
pnpm install
pnpmd dev

构建流程:Vite 服务端和客户端构建完成,后 Nuxt Nitro 进行构建
Nitro 是一个开源 TypeScript 框架,用于构建超快速 Web 服务器。
遇到问题:
无法找到 oxc-parser 模块
原因是某些 Node 版本下不会提供 64位的 .node 文件,需要我们去安装新的 Node 版本
从 Node 20.18.1 切换到 Node 20.21.0 之后解决了

目录结构

app/
应用层模板,负责根模板、HTML、head、meta 等。
app.vue
nuxt 的主组件
没有 pages/ 目录时,显示该组件
如果存在 pages 目录,该组件相当于 layout 存在,通过 <NuxtPage> 去显示 /pages 目录下的内容,还可以配合 <NuxtLayout> 一起使用
创建
/pages目录后,必须创建index.vue,否则根目录显示会出异常
<template>
<header>Header content</header>
<NuxtPage />
<footer>Footer content</footer>
</template>
pages/
基于文件系统的路由。每个 .vue 文件映射一个路由(例如 pages/index.vue -> /)。
- 默认支持扩展:
**.vue**、**.js**、**.jsx**、**.mjs**、**.ts**或**.tsx**。其他需要额外配置 - 单根元素组件:页面必须具有单根元素(和
Vue2一致),由于页面之间的路由过渡
动态路由
[id].vue、users-[groups].vue,类似 vite-plugin-page 的路由功能
- 捕获所有路由:使用
[...slug].vue扩展运算符。访问$route.params.slug时,会将路径拆解成数组返回/hello/test => ["hello", "test"] - 嵌套路由:即嵌套目录下的路由,和 Vue-Router 生成
routes配置 结构类似,只是<router-view>组件变成<NuxtPage> - 路由跳转:使用
<NuxtLink to="/">,类似<router-link> - 函数式导航:使用
navigateTo({ path, query })完成,和 Vue-Router 的router.push()类似 - 避免路由分组/路由扁平化:目录使用
(routername)括号括起来,可以使其目录内部的子路由扁平化,即不需要带上/routername路径去访问 - 路由参数访问:使用
$route.params.xxx,如上述$route.params.id,组合式 API 使用useRoute获取route实例,类似 Vue-Router
定义元信息:使用 definePageMeta({}),以对象形式定义路由组件元数据,在访问页面时。全局通过 route.meta.xxx 去获取,包括特殊辕信息
- alias:定义路由别名,可能是 Vue-Router 的
name - keep-alive:将组件包裹到
<KeepAlive>,父路由和<router-view>一样在<NuxtPage keep-alive>声明 - layout:使用的布局组件
- layoutTransition / pageTransition:是否启用声明的
<transion>过渡效果,false不启用 - middleware:加载页面前使用的中间件
- name:路由名称
- path:自定义路径匹配
- props:和组件
defineProps联动,将路由配置props传递给组件
客户端和服务端组件区分
pages 下组件区分
客户端使用 .client.vue,服务端使用 .server.vue,客户端可以导航到服务端组件,但还是会在服务端渲染
多页面
在 Vite 中多页面通常在 page 属性下定义, Nuxt 支持了自动化组件导入,所以同样的需要在 Nuxt 配置文件下声明多页面的路由
export default defineNuxtConfig({
extends: ["./some-app"],
});
layouts/
页面布局,例如 layouts/default.vue 用于放置全局 header/footer。
支持两种布局使用形式
- 组件内
definePageMeta: laytout <NuxtLayout name="layoutName">嵌套组件使用
布局名称
- 默认布局使用
default.vue声明 - 命名布局使用
xxx.vue声明
| 文件 | 布局名称 |
|---|---|
| ~/layouts/desktop/default.vue | desktop-default |
| ~/layouts/desktop-base/base.vue | desktop-base |
| ~/layouts/desktop/index.vue | desktop |
建议布局的文件名与其名称匹配:
| 文件 | 布局名称 |
|---|---|
| ~/layouts/desktop/DesktopDefault.vue | desktop-default |
| ~/layouts/desktop-base/DesktopBase.vue | desktop-base |
| ~/layouts/desktop/Desktop.vue | desktop |
动态修改布局
使用 setPageLayout('xxx') 来动态修改布局
components/
用于声明组件,例如通用组件或者根据页面区分组件
大驼峰命名
组件使用规则
自动导入
Nuxt 会自动导入所有组件
也可以显示使用 import {} from '#comopnents' 去显示导入组件内容,使用 # 前缀使用
动态组件
配合 <component is> 使用:
import {} from '#comopnents'去显示导入- 使用
const SomeComponent = resolveComponent('组件名称')去动态导入
组件名称
和目录有关,比如组件路径为 ~/component/Test/Text.vue
那么组件自动导入为 <TestText />
惰性加载和水和 (hydration)
hydration:指服务端生成静态的 HTML 标记和 Vue 实例进行绑定,实现数据同步
服务端渲染中,服务器使用 Vue 渲染组件,生成静态 HTML 标记返回给客户端,客户端渲染并 HTML 标记并添加事件,使 HTML 支持交互式
在组件名称前面使用 Lazy,例如 <LazyTestText />
同时配合 hydration 标记,实现对应的加载策略,例如 <LazyTestText xxxx/>
- hydrate-on-visible:进入窗口时水合,不适合首屏渲染的内容
- hydrate-on-idle:空闲时水合,可以传递超时时间(ms)
- hydrate-on-interaction:在进行组件交互时水合
- hydrate-on-media-query:根据声明媒体查询匹配水合
- hydrate-after:延迟多久后水合
- hydrate-when:根据响应式进行判断,使用类似
v-if - hydrate-never:不水合
执行水合时,会发出 @hydrated 事件
最佳实践
延迟 hydration 可以提供性能优势,但正确使用它至关重要
- 优先处理视口内内容: 避免对关键的、首屏内容进行延迟 hydration。它最适合不需要立即使用的内容。
- 条件渲染: 在惰性组件上使用
**v-if="false"**时,你可能不需要延迟 hydration。你可以只使用普通的惰性组件。 - 共享状态: 注意多个组件之间的共享状态 (
**v-model**)。在一个组件中更新模型可以触发绑定到该模型的所有组件的 hydration。 - 使用每种策略的预期用途: 每种策略都针对特定目的进行了优化。
**hydrate-when**适合用于可能不总是需要 hydration 的组件。**hydrate-after**适用于可以等待特定时间的组件。**hydrate-on-idle**适用于浏览器空闲时可以 hydration 的组件。
- 避免在交互式组件上使用
**hydrate-never****: 如果组件需要用户交互,则不应设置为从不进行 hydration。
自定义组件目录
可以通过 NuxtConfig 配置组件的目录名称,自定义相关规则
export default defineNuxtConfig({
components: [
// ~/calendar-module/components/event/Update.vue => <EventUpdate />
{ path: "~/calendar-module/components", extensions: [".vue"] },
//
// ~/user-module/components/account/UserDeleteDialog.vue => <UserDeleteDialog />
{ path: "~/user-module/components", pathPrefix: false },
// ~/components/special-components/Btn.vue => <SpecialBtn />
{ path: "~/components/special-components", prefix: "Special" },
// It's important that this comes last if you have overrides you wish to apply
// to sub-directories of `~/components`.
//
// ~/components/Btn.vue => <Btn />
// ~/components/base/Btn.vue => <BaseBtn />
"~/components",
],
});
pathPrefix<boolean>:指定组件不使用目录前缀,只用他自己的名称prefix<string>:指定组件前缀名,嵌套目录则声明在最前面,组件名称还是要带上目录前缀,可以和pathPrefix可以共同使用避免目录前缀extensions<string[]>:指定认为是组件的扩展名
导入 npm 包组件
在 ~/modules/xxxx 下面注册 npm 包中的组件
使用 addComponent 函数来完成
import { addComponent, defineNuxtModule } from "@nuxt/kit";
export default defineNuxtModule({
setup() {
// import { MyComponent as MyAutoImportedComponent } from 'my-npm-package'
addComponent({
name: "MyAutoImportedComponent",
export: "MyComponent",
filePath: "my-npm-package",
});
},
});
components 下组件区分
和页面组件相当,客户端组件使用 .client.vue
服务端组件使用 .server.vue
.client组件只在挂载后渲染。- 使用
onMounted()访问渲染的模板,请在onMounted()钩子的回调中添加await nextTick()。
- 使用
同名时整体流程是怎么渲染的??
独立服务器组件 ISLANDS
启用方案
export default defineNuxtConfig({
experimental: {
componentIslands: true,
},
});
服务器组件 nuxt-client 标记
使服务器组件在客户端完成加载和水合操作,进行部分 hydration
<Counter
nuxt-client
:count="5"
/>
NuxtIsLand
客户端和服务器组件配对
composables/
组合式逻辑(自动导入,可直接使用)
- 默认只自动导出最顶层的文件
index.ts和useXXX.ts。 - 嵌套层级目录 需要手动导出并在 Nuxt 配置中配置对应选项
plugins/
- 注册第三方库或全局插件(客户端),主要用于 Nuxt 项目的全局配置,比如说生命周期钩子等。
server/
基于 Nitro 实现的服务器
- 支持混合渲染
- 通用部署
- 完全服务端支持
- 基于 H3 高性能可移植 HTTP 框架

Nitro 后端 API 路由(例如 server/api/posts.get.ts)。
用于编写结构,和服务端交互
整体目录结构分为三部分
- api:编写接口,自动添加
/api前缀,使用useFetch('接口名称')可以获取对应的数据,用于 SSR 场景 - routes:编写页面路由,用于页面访问,不会添加前缀
- middlware:编写中间件,例如日志等
- plugins:服务器插件,传入 Nitro 实例,可以修改配置、挂载钩子等
- utils:添加服务器辅助工具函数,包装原始程序在返回前执行额外操作
-| server/
---| api/
-----| hello.ts # /api/hello
---| routes/
-----| bonjour.ts # /bonjour
---| middleware/
-----| log.ts # log all requests
同时服务器基于 h3js/h3 提供的辅助函数来完成服务器的相关操作
路由与文件约定
文件导出
每个文件都需要导出 defineEventHandler用于定义返回的内容
export default defineEventHandler((event) => {
return {
hello: "world",
};
});
plugin 则是导出 defineNitroPlugin ,使用 Nitro 提供的生命周期事件
请求方法匹配支持
api接口可以通过 xxx.get.ts 、xxx.post.ts 来支持不同的请求操作
路由捕获
使用 ~/server/api/test/[...].ts 去捕获路由参数
相关方法
请求处理:
await readBody(event):获取body 请求体方法getQuery(event):获取 query 查询参数,返回对象格式parseCookie(event):获取传递的 Cookieevent.$fetch('xxxx'):在 sever 层转发请求时,会转发请求头上下文和请求头信息- 不会转发
**transfer-encoding**、**connection**、**keep-alive**、**upgrade**、**expect**、**host**、**accept**请求头
- 不会转发
event.watiUtil(asyncMethod):用于处理异步请求,但不需要等待异步处理完成后才返回,可以立即返回信息,相当于是了个执行方法的壳子
响应处理:
throw createError({statusCode:xxx, statusMessage: ''})手动抛出错误信息- 如果未定义出现错误,则抛出状态码 500
- 如果正常执行则返回 200
setResponseStatus(event, statusCode)自定义返回状态码,主要针对statusCode设置
配置读取
useRuntimeConfig(event):获取服务器运行时被环境变量覆盖的配置,event 是可选的
嵌套路由
可以使用 createRouter 创建子路由,再基于 useBase() 去暴露基础路由 api 和方法
import { createRouter, defineEventHandler, useBase } from "h3";
// 1. 定义子路由
const router = createRouter();
// 2. 声明子路由方法
router.get(
"/test",
defineEventHandler(() => "Hello World"),
);
// 3. 声明路由基础路径,并通过 router.handler 暴露路由方法
export default useBase("/api/hello", router.handler);
常见响应与中间件能力
流发送
使用 sendStream 和 fs.createReadStream 完成文件流的发送操作
import fs from "node:fs";
import { sendStream } from "h3";
export default defineEventHandler((event) => {
return sendStream(event, fs.createReadStream("/path/to/file"));
});
路由重定向
使用 sendRedirect 事件来完成路由重定向
export default defineEventHandler(async (event) => {
await sendRedirect(event, "/path/redirect/to", 302);
});
传统路由中间件(类 express)
使用 fromNodeMiddleware 来得到模拟传统路由中间件的传递方式,只是 next() 不能和异步路由使用
export default fromNodeMiddleware((req, res, next) => {
console.log("Legacy middleware");
next();
});
Nitro 缓存配置
对于 nitro 配置,在nuxt.config.ts 里面配置内容
- storage:跨平台存储层,例如 配置 redis 缓存等
- 在程序中使用
useStorage('存储层名称,如 redis')来使用对应的存储层- getKeys():获取索引
- setItem:设置索引
- getItem:后去缓存索引
- 在程序中使用
export default defineNuxtConfig({
// https://nitro.build/config
nitro: {
storage: {
redis: {
driver: "redis",
/* redis connector options */
port: 6379, // Redis port
host: "127.0.0.1", // Redis host
username: "", // needs Redis >= 6
password: "",
db: 0, // Defaults to 0
tls: {}, // tls/ssl
},
},
},
});
也可以使用服务器插件来完成缓存配置
import redisDriver from "unstorage/drivers/redis";
export default defineNitroPlugin(() => {
const storage = useStorage();
// 1. 导入 redis
const driver = redisDriver({
base: "redis",
// 从运行时环境获取配置信息
host: useRuntimeConfig().redis.host,
port: useRuntimeConfig().redis.port,
/* other redis connector options */
});
// 2. 挂载缓存功能
storage.mount("redis", driver);
});
public/
静态资源公开目录(图片、favicon),不会被编译。
在代码中使用 / 直接访问 public/ 目录中的文件
<template>
<img
src="/img/nuxt.png"
alt="Discover Nuxt"
>
</template>
对比 app/assets 下存储的文件,可以使用 ~/assets/ 进行访问
asset 目录下的文件会被构建工具编译压缩,但不会自动引入
<img src="~/assets/img/image.png"></img>
nuxt.config.ts
- Nuxt 的主配置文件(模块、插件与运行时配置)。
环境配置
环境变量配置
对于运行时的环境变量配置,通过 runtimeConfig 来完成
在后续获取时使用 useRuntimeConfig 获取对应的变量配置
export default defineNuxtConfig({
runtimeConfig: {
// 仅服务端侧渲染能获取到
apiSecret: "123",
// 会在客户端侧暴露
public: {
apiBase: "/api",
},
},
});
ssr 即为服务端侧渲染能获取到的内容,不带 ssr 为客户端侧暴露获取到的内容
- app:应用程序的整体环境配置,客户端侧可见
- nitro:nitro 服务器配置信息,仅服务端侧
- public:公开信息,存放非关键隐私数据,客户端侧可见
- 其他:例如下图中的 apiSercret,仅服务端侧渲染可见,可以用来存放客户端侧不可见的信息
对于隐私信息:
- 最好通过服务器端运行时环境变量注入实现
- 在部署服务器上配置变量信息
后续在配置文件中指定
process.env.xxx来动态导入变量信息不要在项目中编写配置

客户端侧的可见数据:

环境变量(仅客户端侧)
在 /app 目录下定义 app.config.ts 配置客户端侧环境变量
通过 defineAppConfig 暴露
//my-nuxt-app/app/app.config.ts
export default defineAppConfig({
title: "个人博客搭建",
});
在应用程序中使用 useAppConfig 进行读取
const config = useAppConfig();
客户端侧读取结果:

服务端侧读取,无法获取到声明的环境变量信息:

**runtimeConfig**:私有或公共令牌,需要在构建后使用环境变量指定。**app.config**:在构建时确定的公共令牌、网站配置(如主题变体、标题)以及任何非敏感的项目配置。
| 功能 | **runtimeConfig** | **app.config** |
|---|---|---|
| 客户端 | 水合 | 打包 |
| 环境变量 | ✅ 是 | ❌ 否 |
| 响应式 | ✅ 是 | ✅ 是 |
| 类型支持 | ✅ 部分 | ✅ 是 |
| 按请求配置 | ❌ 否 | ✅ 是 |
| 热模块替换 | ❌ 否 | ✅ 是 |
| 非原始 JS 类型 | ❌ 否 | ✅ 是 |
环境覆盖
基于 C12 实现的环境变量覆盖机制,支持覆盖 runtimeConfig 中提供的参数
export default defineNuxtConfig({
$production: {
routeRules: {
"/**": { isr: true },
},
},
$development: {
//
},
$env: {
staging: {
//
},
},
});
使用时 nuxt build --envName <环境名> 来覆盖环境变量
支持环境配置:
$production$development$test$meta:支持配置元信息$env:自定义环境
框架类环境变量
nuxt.config.ts 中支持框架类环境变量,通过对应的框架名指定
- vite:贯穿全称的HMR和构建工作
- webpack:降级方案,支持插件兼容
- postcss:样式工具处理
- nitro:服务端框架
- vue:配置 vue 的特性支持
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
vue: {
propsDestructure: true,
},
nitro: {},
postcss: {
plugins: {
"postcss-nested": {},
"postcss-custom-media": {},
},
},
webpack: {
loaders: {
vue: {
hotReload: true,
},
},
},
vite: {
vue: {
customElement: true,
},
vueJsx: {
mergeProps: true,
},
},
});
外部配置文件
| 名称 | 配置文件 |
|---|---|
| TypeScript | tsconfig.json |
| ESLint | eslint.config.js |
| Prettier | prettier.config.js |
| Stylelint | stylelint.config.js |
| TailwindCSS | tailwind.config.js |
| Vitest | vitest.config.ts |
604

被折叠的 条评论
为什么被折叠?



