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>23.9- 24.5 // 1年
24.5-25.11 // 2年
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
Nitro
h3.js
js
getCookie(event,'authx')
setCookie()
send()
//... https://h3.dev/utilsModules
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
nuxt-auth-utils
@pinia/nuxt
其他常用库
- mongoose 数据库
- bcrypt 加密
其他备注 63


