Nuxt 学习笔记(一)

比 Vue SPA 框架多了哪些

  • 路由系统:多页面基于目录实现自动导入
  • 支持 SSR:无需单独设置服务器,支持服务端渲染的水合操作
  • 自动导入:组件、布局、hooks组合式API自动导入、服务器 API 接口、routes 自动导入
  • 数据获取:提供了 useAsyncDatauseFetch、还有基于 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].vueusers-[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.vuedesktop-default
~/layouts/desktop-base/base.vuedesktop-base
~/layouts/desktop/index.vuedesktop

建议布局的文件名与其名称匹配:

文件布局名称
~/layouts/desktop/DesktopDefault.vuedesktop-default
~/layouts/desktop-base/DesktopBase.vuedesktop-base
~/layouts/desktop/Desktop.vuedesktop
动态修改布局

使用 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 可以提供性能优势,但正确使用它至关重要

  1. 优先处理视口内内容: 避免对关键的、首屏内容进行延迟 hydration。它最适合不需要立即使用的内容。
  2. 条件渲染: 在惰性组件上使用 **v-if="false"** 时,你可能不需要延迟 hydration。你可以只使用普通的惰性组件。
  3. 共享状态: 注意多个组件之间的共享状态 (**v-model**)。在一个组件中更新模型可以触发绑定到该模型的所有组件的 hydration。
  4. 使用每种策略的预期用途: 每种策略都针对特定目的进行了优化。
    • **hydrate-when** 适合用于可能不总是需要 hydration 的组件。
    • **hydrate-after** 适用于可以等待特定时间的组件。
    • **hydrate-on-idle** 适用于浏览器空闲时可以 hydration 的组件。
  5. 避免在交互式组件上使用**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.tsuseXXX.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.tsxxx.post.ts 来支持不同的请求操作

路由捕获

使用 ~/server/api/test/[...].ts 去捕获路由参数

相关方法

请求处理:

  • await readBody(event):获取body 请求体方法
  • getQuery(event):获取 query 查询参数,返回对象格式
  • parseCookie(event):获取传递的 Cookie
  • event.$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);
常见响应与中间件能力
流发送

使用 sendStreamfs.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,
    },
  },
});
外部配置文件
名称配置文件
TypeScripttsconfig.json
ESLinteslint.config.js
Prettierprettier.config.js
Stylelintstylelint.config.js
TailwindCSStailwind.config.js
Vitestvitest.config.ts

参考内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值