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
      • 默认为在pointerenterclickfocus上进行水合
    • 所有延迟水合组件在水合时都会发出@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>

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
      • 适用于用户操作的数据请求,会在服务器和客户端发起两次请求
    • useFetchuseAsyncData 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

Nitro

h3.js

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

Modules

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

image-20240310231810882

image-20240310231831289

访客总数 总访问量统计始于2024.10.29