Skip to content
给予支持/赞助

Nuxt ​

V4.1.3 ​

约定 ​

  • app/components/目录中创建的组件,可直接使用<文件名 />,无需额外import

  • app/pages/目录中的每个文件都代表一个路由`

    • 动态路由

      • 定义目录方式

        • app/pages/user/[id]/index.vue or app/pages/user/[id].vue 可访问 /user/*
        • app/pages/user/[id]/[name].vue 可访问 /user/*/*
        • app/pages/user/user-[id]/index.vue 可访问 /user/user-*
        • 全路由补捕获
          • app/pages/cars/[...name].vue 可访问 /cars/**/**/**/...
      • 使用,只要路由中有,无需在乎层级

        vue
        <template>
        	<div>ID: {{ $route.params.id }}</div>
        	<div>Name: {{ $route.params.name }}</div>
        
        	<!-- 全路由补捕获,得到数组,由后续路由依次形成 -->
        	<div>Names: {{ $route.params.name }} or {{ $route.params.name[0] }}</div>
        </template>
        
        <script setup>
        	const route = useRoute();
          console.log(route.params.id)
          console.log(route.params.name) // 全路由补捕获,得到数组,可自由处理
        </script>
  • 将<NuxtPage />组件添加到app/app.vue作为app/pages路由入口,需删除入口其他template内容

  • app/layouts/公共布局(如页眉和页脚),其中使用<slot />组件显示内容。默认使用app/layouts/default.vue文件

    • 自定义布局可以设置为页面元数据的一部分

资源/样式 ​

vue
<img src="~/assets/img/nuxt.png" /> <!-- app/assets/ 资源 -->
<img src="/img/nuxt.png" alt="Discover Nuxt" /> <!-- public资源 -->


<script>
 // 使用静态导入以实现服务器端兼容性
import '~/assets/css/first.css'
// 动态导入与服务器端不兼容
import('~/assets/css/first.css')
</script>
<style>
@import url("~/assets/css/second.css");
 
// 使用 scss 样式
@use "~/assets/scss/main.scss";
</style>
ts
// nuxt.config.ts
export default defineNuxtConfig({
  css: ['~/assets/css/main.css'], // 公共样式配置,将应用到所有页面中
  app: {
    head: {
      // 外部样式表 external stylesheets
      link: [{ 
        rel: 'stylesheet',
        href:'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css' 
      }],
    },
  },
})


// xxx.vue 动态添加样式表 Dynamically Adding Stylesheets
// 在特定页面使用 useHead 在head中动态设置
useHead({
  link: [{ 
    rel: 'stylesheet', 
    href: 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css' }],
})

plugins插件 ​

  • Nuxt会自动读取app/plugins/目录中的文件,并在创建Vue应用程序时加载它们
  • 可使用.serveror或.client,区分在服务器或客户端加载的插件
  • 只有目录顶层的文件(或任何子目录中的索引文件)才会自动注册为插件
bash
# 只有 foo.ts 和 bar/index.ts 会被注册。
-| plugins/
---| foo.ts      // scanned
---| bar/
-----| baz.ts    // not scanned
-----| foz.vue   // not scanned
-----| index.ts  // currently scanned but deprecated


# 方案二 nuxt.config.ts
export default defineNuxtConfig({
  plugins: [
    '~/plugins/bar/baz',
    '~/plugins/bar/foz',
  ],
})

SEO ​

  • useHead
    • 适合复杂的 head 配置,支持响应式配置更新
  • UseSeoMeta
    • 专门用于 SEO 相关的元数据
    • 更好的类型提示和自动补全,不支持响应式,提供更简洁的 API
    • 自动处理 Open Graph 和 Twitter Card
  • definePageMeta
    • 不会改变网页的meta数据,只是存放在页面route的meta中
  • 注意
    • 两者可以在同一个组件中同时使用
vue
<script setup>
  // 使用-useHead
  // app/app.vue 全局应用
  // app/pages/**  局部配置,优先级更高,替代app.vue
	useHead({
    title:'夏之一周间',
    meta:[
      {name:'description',content:'Some description...'}
    ],
    link:[
      {rel:'stylesheet',href:'xxxx'},
      // ...
    ]
  })
 
 
  // 响应式title
  const title = ref('Contact')
  setTimeout(()=>{title.value='New Contact'},2000)
  useHead({
    titleTemplate:title
  })
</script>

<script setup>
	// 使用-useSeoMeta
	useSeoMeta({
    title:'夏之一周间',
    ogTitle:'夏之一周间',
    description:'Some description...',
    ogDescription:'Some description...'
  })
</script>

<script setup>
  definePageMeta({
    title:'User',
  	meta:[{name:'description',content:'Some description...'}],
    link:[
      {rel:'stylesheet',href:'xxxx'},
      // ...
    ]
  })
  
  const route = useRoute()
  console.log(route.meta) // 得到definePageMeta对象

  useHead(route.meta) // 利用 useHead支持响应式,复用definePageMeta数据
</script>

Layouts ​

vue
// app.vue 中使用<NuxtLayout></NuxtLayout> 包裹
// 新建app/layouts/default.vue 组件,并设置插槽位置 <slot/> 

// 对于其他类型的layout布局,在app/layouts/myLayoutName.vue 中定义
// app.vue 中使用时配置name,全局替换  <NuxtLayout name='myLayoutName'></NuxtLayout> 



<template>
	<button @click="setPageLayout('myLayoutName')">动态切换 layout</button>
</template>

<script setup>
  // 定制个别页面 layout时,借助 definePageMeta 配置layout选项实现
  definePageMeta({
    layout:'myLayoutName',
    ...,
  })
</script>

components ​

  • 懒加载 Lazy
    • 直接在组件名前追加Lazt 如 <LazyAboutUs/>
    • 为了优化您的应用,可通过延迟某些组件的水合,直到它们可见,或者直到浏览器完成更重要的任务
    • hydrate-on-interaction
      • 默认为在pointerenter、click和focus上进行水合
    • 所有延迟水合组件在水合时都会发出@hydrated事件
vue
<template>
	<UsersProfile/> <!-- 方式1:直接引入,根据文件路径的命名 -->
	<Profile/>

	<!-- hydrate-on-visible 在视口中可见时对其进行水化加载 -->
  <LazyMyComponent hydrate-on-visible /> 
	<!-- hydrate-on-idle 浏览器空闲时对零部件进行水合 -->
	<LazyMyComponent hydrate-on-idle />
	<!-- hydrate-on-interaction 指定的相互作用后水合组分 mouseover/click/... -->
	<LazyMyComponent hydrate-on-interaction="mouseover" />
	<!-- hydrate-on-media-query 窗口与媒体查询匹配时,自动化组件 -->
	<LazyMyComponent hydrate-on-media-query="(max-width: 768px)" @hydrated="onHydrate"/>
	<!-- hydrate-after 指定的延迟(以毫秒为单位)后使组件水合 -->
	<LazyMyComponent :hydrate-after="2000" @hydrated="onHydrate"/>
	<!-- hydrate-when 基于布尔条件对零部件进行水合 -->
	<LazyMyComponent :hydrate-when="isReady" @hydrated="onHydrate"/>
</template>

</template>

<script setup>
  // 方式2:传统引入
	import Profile from '~/components/users/profile.vue'
  
  // 方式3: components 对象实现
  import { UsersProfile } from '#components'
  
  const isReady = ref(false)
  function onHydrate () {
  	console.log('Component has been hydrated!')
	}
</script>

路由 Router ​

  • <NuxtLink>进行页面跳转,可以防止整页刷新,并允许动画过渡

    • 当<NuxtLink>进入客户端的viewport时,会自动提前预取链接页面的组件和有效负载(生成的页面),从而加快导航速度
  • useRouter

  • navigateTo路由跳转

    • 在路由中间件中使用navigateTo时,必须 return 其结果以确保中间件执行流正确工作
  • defineRouteRules 在页面级别定义混合渲染的路由规则

    • 更多路由规则定义方式可查看nuxt.config.ts
    • 使用当前功能,必须在nuxt.config.ts中启用experimetal.inlineRouteRules
  • 其他细节

    • Prefetch Link 预取 nuxtLink 默认检测链接何时可见,并在浏览器空闲时提前获取内容,更快的交互,同时有配置选项可修改
vue
<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>
        <li><NuxtLink to="https://baidu.com" target="_blank">百度一下</NuxtLink></li>
        <li><NuxtLink to="/posts/2">Post 2</NuxtLink></li>
      </ul>
      <br/>
    	<button @click="router.push('/about')">About</button>
      <button @click="navigateTo('/about')">About</button>
      <button @click="edit()">Edit</button>
    </nav>
  </header>
</template>
<script setup>
  defineRouteRules({
    prerender: true, // 启用当前页面的预渲染
  })
  const route = useRoute()
	const router = useRouter()
  
  function edit(){
    router.push(`/form/edit/${route.params.id}`)
  }
  
</script>
vue
<!-- no-prefetch  prefetch-on  prefetch -->
<template>
	<div>
    <NuxtLink to="/about" no-prefetch>About page not pre-fetched</NuxtLink>
    <NuxtLink to="/about" :prefetch="false">关闭预取</NuxtLink>
    <NuxtLink to="/about" prefetch-on="visibility">About page not pre-fetched</NuxtLink>

  </div>  
</tempalte>
<script setup>
/* 
prefetch-on
  - visibility 【默认值】当内容可见时,它会自动进行预加载
  - interaction 当链接悬停或聚焦时进行预取,侦听pointerenter和focus事件,要交互时主动预取
*/
</script>
js
// passing 'to' as a string
await navigateTo('/search')

// ... or as a route object
await navigateTo({ path: '/search' })

// ... or as a route object with query parameters
await navigateTo({
  path: '/search',
  query: {
    page: 1,
    sort: 'asc',
  },
})

中间件 middleware ​

  • xxx.global.ts全局中间件,表示每次访问路由都会自动执行的中间件
    • 默认执行顺序,根据文件名/目录a-z排序,按顺序执行,可手动命名顺序01/02/03
  • xxxx.ts局部中间件
    • 只有在页面组件definePageMeta中显式引入,才会在特定引入位置执行
    • 引入位置按顺序执行
  • 本地中间件,在页面definePageMeta中直接定义
js
// app/middleware/01.main.global.js
export default defineNuxtRouteMiddleware((to,from)=>{
  console.log('Global Middleware:',to,from) // 同Vue全局路由守卫,可执行网络请求/简单函数操作
})

// app/middleware/name.ts
export default defineNuxtRouteMiddleware((to,from)=>{
  console.log('Name Middleware:',to,from) // 同Vue 组件路由守卫,可执行网络请求/简单函数操作
})

// app/pages/xxx.vue
definePageMeta({
  // middleware:['name','xxx'] // 使用局部中间件
  
  middleware:[
    (to,from)=>{ ... }, // 本地中间件
    ...
  ]
})

组合API composables ​

  • app/composables/
  • 价值:组件页面更简洁,逻辑复用
  • 注意
    • nuxt仅读取app/composables/下的文件,更深层composables下的目录将不支持自动引入,需手动 import
    • 推荐:当存在大量app/composables时,可用过外层app/composables/index.ts 重新导出即可
js
// app/composables/getUsers.ts 示例
import {ref} from 'vue'
export const getUsers = ()=>{
  const loader = ref(false)
  const names = ref(['Jay','Doe','LiHua'])
  const addUser = (args:Array) => {
    loader.value = true
    setTimeout(()=>{
      names.value = [...names.value,...args]
      loader.value = false
    },2000)
  }
  return { loader, names, addUser }
}
vue
<!-- app/pages/xxx.vue -->
<template>
	<ul v-if="users.loader.value">
    <li v-for="user from users.names.value">{{ user }}</li>
  </ul>
	<div v-else> loading... </div>
	<button @click="users.addUser('夏之一周')"> Add user name </button>
</template>

<script setup>
	const users = getUsers() 
</script>

utils

  • app/utils:小型可复用的函数,与组件无关;composables与组件状态等存在较多关联
  • 支持自动导入,默认导出时根据文件名使用
ts
// app/utils/index.ts
export const randomNumber = (min,max)=>{
  return Math.floor( Math.random()*(max-min+1) ) + min
}

插件 plugins ​

  • app/plugin 与视图/Nuxt高度相关,应用挂载前运行生效
  • 可用于实现 注册全局指令和组件
  • 注意
    • 默认扫描plugin/根目录下的内容,深层文件夹下需手动配置到nuxt.config.ts中
ts
// app/plugins/index.ts 定义插件
import MyButton from '~/components/button.vue'

export default defineNuxtPlugin(nuxtApp=>{
  // 注册全局属性
  nuxtApp.provide('red','#fff000')
  nuxtApp.provide('blue','#0000ff')
  // 注册全局函数
  nuxtApp.provide('alertFun',(name)=>{
    // ...函数
    window.alert(`Hello,${name}`)
  })
  // 注册全局组件
  nuxtApp.vueApp.component("MyButton",MyButton)
})
vue
<!-- 使用插件, app/pages/contact.vue -->
<template>
	<!-- template 中可直接使用,无需引入 -->
	<p>  Red: {{ $red }}  </p>
	<p>  Bule: {{ $blue }}  </p>
	<button @click="$alertFun('夏之一周')">Alert</button>
</template>
<script setup>
  // script 中通过useNuxtApp导出
	const { $red, $blue } = useNuxtApp()
  console.log($red, $blue)
</script>

404页 error ​

  • 访问不存在的路由将展示 默认404页面,修改404页面可通过自定义app/error.vue实现
vue
<!-- app/error.vue -->
<template>
	<h1>Error:{{ error.statusCode }}</h1>
	<p>{{ error.message }}</p>
</template>
<script setup>
	const props = defineProps(['error']) // 获取错误对象
</script>

网络请求 ​

  • 通用渲染:类php/Ruby的传统服务器呈现,
  • 类型
    • $fetch
      • 适用于用户操作的数据请求,会在服务器和客户端发起两次请求
    • useFetch: useAsyncData and $fetch的 wrapper,组合式Api且上下文感知
      • 适用于基于页面状态需要重复获取数据;不适用页面挂载后,由用户操作触发的请求
      • 返回值是响应式,多数不需要返回值再触发其他响应的请求,显得多余
      • 在服务端发起请求,然后将数据传输到客户端,不会在客户端重复发起二次请求
      • 采用强大的缓存系统+服务端请求,页面呈现更快!!!
    • useAsyncData:似于useFetch,但提供了更细粒度的控制
      • 组合式Api,类似创建promise,当所有内容解析完毕并获取所需数据时调用 resolve
  • 注意
    • 在Vue组件的setup函数中使用$fetch函数执行数据获取,这可能会导致数据被获取两次,一次在服务器上(渲染HTML),另一次在客户端上(当HTML被水合时),增加交互时间并导致不可预测的行为;而useFetch和useAsyncData通过确保在服务器上调用API,并将数据转发到客户端来解决此问题
    • 可通过useNuxtApp().payload访问的JavaScript对象。它在客户端上使用,以避免在合并期间在浏览器中执行代码时重新获取相同的数据。
  • 最佳实践
    • 最佳实践应该是参考官方的custom-useFetch,先利用plugin把$fetch封装一下(类似axios的封装),再把此实例覆盖掉 useFetch 里的封装的 $fetch,这样需要服务端渲染时,使用useFetch,其他前端交互产生的请求使用 useNuxtApp().$api即可
      • 我现在发现用 nuxt 做全栈(中小型内容站),useFetch不用封装,可以简单封一下$fetch,用来增加 headers 和 toast
vue
<template>
	<div v-if="status === 'pending'"> Loading... </div>
	<div v-else-if="status === 'error'">{{ error.message }}</div>
	<div v-else-if="status==='success'">
    <div v-for="item in MyData" :key="item.id">
      <p>UserName:{{ item.name }}</p>
      <!-- 跳转详情页,携带信息id -->
      <nuxtLink :to="`/detail/${item.id}`">Detail</nuxtLink>
  	</div>
  </div>
	
</template>

<script setup lang="ts">
 // 会同时在服务器和客户端发起请求
 const data = await $fetch('http://xxx/getUser') 
 
 const param1 = ref('value1')
// useFetch 返回值是响应式数据,需要.value
// type AsyncDataRequestStatus = 'idle' | 'pending' | 'success' | 'error'
const { data:MyData, status, error, refresh, clear } = await useFetch('/api/modules', {
  pick: ['title'], // pick 指定返回数据中仅接收 title,其他数据将被丢弃
  method:'post', // 请求方式
  body:{xxx:xxx,...}, // 自定义请求体
  query: { param1, param2: 'value2' }, // url参数
})
 console.log(MyData.value, status.value) 
  
  
  // useAsyncFetch,
  const {} = await useAsyncData('someData',()=>{
    return $fetch('http://xxx/getUser')
  })
</script>

server 服务 ​

  • 与app同级目录
    • /server
      • /api 处理api路由
        • /employees.js 将处理/api/employees的请求,包含一切请求方式POST/GET/PULL/DEL
        • /user
          • /index.get.js 仅处理/api/user下的GET请求,index可忽略
          • /index.post.js 仅处理/api/user下的POST请求,index可忽略
      • /routes 通用路由,并非关键字,使用逻辑与/api完全一致
        • 不需要加/api或/routes之类前缀,直接使用文件名进行请求
        • 建议get/post等明确请求的内容放在/api下管理更好
      • /middleware 仅限服务端的中间件,将运行在每个路由中
        • 运行顺序遵守目录结构,文件名排序
      • /plugins 服务端插件
        • 用于创建文件/扩展服务器
        • 基于 Nitro 构建,可使用 Nitro的工具,Nitro中使用了 h3.js,因此也可直接使用
        • 类似中间件middleware,任何内容都会在服务器启动时运行
      • /utils 工具
        • 内容自动注册,/server/下可直接使用
  • 已知接口处理逻辑
    • app/下使用useFetch调用server/中的API
    • server/下的/api再使用$fetch调用实际的外部接口
      • 这里仅服务端,$fetch不用担心调用两次,也不需要useFetch的响应式,或许正是设计的意义
js
/*  api or routes  */

/*
支持示例:server/api/employees.xx.js
xxx.get.js - GET 请求
xxx.post.js - POST 请求
xxx.put.js - PUT 请求
xxx.patch.js - PATCH 请求
xxx.delete.js - DELETE 请求
xxx.options.js - OPTIONS 请求
xxx.head.js - HEAD 请求
*/

// - event 包含请求和响应的一些列数据
export default defineEventHandler( async (event)=>{
	try{
      // return { hello:'夏之一周' } // 接口响应
      const body = await readBody(event) // 获取请求体数据
      const query = getQuery(event) // 获取url参数
      
      // 数据校验,错误时抛出异常
      if(body.fullname.length <= 3){
        throw createError({
          statusCode:404,
          statusMessage:'name is too short'
        }) 
      }
      
      // 获取三方接口并作为data响应
      const data = await $fetch('https://xxxx.xxx/api/user',{
        method:'POST'
        body,
      })
      return data
  }catch(error){
    throw createError(error) // 触发错误后,使用当前接口的位置也应进行try{}catch(){}
  }
})
js
/*  middleware   */
// server/middleware/02.logger.js
export default defineEventHandler((event)=>{
  console.log(`LOG: ${getRequestURL(event)}`) // 日志输出每次访问的url,在服务端
})

// server/middleware/01.auth.js
export default defineEventHandler((event)=>{
  const authToken = getCookie(event,'authx') // 获取用户cookie
  if(!authToken || authToken !== 'secretToken'){
    // throw createError({
    //  statusCode:401,
    //  statusMessage:'Unauthorized'
    // })
    console.log('Unauthorized access')
  }
  // 设置event上下文信息,可在后续任意middleware中得到
  event.config.user = {
    id:'xxxxxx',
    name:'忽然之间'
  }
})
js
/*	plugins		*/
export default defineNitroPlugin((nitroApp)=>{
  
  // 持续监听请求,收到请求就触发
  nitroApp.hooks.hook('request',(event)=>{
    console.log('Run on request') 
  })
  
  // 请求响应前 触发
  nitroApp.hooks.hook('beforeResponse',(event)=>{
    console.log('Run before response') 
  })
  
  // 请求响应后 触发
  nitroApp.hooks.hook('afterResponse',(event)=>{
    console.log('Run after response') 
  })
  
  // 请求得到错误,监听系统抛出的所有异常
  nitroApp.hooks.hook('error',(event)=>{
    console.log('Run on Error')
  })
})
typescript
// utils/index.ts

export async function getEmployees(){
  try{
    const result = await $fetch('http://xxx/xx');
    // ...其他任意逻辑
    return result
  }catch(error){
    throw error // 抛出错误
  }
}

nuxt.config.ts ​

ts
export default defineNuxtConfig({
  components:[
    {
      path:'@/components',
      pathPrefix:false // 关闭目录前缀,可直接使用组件名,默认true
    },
    {
    	path:'@/otherComponents', // 新增 components目录,自动扫描
    }
  ],
  nitro:{
    prerender:{
      // 注:预渲染的页面将不会再更新,除非再次打包
      // 方式1:指定预渲染的路由,生成SSR;
      routes:["/","/home"],
      // 方式2:预渲染所有路由,忽略指定路由
      crawlLinks:true, // 预渲染所有路由,默认为false
      ignore:["/"], // 要忽略预渲染的路由
    }
  },
  routeRules:{
    // 指定路由规则
    "/":{
      prerender:true, // 启用首页 / 的预渲染
    }
  },
  experimental:{
    // 启用试验性功能
    inlineRouteRules:true, // 启用页面级别定义路由规则
    crossOriginPrefetch: true, // 启用使用推测规则API的跨域预取
    defaults: {
      nuxtLink: {
        prefetch: false, // 禁用应用全局的预取
      },
    },
  }
})

打包构建 ​

  • 预渲染配置请查看 nuxt.config.ts
shell
generate # 生成静态应用版本,打包时会预先做一次接口数据的获取,实际运行后接口需额外部署

build # 生成传统SSR服务器渲染版本应用

preview # 在预览模式下运行 pnpm build 产生的生产构建

进阶 ​

Nuxt运行过程 ​

  • pnpm run dev
  • Nuxt处理流将首先启动【全程在服务端,nuxt将预先构建并运行应用版本】
    • 初始化 Nitro ,SSR实现引擎
    • 运行 Srever Middlewares 服务端中间件
    • 运行服务端 Plugins 插件 并启动Nuxt应用
    • 处理 Route 路由
    • 运行 APP Middlewares 应用中间件,进行路由验证
    • 创建路由,运行应用中间件
    • 渲染页面和组件 并输出 HTML
    • 将输出提供给客户端
  • 客户端运行
    • 启动 Nuxt
    • 执行插件 Execute Plugins
    • 路由验证/处理 Route Validation
    • 运行中间件 Nuxt App Mlddlewares
    • 挂在应用 Mount App
    • 设置水合过程 Hydration ready

useNuxtApp与tryUseNuxtApp ​

useNuxtApp 和 tryUseNuxtApp 是访问应用运行时上下文(Runtime Context)的两个核心 Composable。它们都用于获取 nuxtApp 实例,但核心区别在于错误处理机制和适用场景

  • useNuxtApp() 是访问 Nuxt 运行时上下文的标准方式。它返回一个 nuxtApp 对象,该对象是客户端和服务器端共享的运行时核心,包含了 Vue 实例、运行时钩子、SSR 上下文等关键信息
  • tryUseNuxtApp() 是 useNuxtApp() 的安全版本。当你在一个不一定处于 Nuxt 运行时上下文中的地方调用它时(例如某些独立的工具函数、早期执行的中间件等),使用它可以避免应用崩溃
    • 路由中间件:在 defineNuxtRouteMiddleware 中,Vue 实例可能尚未完全初始化。此时直接调用 useNuxtApp() 可能会出错或获取到不完整的状态。使用 tryUseNuxtApp() 可以安全地检查上下文是否存在
    • 可选依赖的 Composable:当一个 Composable 可以在 Nuxt 项目内外复用时,使用 tryUseNuxtApp() 可以优雅地降级处理。
    • 优先使用 useNuxtApp():在绝大多数应用开发场景(组件、插件、Composable)中,useNuxtApp() 是首选。只有当你明确知道当前环境可能没有 Nuxt 运行时上下文时,才应使用 tryUseNuxtApp()。
特性useNuxtApp()tryUseNuxtApp()
行为获取 nuxtApp 实例,若不可用则抛出错误获取 nuxtApp 实例,若不可用则返回 null
返回类型NuxtApp`NuxtApp
适用场景组件、插件、Composable 等必须依赖运行时的场景可选依赖、工具函数、中间件等非必须依赖运行时的场景
js
// 1.可以通过 vueApp 属性注册全局组件、指令或安装 Vue 插件
const nuxtApp = useNuxtApp()
// 注册全局组件
nuxtApp.vueApp.component('MyComponent', MyComponent)
// 注册全局指令
nuxtApp.vueApp.directive('my-directive', myDirective)


// 2.运行时钩子 (hook / callHook)
const nuxtApp = useNuxtApp()
// 注册钩子
nuxtApp.hook('page:start', () => {
  console.log('页面开始渲染')
})
// 手动调用钩子
await nuxtApp.callHook('my-custom-hook')


// 3.注入全局方法 (provide),可在任何组件或 Composable 中通过 $ 前缀访问
// 在插件或组件中注入
const nuxtApp = useNuxtApp()
nuxtApp.provide('greet', (name: string) => `Hello, ${name}!`)

// 在任何地方使用
const { $greet } = useNuxtApp()
console.log($greet('Nuxt')) // 输出: Hello, Nuxt!


// 4. 访问 SSR 上下文 (ssrContext / payload)
// 在服务器端,可以访问 ssrContext 获取请求信息;payload 则用于在 SSR 和客户端之间传递数据
const nuxtApp = useNuxtApp()
// 仅在服务器端可用
if (process.server) {
  console.log(nuxtApp.ssrContext.url)
}
// 客户端和服务器端都可用
console.log(nuxtApp.payload.serverRendered)

Nitro ​

h3.js ​

js
getCookie(event,'authx')
setCookie()
send()
//... https://h3.dev/utils

Modules ​

nuxt-auth-utils 会话认证 ​

nuxt-auth-utils 为管理客户端和服务器端会话数据提供了便捷的工具;它使用安全且密封的 Cookie 来存储会话数据,因此您无需设置数据库来存储会话信息。

js
npx nuxt module add auth-utils 	// 1.使用 nuxt CLI 安装 nuxt-auth-utils 模块

// 2.配置环境变量
// - 配置Cookie存储会话数据的密钥,默认添加并使用环境变量中的 NUXT_SESSION_PASSWORD 密钥进行加密
// - 开发环境中如果未设置 NUXT_SESSION_PASSWORD,Nuxt Auth Utils 会自动生成一个密码。
NUXT_SESSION_PASSWORD=a-random-password-with-at-least-32-characters // .env文件

// 3.验证安装 auth-utils默认使用 /api/_auth/session 路由来进行会话管理
// - 访问 /api/_auth/session 应该返回空会话对象,表明模块已成功安装


// 4.用户会话管理
// - app层 使用 useUserSession() 获取用户会话数据
// - server层 使用setUserSession()、getUserSession()、clearUserSession()等处理用户会话数据

// OAuth 认证集成
// - 支持 40+ 主流 OAuth 提供商,包括 GitHub、Google、Facebook 等


// 推荐目录结构
server/
├── api/
│   ├── login.post.ts        # 密码登录 API
│   └── register.post.ts     # 用户注册 API
├── routes/
│   └── auth/
│       ├── github.get.ts    # GitHub OAuth 路由
│       └── google.get.ts    # Google OAuth 路由
└── plugins/
    └── session.ts           # 会话钩子配置
vue
<script setup>
<!-- 
  Nuxt Auth Utils 提供 useUserSession() 组合式函数,方便在组件中管理用户会话
  - ready 认证会话是否已经准备就绪。在页面初始加载验证 Cookie 期间为 false,验证完成后变为 true
  - loggedIn  计算属性,用于指示当前用户是否已经登录
  - user 如果用户已登录,该属性会返回包含用户信息的对象;如果未登录,则返回 null
  - session 返回当前的会话对象,后端配置时的整个会话配置,可能包含loggedInAt等
  - fetch 从服务器重新获取当前的用户会话数据,常用于登录后刷新客户端会话数据
  - clear 清除当前的用户会话并移除会话 Cookie(即实现退出登录功能)例如:@click="clear"
  - openInPopup 用于在弹窗(Popup)中打开 OAuth 授权路由;授权成功后,弹窗会自动关闭;可绑定到第三方登录按钮上(例如:@click="openInPopup('/auth/github')"),以提供弹窗式的第三方登录体验。
-->
const { loggedIn, user, session, fetch, clear, ready,openInPopup } = useUserSession()
</script>

<template>
  <div v-if="loggedIn">
    <h1>欢迎回来,{{ user.name }}!</h1>
    <p>登录时间:{{ new Date(session.loggedInAt).toLocaleString() }}</p>
    <button @click="clear">退出登录</button>
  </div>
  <div v-else>
    <h1>请登录</h1>
    <button @click="openInPopup('/auth/github')">使用 GitHub 登录</button>
  </div>
</template>
js
<!-- 
	服务端 server/ 目录下,模块会自动导入一系列辅助函数,用于在服务端管理会话和验证用户身份
  - setUserSession(event, data) 设置用户会话。数据会被加密存储在 Cookie 中。会自动合并新数据与现有数据
  - replaceUserSession(event, data):替换用户会话。与 setUserSession 类似,但不会合并数据,而是完全覆盖
  - getUserSession(event):获取当前的用户会话数据
  - clearUserSession(event):清除当前的用户会话,自动完成“服务端会话销毁+客户端Cookie清除+前端状态重置”的完整流程
  - requireUserSession(event):强制要求验证用户会话。如果请求中没有有效的 user 会话,该函数会直接抛出 401 错误。确保只有登录用户才能访问
-->
// 设置用户会话
await setUserSession(event, {
  user: { id: '123', name: 'John Doe' },
  loggedInAt: new Date()
})
 
// 获取当前会话
const session = await getUserSession(event)
 
// 清除会话
await clearUserSession(event)
 
// 验证用户认证状态(未认证时返回 401),一般放在全局的认证拦截中间件
const session = await requireUserSession(event)
密码认证实现 ​
js
// 哈希密码,用户密码入库前加密
const hashedPassword = await hashPassword('user-password')
 
// 验证密码,对比用户传入的 password 和数据库中加密后的 passwordHash
const valid = await verifyPassword(user.passwordHash, password)
if (!valid) {
  throw createError({ statusCode: 401, message: '密码错误' })
}
 
// 检查是否需要重新哈希(当哈希配置改变时)
if (passwordNeedsRehash(hashedPassword)) {
  const newHashedPassword = await hashPassword('user-password')
}



<!--【登录API模版】server/api/login.post.ts -->
export default defineEventHandler(async (event) => {
  const { email, password } = await readBody(event)
  
  // 从数据库获取用户
  const user = await db.getUserByEmail(email)
  if (!user) {
    throw createError({ statusCode: 401, message: '用户不存在' })
  }
  
  // 验证密码
  const valid = await verifyPassword(user.passwordHash, password)
  if (!valid) {
    throw createError({ statusCode: 401, message: '密码错误' })
  }
  
  // 设置用户会话
  await setUserSession(event, {
    user: { id: user.id, email: user.email, name: user.name },
    loggedInAt: new Date()
  })
  
  return { success: true }
})
会话钩子 ​

会话钩子可以扩展会话功能,如添加自定义数据或记录日志

js
// server/plugins/session.ts
export default defineNitroPlugin(() => {
  // 会话获取时触发
  sessionHooks.hook('fetch', async (session, event) => {
    // 从数据库加载用户详细信息
    if (session.user) {
      session.user.profile = await db.getUserProfile(session.user.id)
    }
  })
  
  // 会话清除时触发
  sessionHooks.hook('clear', async (session, event) => {
    // 记录用户登出日志
    await db.logoutLog(session.user.id)
  })
})
其他配置 ​
js
export default defineNuxtConfig({
  runtimeConfig: {
    auth: {
    	loadStrategy: 'client-only' // 仅在客户端加载会话
  	},
    // 会话配置
    session: {
      maxAge: 60 * 60 * 24 * 7, // 1周有效期
      cookie: {
        secure: process.env.NODE_ENV === 'production',
        sameSite: 'strict'
      }
    }
  }
})
<AuthState> 组件 ​
vue
<!--  使用 <AuthState> 组件安全显示认证状态 -->
<template>
  <AuthState v-slot="{ loggedIn, clear }">
    <button v-if="loggedIn" @click="clear">退出登录</button>
    <NuxtLink v-else to="/login">登录</NuxtLink>
    <template #placeholder>
      <button disabled>加载中...</button>
    </template>
  </AuthState>
</template>
WebAuthn (Passkey) 认证 ​

WebAuthn 提供无密码认证方式,通过生物识别或安全密钥进行身份验证

js
npx nypm i @simplewebauthn/server @simplewebauthn/browser   // 安装依赖

// nuxt.config.ts 中启用启用WebAuthn
export default defineNuxtConfig({
  auth: {
    webAuthn: true
  }
})

// 使用....

sitemap ​

用于生成最佳实践XML站点地图的模块,这些站点地图由抓取您站点的机器人使用

js
npx nuxi@latest module add sitemap // 项目中安装sitemap

// nuxt.config.ts
export default defineNuxtConfig({
  // ...
  modules:['@nuxtjs/sitemap'],
  site:{
    url:'https://xxxx.xx/', // 线上链接
    name:'My App'
  }
})

NuxtImage ​

vue
<template>
	<NuxtImg src="/images/horse.jpg" alt="horse" width="300" height="200" format="webp" quality="10"/>
</template>

NuxtUI ​

@pinia/nuxt ​

其他常用库 ​

  • mongoose 数据库
  • bcrypt 加密

其他备注 63 ​

image-20240310231810882

image-20240310231831289