一、背景介绍
2022 年 11 月 16 日,全球最大的 Nuxt 会议 Nuxt Nation 2022 在线举行,并正式发布了 Nuxt.js 3.0 的第一个稳定版本。Nuxt 3 是基于 Vite、Vue3 和 Nitro 的 Nuxt 框架的现代重写,具有一流的 Typescript 支持,是两年多研究、社区反馈、创新和实验的结果。为每个人提供了一个愉快的 Vue 全栈开发体验。
因为nuxt优越的服务端渲染能力,所以对于nuxt3项目关注已久!我们开始吧!
二、官方网址
Nuxt 3 - 中文文档
Nuxt - The Intuitive Vue Framework
Announcing 3.0 · Nuxt
Nuxt - Installation
https://github.com/nuxt/create-nuxt-app/blob/master/README.md
三、新特性介绍
1、更轻量:以现代浏览器为目标的服务器部署和客户端产物最多可缩小 75 倍
2、更快:基于 nitro 提供动态代码分割能力,以优化冷启动性能
3、Hybrid:增量静态生成和其他的高级功能现在都成为可能
4、Suspense:在任意组件和导航前后都可以获取数据
5、Composition API:使用 Composition API 和 Nuxt 3 的 composables 实现真正的代码复用
6、Nuxt CLI:没有任何依赖,帮你轻松搭建项目和集成模块
7、Nuxt Devtools:通过直接在浏览器中查看信息和快速修复实现更快地工作
8、Nuxt Kit:具有 Typescript 和跨版本兼容性的全新模块开发
9、Webpack 5:更快的构建时间和更小的包大小,无需配置
10、Vite:使用 Vite 作为打包工具,体验闪电般快速的 HMR
11、Vue 3:Vue 3 是你下一个 Web 应用程序的坚实基础
12、TypeScript:使用原生 TypeScript 和 ESM 构建,无需额外步骤
Nuxt 3.0 稳定版正式发布,基于 Vue 3 的 Web 框架
四、重要概念
SPA应用:也就是单页应用,这些多是在客户端的应用,不能进行SEO优化(搜索引擎优化)。
SSR应用:在服务端进行渲染,渲染完成后返回给客户端,每个页面有独立的URL,对SEO友好。
约定式路由:不需要配置路由,在根目录pages目录下创建业务页面,即可访问。
约定式配置:如layouts布局、middleware、plugins
五、启动基础项目
https://github.com/nuxt/create-nuxt-app/blob/master/README.md
5.1、创建项目:
创建nuxt3项目:
pnpm dlx nuxi init nuxt-app
npx nuxi init nuxt3-app
5.2、安装依赖
pnpm install --shamefully-hoist
5.3、启动项目
启动成功
六、项目配置、基础使用
6.1、访问pages/index.vue
根目录app.vue,<NuxtWelcome />替换为:<NuxtPage/>
<template>
<NuxtPage/>
<!-- <div>
<NuxtWelcome />
</div> -->
</template>
pages/index.vue
<template>
<div>
<h1>snow</h1>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
pages/about.vue
<template>
<div>
<h1>about</h1>
</div>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
这样就可以访问页面了:
6.2、动态路由
6.2.1、目录结构
6.2.2、访问:/user
如果没有pages/user/index页面,只有[id].vue,访问的时候式404页面
6.2.3、访问:/user/1,动态路由,获取动态路由参数
{{$route.params.id}}
6.2.4、访问:/user/1?name=snow,动态路由,获取路由普通参数:
{{$route.query.name}}
6.2.5、通过useRoute()获取路由信息
const route = useRoute()
<template>
<div>
<h1>user-id:{{$route.params.id}}</h1>
<h1>user-name:{{$route.query.name}}</h1>
</div>
</template>
<script setup lang="ts">
const route = useRoute()
console.log(route.params, route.query)
</script>
<style scoped>
</style>
6.3、路由跳转
经过测试一下几种方式均成功跳转。页面内容均正常展示。
6.3.1、
<NuxtLink to="/user/1">user-id</NuxtLink>
6.3.2、
6.3.3、
<NuxtLink :to="{ path: `/user/${3}`}">user-id-3</NuxtLink>
6.3.4、编程式路由
<script setup lang="ts">
function toUser() {
navigateTo({
path: '/user/4',
query: {
name: 'snow'
}
})
}
</script>
6.4、加载静态资源
<div>
<img src="~/assets/nginx.jpg" alt="nginx">
<img src="~/public/nginx.jpg" alt="nginx">
</div>
nuxt项目tsconfig.json有配置,public目录下的文件可以省略/public目录,我这里测试不可以,后续会再研究
已测试成功,可以省略 “~/public” 目录
6.5、设置页面title,不会在页面展示,只会在页面title部分展示
6.5.1、
<Title>snow-title</Title>
<template>
<Title>snow-title</Title>
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
如图:
ctrl + u:
6.5.2、使用useHead()方法
useHead({
title: 'snow-title-2'
})
设置成功,useHead优先级小于<Title>标签。
6.5.3、同理使用useHead设置页面的TDK信息
<script setup lang="ts">
useHead({
title: 'snow-title-2',
meta: [
{ name: 'description', content: 'snow-desc'},
{ name: 'keywords', content: 'snow-kw'}
]
})
</script>
6.6、layouts
6.6.1、理解:布局、页面布局、公共布局、基础布局
6.6.2、根目录创建layouts目录,/layouts/default.vue
<template>
<div>
snow-default-layout
</div>
<slot />
</template>
<script setup lang="ts">
</script>
<style scoped>
</style>
6.6.3、app.vue
<template>
<NuxtLayout>
<NuxtPage/>
</NuxtLayout>
<!-- <div>
<NuxtWelcome />
</div> -->
</template>
6.6.4、
6.6.5、某页面不想使用layout:
definePageMeta({
layout: false
})
6.6.6、某页面使用指定layout
definePageMeta({
layout: 'layout-snow'
})
6.6.7、动态设置layout
const router = useRoute()
function enableLayout () {
router.meta.layout = "layout-snow"
}
6.6.8、全局设置,指定layout
<template>
<NuxtLayout :name="layoutSnow">
<NuxtPage/>
</NuxtLayout>
<!-- <div>
<NuxtWelcome />
</div> -->
</template>
<script setup lang="ts">
const layoutSnow = ref("layout-snow")
</script>
6.7、plugins
/plugins/index.ts
export default defineNuxtPlugin(()=>{
return {
provide: {
hello: (msg: string) => `hello ${msg}`
}
}
})
/pages/plugin.vue
<template>
<div>
snow-plugin
</div>
</template>
<script setup lang="ts">
const { $hello } = useNuxtApp()
console.log('9', $hello('world'))
</script>
<style scoped>
</style>
6.8、middleware
/middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from)=>{
console.log("auth")
})
/middleware/snow.ts
export default defineNuxtRouteMiddleware((to, from)=>{
console.log("snow")
})
/pages/plugin.vue
<template>
<div>
snow-plugin
</div>
</template>
<script setup lang="ts">
definePageMeta({
middleware: ["auth", "snow"]
})
</script>
<style scoped>
</style>
如图:
6.9、server
6.9.1、目录
6.9.2、server/api/hello.ts
export default defineEventHandler((event) => {
return {
api: "works"
}
})
6.9.3、server/api/test.get.ts
export default defineEventHandler((event) => {
return `test get ts`
})
6.9.4、server/api/test.post.ts
export default defineEventHandler((event) => {
return `test post ts`
})
post请求需要使用postman工具:
6.9.5、 server/api/foo/[...].ts
可以带有参数
export default defineEventHandler(() => `default api handle`)
6.9.6、server/api/submit.post.ts
export default defineEventHandler (async (event) => {
const body = await readBody(event);
return { body }
})
6.9.7、server/api/query.get.ts
export default defineEventHandler((event) => {
const query = getQuery(event)
return { a: query.param1, b: query.param2 }
})
6.10、使用element-plus
6.10.1、安装
pnpm install element-plus
pnpm add sass sass-loader -D
pnpm add unplugin-auto-import unplugin-icons unplugin-vue-components -D
6.10.2、package.json
{
"private":true,
"scripts":{
"build":"nuxt build",
"dev":"nuxt dev",
"generate":"nuxt generate",
"preview":"nuxt preview",
"postinstall":"nuxt prepare"
},
"devDependencies":{
"nuxt":"3.0.0",
"sass":"1.57.1",
"sass-loader":"13.2.8",
"unplugin-auto-import":".12.1",
"unplugin-icons":".14.15",
"unplugin-vue-components":".22.12"
},
"dependencies":{
"element-plus":"^2 .2.27"
}
}
6.10.3、assets/scss/index.scss
@use "element-plus/dist/index.css";
6.10.4、tsconfig.json
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json",
"compilerOptions": {
"types": ["element-plus/global"]
}
}
6.10.5、 nuxt.config.ts
import AutoImport from 'unplugin-auto-import/vite'
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import IconsResolver from "unplugin-icons/resolver";
const lifecycle = process.env.npm_lifecycle_event;
export default defineNuxtConfig({
vite: {
plugins: [
AutoImport({
resolvers: [
ElementPlusResolver(
),
IconsResolver()]
}),
Components({
dts: true,
resolvers: [ElementPlusResolver(
{
importStyle: false
}
)]
}),
],
},
components: true,
css: ["~/assets/scss/index.scss"],
transpile: ["element-plus"],
build: {
transpile: lifecycle === "build" ? ["element-plus"] : [],
},
})
6.10.6、plugins/element-plus.ts
import { ID_INJECTION_KEY } from 'element-plus';
export default defineNuxtPlugin(nuxtApp => {
// Doing something with nuxtApp
nuxtApp.vueApp.provide(ID_INJECTION_KEY,{
prefix: Math.floor(Math.random() * 10000),
current: 0,
})
})
6.10.7、pages/index.vue
<el-button> ElButton </el-button>
6.10.8、成功实现效果
6.11、获取数据 | Nuxt 3 - 中文文档
请求数据的方法有:useAsyncData、useLazyAsyncData (useAsyncData+lazy:true)、useFetch、useLazyFetch (useFetch+lazy:true)
pages/req.vue
<template>
<div class="container">
<h1>snow</h1>
</div>
</template>
<script setup lang="ts">
const token = useCookie("token");
const useFetchData = await useFetch('https://md.abc.com.cn/m-staff-center/api/v1/role/pageList',{
method: "get",
headers: {
'Authorization' : `Bearer ${token.value}`
}
})
console.log('17useFetchData', useFetchData.data._rawValue)
const useAsyncDataData = await useAsyncData('getRoleList', () => $fetch('https://md.abc.com.cn/m-staff-center/api/v1/role/pageList', {
method: "get",
headers: {
'Authorization' : `Bearer ${token.value}`
}
}))
console.log('24useAsyncDataData', useAsyncDataData.data._rawValue)
</script>
数据获取成功:
注:1,这里使用的全路径接口地址请求的数据,没有因为是本地开发出现跨域问题,同时没有配置代理。2、多次尝试配置代理,使用方法包括vite、nitro,均没有成功,网上暂时也没有找到明确的解答。3、如果后续有了代理相关的进展会及时更新。4、既然本地使用全路径没有跨域问题,那么我考虑在封装请求时候使用全局环境变量来拼接不同环境的域名+接口地址。
6.12、封装请求
6.12.1、utils/request.ts
import { ElMessage } from 'element-plus'
const fetch = (url: string, options?: any): Promise<any> => {
const token = useCookie("token");
const headers = { // headers信息
'Authorization' : `Bearer ${token.value}`
}
const { public: { baseUrl } } = useRuntimeConfig()
const reqUrl = baseUrl + url
return new Promise((resolve, reject) => {
useFetch(reqUrl, { ...options, headers }).then(({ data }: any) => {
const value = data.value
if (!data._rawValue) {
// 这里处理错误回调
reject(value)
}else if(data._rawValue.code !== '0'){
ElMessage({
message: data._rawValue.msg,
type: 'error',
})
} else {
console.log('40data', data._rawValue)
resolve(ref(data))
}
}).catch((err: any) => {
reject(err)
})
})
}
export default new class Http {
get(url: string, params?: any): Promise<any> {
return fetch(url, { method: 'get', params })
}
post(url: string, params?: any): Promise<any> {
return fetch(url, { method: 'post', params })
}
put(url: string, body?: any): Promise<any> {
return fetch(url, { method: 'put', body })
}
delete(url: string, body?: any): Promise<any> {
return fetch(url, { method: 'delete', body })
}
}
6.12.2、baseUrl
// 参见本文6.13多环境开发
import { loadEnv } from 'vite'
runtimeConfig: { // 运行时常量
public: { // 客户端-服务的都能访问
baseUrl: loadEnv(process.argv[process.argv.length-1], './env').VITE_SERVER_NAME
}
},
6.12.3、utils/api.ts
import Http from '@/utils/request'
export const config1 = (params: any) => {
return Http.get('/m-staff-center/api/v1/pageList', params)
}
export const getVideoList = (params: any) => {
return Http.post('/m-staff-center/api/v1/getName', params)
}
6.12.4、使用
config1('').then((res: any) => {
console.log('27', res)
}).catch((err: any)=>{
console.log('29', err)
})
getVideoList('').then((res: any) => {
console.log('51', res)
}).catch((err: any)=>{
console.log('54', err)
})
经测试,成功。
6.13、多环境开发
生产使用的项目通常会有dev、test、uat、pre、prd等环境,我们需要配置多种开发环境满足企业级开发要求。
6.13.1、根目录创建env目录,创建环境变量文件
文件命名规则 .env.[环境变量名,如dev]
.env.dev文件,其他文件同理
# 请求接口地址
VITE_REQUEST_BASE_URL = '/m-staff-center/api/v1'
VITE_SERVER_NAME = 'https://md.abc.com.cn/'
# VITE开头的变量才会被暴露出去
6.13.2、package.json
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev --mode dev",
"test": "nuxt dev --mode test",
"uat": "nuxt dev --mode uat",
"pre": "nuxt dev --mode pre",
"prd": "nuxt dev --mode prd",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
6.13.3、nuxt.config.ts
import { loadEnv } from 'vite'
console.log('基础服务路径', loadEnv(process.argv[process.argv.length-1], './env').VITE_SERVER_NAME)
多环境配置成功,环境变量可以用于本地代理使用,如本地代理使用环境域名,等。
6.14、服务器部署
npm run build
使用“混合渲染模式”创建一个.output目录。
其中包含所有应用程序、服务器和依赖项,可用于生产。
可通过node、pm2等启动后提供WEB服务。
打包成混合渲染程序,常用。
打包生成.output
npm run generate
使用“SPA方式预渲染应用程序”的每个路由(启动server对整个程序代码中涉及的URL进行一次服务端和客户端渲染),将结果存储在纯 HTML 文件中,可以部署在任何静态托管服务上。
该命令触发nuxi build带有prerender:true的参数。
打包成SPA客户端渲染程序,常用。
打包生成dist
build:混合渲染模式,node启动项目(或者使用pm2),nginx做代理转发。
node
node ./.output/server/index.mjs
pm2
pm2 start ecosystem.config.js
// 使用pm2,根目录创建ecosystem.config.js
module.exports = {
apps: [
{
name: 'NuxtAppName',
exec_mode: 'cluster',
instances: 'max',
script: './.output/server/index.mjs',
}
]
}
未测试。
// nginx配置
{
listen 80;
server_name www.abc.com; // 域名
location / {
proxy_pass http://127.0.0.1:3000; // 转发到当前服务器3000端口
}
}
已测试,成功。
generate:预渲染模式,生成静态资源,nginx直接启服务。
已测试,成功。
6.15、其他内容
待补充
七、过程记录
7.1、init项目不成功
解1:墙内的同学可能创建不成功,这里推荐一个链接:https://github.com/nuxt/starter
解2:开始使用科学上网的方法也没有下载成功,后来使用了付费科学上网的方法下载成功。下载成功后如图。
7.2、useHead接收参数如下:
useHead(meta: Computable<MetaObject>): void
interface MetaObject extends Record<string, any> {
charset?: string
viewport?: string
meta?: Array<Record<string, any>>
link?: Array<Record<string, any>>
style?: Array<Record<string, any>>
script?: Array<Record<string, any>>
noscript?: Array<Record<string, any>>
titleTemplate?: string | ((title: string) => string)
title?: string
bodyAttrs?: Record<string, any>
htmlAttrs?: Record<string, any>
}
charset: 指定 HTML 的字元编码,预设为 utf-8。
viewport: 设定网页的可见区域,预设为 width=device-width, initial-scale=1。
meta: 接受一个阵列,阵列中的每个元素,都將会建立一个 <meta> 标记,元素中物件的属性将对应至的属性。
link: 接受一个阵列,阵列中的每个元素,都将会建立一个 <link> 标记,元素中物件的属性将对应至的属性。
style: 接受一个阵列,阵列中的每個元素,都将会建立一个 <style> 标记,元素中物件的属性将对应至的属性。
script: 接受一个阵列,阵列中的每個元素,都将会建立一个 <script> 标记,元素中物件的属性将对应至的属性。
noscript: 接受一个阵列,阵列中的每個元素,都将会建立一个 <noscript> 标记,元素中物件的属性将对应至的属性。
titleTemplage: 接受一個字串或函數,用來动态的设定该页面元件的网页标题。
title: 在页面元件设置静态的网页标题。
bodyAttrs: 接受一个物件,设置网页中标识的属性,物件中的属性将对应至的属性。
htmlAttrs: 接受一个物件,设置网页中标识的属性,物件中的属性将对应至的属性。
理解useHead:相当于设置页面<head>标签内相关内容。
7.3、出现类似包不能解析的问题
解决:
根目录创建 .npmrc 文件
shamefully-hoist = true
删除node_modules,再次执行pnpm install ,解决成功
有些包仅在根目录的node_modules时才有效,可以通过此配置,提升那些不在node_modules根目录的包。
或者执行:
pnpm i --shamefully-hoist
两种方法均测试成功。
7.4、publicPath (nuxt2.X) 等同于 baseURL (nuxt3.0)
Nuxt Configuration Reference · Nuxt
7.4.1、nuxt2.x,nuxt.config.js
build: {
publicPath: process.env.PUBLIC_PATH,
}
7.4.2、nuxt3,nuxt.config.ts
app: {
baseURL: '/m-abc-pc',
}
7.4.3、使用场景
使用这一项配置的时候请求资源的地址就会有这个目录,如下图,在服务区部署时候很重要。
7.5、开发环境修改端口
7.6、设置网站 favicon
7.6.1、favicon.ico 文件放在public目录下。
7.6.2、nuxt.config.ts app/head/link下增加相应内容
link: [
{ rel: "icon", href: "/favicon.ico", type: 'image/x-icon'},
],
经过测试,成功。
八、欢迎交流指正,关注我,一起学习。
参考链接:
Configuration | ⚗️ Nitro
[Day 24] Nuxt 3 搜尋引擎最佳化 (SEO) 與 HTML Meta Tag - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天
Nuxt3-动态更改meta信息(关键词、标题、描述)_fat_shady的博客-CSDN博客
令人愉快的 Nuxt3 教程 (一): 应用的创建与配置 - 知乎
技术胖-Nuxt3从零到实战手把手 免费视频图文教程
Nuxt.js 3.0 正式发布! - 知乎
版权归原作者 snowball@li 所有, 如有侵权,请联系我们删除。