nuxt.config.ts的配置
1. 配置应用标题和图标
arduino
export default ({
app: {
head: {
title: '我是nuxt应用', // 应用的默认标题
charset: "utf-8", // 字符集
htmlAttrs: {
lang: "zh-CN", // HTML的语言属性为"zh-CN"(中文)
},
// 修改页签图标
link: [
{
rel: 'icon',
type: 'image/x-icon',
href: '/favicon.png', // 该图片文件的放置在根目录下的public下
},
],
},
},
...
});
应用的头部配置需要放置在app:{}
中,
2. 配置渲染方式
服务端渲染
arduino
export default ({
ssr: true
...
});
客户端渲染
arduino
export default ({
ssr: false
...
});
混合渲染
arduino
export default ({
// ssr: false 这里可以不用写
routeRules: {
'/': { ssr: true }, // 开启服务端渲染
'/route1': { ssr: false }, // 仅客户端渲染
'/route2': { ssr: false }, // 仅客户端渲染
'/route1/*': { ssr: false },// 仅客户端渲染
'/route2/*': { ssr: false },// 仅客户端渲染
},
...
});
3. 配置端口
yaml
export default ({
devServer: {
port: 8080, //配置端口为8080
},
server: {
port: 3000,
host: '0.0.0.0', //指定应用在生产环境中可被访问的主机地址。设置为 '0.0.0.0' 表示应用将接受来自任何 IP 地址的请求。
timing: false, //用于启用或禁用服务器端请求计时,这对于调试和性能监控可能有用。
},
...
});
server
配置是针对生产环境的,而 devServer
配置是针对开发环境的。
4. 配置开发代理--网关配置
nuxt3的代理需要在nitro里面进行配置
arduino
export default ({
nitro: {
devProxy: {
"/api": {
target: "http://xxx.xxx.x.xxx:xxxx", // 后端的接口地址
changeOrigin: true,
prependPath: true,
},
},
routeRules: {
'/api/**': {
proxy: 'http://xxx.xxx.x.xxx:xxxx/**'
}
}
},
...
});
注意routeRules后面还有/**
,这是必不可少的,devProxy
是客户端渲染的,而 routeRules
是针对的服务端渲染的。
5. 多环境开发配置
假设我们有多个环境,如dev、sit环境,这里采用的是通过命令行传参,定义构建的是哪个环境,并在项目里接收参数设置环境变量。
在根目录下新建一个文件,这里叫作build.js,进行参数的接收
javascript
// build.js
// 解析命令行参数
//这里是获取构建命令 --env=环境,注意,我们通过process.env.npm_config_argv获取的对象是字符串的形式,所以需要通过JSON.parse进行转换
const envArg = JSON.parse(process.env.npm_config_argv)?.original[1];
//sit
const env = envArg ? envArg.split('=')[1] : 'dev'; //
// 设置环境变量
process.env.NUXT_ENV_MODE = env;
// 接下来,调用 Nuxt 构建命令或其他需要的脚本
console.log(`正在以 ${env} 模式构建...`);
在nuxt.config.ts文件引用,并且修改nitro routeRules的proxy
javascript
import './build.js';
...
function getEnv(env: any) {
let api = '';
switch (env) {
case 'dev':
api = 'http://dev-api/**';
break;
case sit:
api = 'http://sit-api/**';
break;
}
return api;
}
...
export default ({
nitro: {
routeRules: {
'/api/**': {
proxy: getEnv(process.env.NUXT_ENV_MODE)
}
}
},
...
});
规定构建命令传参方式("--env=环境变量"是我们的参数):
ini
//构建命令 终端执行
yarn build --env=环境变量
//例子------构建sit环境
yarn build --env=sit
这时候执行构建命令,便可以构建出相应的环境了
更多关于nuxt.config.ts的配置可参考:ezdoc.cn/docs/nuxtjs...
插件的安装并全局引入
ant-design-vue
除了使用yarn
或npm
等命令安装ant-design之外,需要在根目录创建一个plugins
文件夹(有就不用创建了)创建一个文件(这里用的是ts)antd.ts
javascript
// plugins/antd.ts
import Antd from 'ant-design-vue';
import 'ant-design-vue/dist/antd.less';
import { defineNuxtPlugin } from 'nuxt/app';
export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.use(Antd);
});
nuxt.config.ts配置
arduino
export default ({
plugins: [
'@/plugins/antd.ts',
'@/plugins/echarts.ts',
{ src: '@/plugins/pdf.ts', ssr: false, mode: client }
],
...
});
类似这样的依赖库需要在plugins文件夹下创建文件全局引入,并在nuxt.config.ts,但类似less、typescript等插件或扩展类的直接使用命令安装相应的版本就可以了。
API的解释
useAsyncData、useFetch、useLazyAsyncData和useLazyFetch的区别
Nuxt 3 提供了几种不同的函数来处理异步数据和 API 请求,包括 useAsyncData
, useFetch
, useLazyAsyncData
, 和 useLazyFetch
。这些函数都是为了在 Nuxt 应用中更方便地处理异步操作,但它们各自有不同的使用场景和行为。下面是每个函数的概述及其之间的主要区别:
1. useAsyncData
-
用途:用于在服务器端或客户端获取异步数据,并将数据响应式地绑定到组件上。这个函数特别适合用于获取初始渲染页面时需要的数据。
-
特点:它会在服务器端进行数据的获取(如果是首次页面加载的情况),之后的数据更新会在客户端进行。这有助于实现服务端渲染(SSR)和静态站点生成(SSG)。
-
场景:适合需要预先获取数据以便在页面加载时立即可用的场景。
2. useFetch
-
用途 :与
useAsyncData
类似,也是用于获取异步数据并响应式地绑定到组件。它提供了更多的配置选项,比如可以设置请求的方式(GET、POST 等)。 -
特点 :
useFetch
也支持 SSR 和客户端数据获取,但提供了更丰富的配置选项,例如可以设置请求头、方法等。 -
场景:当你需要更细粒度的控制请求配置时使用。
3. useLazyAsyncData
-
用途:这个函数用于懒加载异步数据,即数据的获取将推迟到明确需要时再进行。
-
特点:它不会在服务器端自动执行,数据的获取会延迟到客户端,并且通常是在用户进行某些操作(如点击按钮)时触发。
-
场景:适合那些不需要在页面初次加载时立即可用,或者对页面首屏加载性能有严格要求的场景。
4. useLazyFetch
-
用途 :与
useLazyAsyncData
类似,用于在客户端懒加载异步数据。它也提供了配置选项来控制请求的行为。 -
特点 :这个函数提供了类似于
useFetch
的配置能力,但数据获取的触发是懒惰的,需要在特定时机手动触发。 -
场景:当你需要在客户端懒加载数据,并且需要详细配置请求时使用。
总结
-
立即获取数据 :如果你需要在页面加载时立即获取数据,并可能涉及服务端渲染,使用
useAsyncData
或useFetch
。 -
懒加载数据 :如果数据不需要立即加载,或者你想在用户执行某些操作后才获取数据,以优化性能或用户体验,使用
useLazyAsyncData
或useLazyFetch
。 -
配置请求 :如果需要对 HTTP 请求进行详细配置,比如设置请求头或请求方法,倾向于使用
useFetch
或useLazyFetch
。
这些函数提供了灵活的数据获取方式,可以根据你的具体需求和应用场景选择最合适的方法。
useHydration
useHydration
是 Nuxt 3 中的一个组合式 API(Composition API),设计用来处理客户端渲染(Client-Side Rendering, CSR)和服务端渲染(Server-Side Rendering, SSR)之间的状态同步问题。在 SSR 应用中,首次页面加载时的 HTML 是在服务器上生成的,然后发送给客户端。客户端接着"激活"这些静态标记,使其变成动态的应用。这个激活过程通常被称为"hydration"。useHydration
用于在这个过程中,特别是在客户端上,控制和管理特定的状态或逻辑,以确保客户端和服务端的同步。
使用场景
-
条件渲染: 在服务端生成一部分标记,但你希望在客户端根据用户的交互或设备能力(例如,检测是否支持 JavaScript)来决定是否显示这部分内容。
-
客户端特有的操作: 某些操作只能或应该在客户端进行,如访问本地存储(localStorage)、使用
window
或navigator
对象等。
基本使用
基本上,useHydration
允许你检测组件是否正在进行 hydration 过程。这可以帮助你决定何时执行客户端特定的逻辑。
xml
<script setup>
import { useHydration } from '#app'
// 检测是否在进行客户端的 hydration 过程
const isHydrating = useHydration()
// 你可以基于 isHydrating 的值来决定是否执行某些操作
</script>
示例
假设你有一个组件,它在客户端上需要根据用户的交互来显示不同的内容,但这个交互不会影响到服务端渲染的结果。你可以利用 useHydration
来控制这部分逻辑的执行时机。
xml
<script setup>
import { useHydration, onMounted } from 'vue'
import { someClientSideOnlyFunction } from 'path/to/your/functions'
const isHydrating = useHydration()
onMounted(() => {
if (!isHydrating.value) {
// 仅在客户端且非 hydration 过程中执行
someClientSideOnlyFunction()
}
})
</script>
在这个示例中,someClientSideOnlyFunction
只会在客户端渲染完成后执行,避免了在服务端渲染过程中执行客户端特定的逻辑。
注意事项
-
使用
useHydration
需要对 SSR 和 CSR 有一定的理解,特别是它们在应用初始化时的行为差异。 -
适当使用
useHydration
可以提升应用性能和用户体验,尤其是在涉及到复杂客户端交互的场景中。 -
记得检测
isHydrating.value
的值来确保代码只在适当的时机执行,避免不必要的性能开销或潜在的错误。
通过合理利用 useHydration
,你可以更精细地控制应用在不同渲染环境下的行为,从而提高应用的灵活性和用户体验。
接口的封装
在nuxt中,我们可以使用useAsyncData或useFetch进行接口的调用,在这里我采用了二者的接口,使用useFetch进行接口的调用,useAsyncData对useFetch获取的数据进行处理。
1. useFetch的封装
typescript
import { type UseFetchOptions } from "nuxt/app";
export interface IResultData {
data?: any;
code?: number;
message?: string;
success?: boolean;
}
// 封装请求函数
async function request(
url: string,
option?: UseFetchOptions<IResultData>,
): Promise<Partial<IResultData>> {
if (process.client) {
await nextTick(); //解决刷新页面useFetch无返回
}
const { data } = await useFetch<IResultData>(url, {
onRequest({ options }) {
// 设置请求头
options.headers = {....};
return options;
},
// 请求拦截error
onRequestError({ response }) {
throw new Error(response);
},
// 响应拦截
onResponse({ response }) {
const { code, message } = response._data;
if (code !== SUCCESS_CODE) {
throw new Error(message);
}
return response._data;
},
...option
}
);
return data.value || {};
}
// 封装常用方法
export function get(url: string, params?: any, options?: UseFetchOptions<IResultData>) {
return request(url, { method: "GET", params, ...options });
}
export function post(url: string, params: any, options?: UseFetchOptions<IResultData>) {
return request(url, { method: "POST", body: params, ...options });
}
export function put(url: string, params: any, options?: UseFetchOptions<IResultData>) {
return request(url, { method: "PUT", body: params, ...options });
}
export function del(url: string, params: any, options?: UseFetchOptions<IResultData>) {
return request(url, { method: "DELETE", body: params, ...options });
}
2. useAsyncData的使用
javascript
useAsyncData('get-data', async () => {
const results = await Promise.allSettled([api1(), api1()]);
results?.forEach((result, index) => {
if (result.status === 'fulfilled') {
switch (index) {
case 0:
const data1 = result.value.data;
break;
case 1:
const data2 = result.value.data;
break;
}
} else {
console.error(`Request ${index} failed:`, result.reason);
}
});
});
当需要执行并行和独立的异步操作并收集所有结果时,Promise.allSettled()
就是不错的选择,即使一些异步操作可能失败。
如果不需要执行多个接口:
javascript
useAsyncData('get-data-2', async () => {
const res = await api()
console.log(res)
});
注意:
如果在同一个页面中使用useAsyncData执行多个相同的接口(但参数不一样)会导致最终的数据都会被最后调用的那个接口的数据覆盖。解决办法,不使用useAsyncData执行,采用常见的项目的接口调用方法。
路由生成
nuxt的项目会对在pages
目录下的文件自动生成路由,不需要自行配置,除components
文件夹外;components
文件夹下的文件不会被nuxt处理。
Nuxt.js
依据 pages
目录结构自动生成 vue-router
模块的路由配置。假设 pages
的目录结构如下:
diff
pages/
--| custompage/
-----| index.vue
--| index.vue
那么,Nuxt.js
自动生成的路由配置如下:
css
router: {
routes: [
{
name: 'index',
path: '/',
component: 'pages/index.vue'
},
{
name: 'custompage',
path: '/custompage',
component: 'pages/custompage/index.vue'
}
]
}
layout布局
在根目录下的layouts
文件夹下的存储的是页面的布局,命名为default
的文件会成为每一个页面级的默认布局。例子:
xml
<template>
<CHeader />
<div>
<slot></slot>
</div>
<CFooter />
</template>
<script lang="ts">
import CHeader from '@/components/common/CHeader.vue';
import CFooter from '@/components/common/CFooter.vue';
export default defineComponent({
name: 'layout',
components: { CHeader, CFooter},
});
</script>
在app.vue使用
xml
<template>
<div id="app">
<NuxtLayout>
<NuxtPage></NuxtPage>
</NuxtLayout>
</div>
</template>
如果某个页面不需要使用这个布局
xml
<script lang="ts">
export default defineComponent({
name: 'xxxx',
setup() {
//ui布局
definePageMeta({
layout: false,
});
},
});
</script>
使用其他布局则把layouts文件夹下的文件名赋值给definePageMeta的的layout属性就好:
xml
<script lang="ts">
export default defineComponent({
name: 'xxxx',
setup() {
//ui布局
definePageMeta({
layout: '其他布局的文件名',
});
},
});
</script>
服务端部署
nginx配置:
ini
server {
listen 80;
server_name localhost;
gzip_static on;
gzip_proxied expired no-cache no-store private auth;
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_http_version 1.0;
gzip_comp_level 9;
gzip_types text/plain application/javascript text/css application/xml;
gzip_vary on;
# 将客户端访问代理到node服务的3000端口
location / {
index index.html index.htm index.jsp;
proxy_pass http://web-official-website-node-node.dev-cicd.svc.cluster.local:3000/; #web-official-website-node-node nuxt内网地址
proxy_redirect off;
proxy_set_header Host $host; # 传递域名
proxy_set_header X-Real-IP $remote_addr; # 传递ip
proxy_set_header X-Scheme $scheme; # 传递协议
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /api/ {
index index.html index.htm index.jsp;
proxy_pass http://yunshang-portal-gateway-node.dev-cicd.svc.cluster.local:49010/; #yunshang-portal-gateway 网关集群内网连接
proxy_redirect off;
proxy_set_header Host $host; # 传递域名
proxy_set_header X-Real-IP $remote_addr; # 传递ip
proxy_set_header X-Scheme $scheme; # 传递协议
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
构建执行 "yarn build" 命令之后,将编译出的以下文件上传到服务器:
lua
ecosystem.config.cjs
nuxt.config.ts
package.json
.nuxt
.output
通过Docker镜像安装pm2启动nuxt服务,以下是Dockerfile配置:
bash
# node版本最好和开发环境的版本保持一致
FROM node:hydrogen-alpine3.19
# 容器内创建目录/nuxt3
RUN mkdir -p /nuxt3
# 设置工作目录
WORKDIR /nuxt3/
# 复制当前的内容到容器内容部目录/nuxt3
COPY ./web-official-website-node/ /nuxt3/
RUN cd /nuxt3/
# 设置文件权限
RUN chmod -R 777 *
# 禁用SSL检查
RUN npm config set strict-ssl false
# 安装pm2
RUN npm i pm2 -g --registry=http://registry.npm.taobao.org
# 容器对外暴露的端口号,要和node项目配置的端口号一致
EXPOSE 3000
# 运行
ENTRYPOINT ["pm2-runtime", "start", "ecosystem.config.cjs"]
编码常见
1. 标签
在服务端渲染的页面中,如果存在部分组件需要进行客户端渲染可使用该标签进行包裹,如:
xml
<ClientOnly>
<我是客户端渲染的组件/>
</ClientOnly>
2. 只能在客户端渲染代码
在Nuxt 3中,有些行为或代码只适用于客户端(浏览器环境),在服务端渲染(SSR)环境中执行时会报错。这通常是因为某些对象或功能在Node.js环境中不存在,但在浏览器中是可用的。以下是一些在服务端渲染中可能导致错误的常见情况:
-
访问浏览器特定的全局对象 :例如
window
、document
、localStorage
和sessionStorage
。这些对象只在浏览器环境中存在,在服务端渲染中使用它们会导致ReferenceError
。 -
直接操作DOM :任何直接操作DOM的代码,如
document.getElementById
或使用querySelector
,在服务端都会失败,因为在Node.js环境中没有DOM。 -
依赖于浏览器API的功能 :例如
FileReader
、WebSocket
、Canvas
API,这些只在浏览器中定义。 -
客户端特定的事件处理:比如监听滚动事件、点击事件等。这些在服务端没有相应的环境来触发它们。
-
使用浏览器导航和位置接口 :如使用
window.location
或history
API来处理URL和导航。
如果需要进行上述行为,我们可以采取以下措施
- 使用
process.client
和process.server
来判断当前代码是在客户端还是服务端执行,并据此调整行为。 - 将依赖于客户端API的代码放在Vue的生命周期钩子
mounted
中,因为mounted
只在客户端调用。 - 对于客户端专用的功能或组件,可以使用动态导入(dynamic import)并结合
ssr: false
来避免在服务端加载它们。 - 使用标签对使用上述代码的组件进行包裹。