Nuxt
V4.1.3
说明:
- 默认情况下使用服务器端渲染
server-side rendering by default - NodeJS 确保使用偶数编号的版本
Node.js: Make sure to use an even numbered version
- 默认情况下使用服务器端渲染
常见问题
约定
app/components/目录中创建的组件,可直接使用<文件名 />,无需额外importapp/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文件- 自定义布局可以设置为页面元数据的一部分
资源/样式
app/assets/- 【自然位置 natural place】CSS样式 local stylesheets;样式表将内联呈现在Nuxt的HTML中,style设置scoped仍有效
public
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时,会自动提前预取链接页面的组件和有效负载(生成的页面),从而加快导航速度
- 当
useRouternavigateTo路由跳转- 在路由中间件中使用
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
- 默认执行顺序,根据文件名/目录a-z排序,按顺序执行,可手动命名顺序
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重新导出即可
- nuxt仅读取
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:useAsyncDataand$fetch的 wrapper,组合式Api且上下文感知- 适用于基于页面状态需要重复获取数据;不适用页面挂载后,由用户操作触发的请求
- 返回值是响应式,多数不需要返回值再触发其他响应的请求,显得多余
- 在服务端发起请求,然后将数据传输到客户端,不会在客户端重复发起二次请求
- 采用强大的缓存系统+服务端请求,页面呈现更快!!!
useAsyncData:似于useFetch,但提供了更细粒度的控制- 组合式Api,类似创建promise,当所有内容解析完毕并获取所需数据时调用 resolve
- 注意
- 在Vue组件的setup函数中使用
$fetch函数执行数据获取,这可能会导致数据被获取两次,一次在服务器上(渲染HTML),另一次在客户端上(当HTML被水合时),增加交互时间并导致不可预测的行为;而useFetch和useAsyncData通过确保在服务器上调用API,并将数据转发到客户端来解决此问题 - 可通过
useNuxtApp().payload访问的JavaScript对象。它在客户端上使用,以避免在合并期间在浏览器中执行代码时重新获取相同的数据。
- 在Vue组件的setup函数中使用
- 最佳实践
- 最佳实践应该是参考官方的custom-useFetch,先利用
plugin把$fetch封装一下(类似axios的封装),再把此实例覆盖掉 useFetch 里的封装的$fetch,这样需要服务端渲染时,使用useFetch,其他前端交互产生的请求使用useNuxtApp().$api即可- 我现在发现用 nuxt 做全栈(中小型内容站),useFetch不用封装,可以简单封一下$fetch,用来增加 headers 和 toast
- 最佳实践应该是参考官方的custom-useFetch,先利用
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/中的APIserver/下的/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/utilsModules
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


