作为Nuxt 的初学者,在首次使用 Nuxt 框架进行项目开发时,遇到了很多问题,记录一下:
Nuxt.js 是什么
Nuxt.js 是一个基于 Vue.js 的通用应用框架,旨在简化Vue.js 应用的开发,特别是在服务端渲染(SSR)、静态站点生成等方面提供了便捷的配置和工具。
在学习 Nuxt 之前需要掌握以下前置知识:
- Vue.js
- HTML、CSS、JavaScript
- 前端构建工具(Webpack、babel 等)
- 服务端知识(如 Node.js、Express 等)
- Git 版本控制工具
如何搭建一个 Nuxt.js 项目
- 创建一个新的入门项目
npx nuxi@latest init <project-name>
- 使用 Visual Atudio Code 打开项目,并安装依赖:
code <project-name> yarn
- 启动项目
yarn dev --open
Nuxt3 常用组件使用
NuxtPage
使用 <NuxtPage>
组件,才能显示位于 pages/
目录中的页面。
NuxtImg
<img />
标签改用 <NuxtImg />
。
- 使用内置提供程序优化本地和远程映像
- 将
src
转换为提供程序优化的 URL - 根据
width
和height
自动调整图片大小 - 提供
sizes
选项时生成响应式尺寸 - 支持本机延迟加载以及其他
<img>
属性
NuxtLink
自定义配置
nuxt3 自定义路由
在 nuxt3 中,路由是由页面文件中的目录结构定义的。因为底层使用的 Vue-router,所以可以自定义路由。
app 目录下新建 router.option.ts
文件,自定义正则表达式来匹配动态路径
const reRouter = [
{
path: '/product/:modelTypeNo([a-z0-9]+)/:id(\\d+)/:param(canshu|tupian|zixun|dealer)?/',
component: () => import('../pages/productDetail/[id].vue'),
},
];
export default {
routes: () => [...reRouter],
};
环境配置
.env
,默认;
.env.development
,本地开发环境;
.env.test
,测试环境;
.env.production
,生产环境
BASE_URL = xxx # 接口域名
URL = xxx #前端域名
ENV = 'development' ## 环境名称
nuxt.config.ts
文件中进行配置:
export default defineNuxtConfig({
runtimeConfig: {
public: {
env: process.env.ENV,
},
},
});
通过 useRuntimeConfig()
,获取 nuxt.config.ts
中配置的 runtimeConfig
const config = useRuntimeConfig();
console.log(config.public.env);
package.json
文件中配置,不同环境执行不同的命令:
"scripts": {
"dev": "nuxt dev",
"dev:test": "nuxt dev --dotenv .env.test",
"dev:prod": "nuxt dev --dotenv .env.production",
"build": "nuxt build",
"build:test": "nuxt build --dotenv .env.test",
"build:prod": "nuxt build --dotenv .env.production",
},
assetsInlineLimit
图片转 base64 编码的阈值(默认是 4096字节)。为防止过多的 http 请求,nuxt 会将小于此阈值的图片转为 base64 格式,可根据实际需求进行调整。
// nuxt.confug.ts
export default defineNuxtConfig({
vite: {
build: {
assetsInlineLimit: 5,
},
},
})
Nuxt3 使用 Unocss
unocss 默认 1rem = 4px,也就是说如果你设置了 m-20(20的单位是像素),那么实际的效果就是5rem,这样的效果是不对的,所以,我们需要改一下 Unocss 的配置
// 1. 安装
yarn add @unocss/preset-rem-to-px -D
yarn add @unocss/preset-uno -D
// 2. 配置
// uno.config.ts
import { defineConfig } from 'unocss';
import presetRemToPx from '@unocss/preset-rem-to-px';
import presetUno from '@unocss/preset-uno';
export default defineConfig({
// ...UnoCSS options
presets: [
presetUno(),
presetRemToPx({
baseFontSize: 4, // 设置为4,实现 w-1=1px
}),
],
});
// 3. 结合 px-to-vw,实现页面自适应
yarn add postcss-px-to-viewport-8-plugin -D
// nuxt.config.ts
export default defineNuxtConfig({
postcss: {
plugins: {
'postcss-px-to-viewport-8-plugin': {
unitToConvert: 'px', // 需要转换的单位,默认为"px"
viewportWidth: 375, // 设计稿的视口宽度
unitPrecision: 5, // 单位转换后保留的精度
propList: ['*', '!font-size'], // 能转化为vw的属性列表,!font-size表示font-size后面的单位不会被转换
viewportUnit: 'vw', // 希望使用的视口单位
fontViewportUnit: 'vw', // 字体使用的视口单位
// 需要忽略的CSS选择器,不会转为视口单位,使用原有的px等单位。
// 下面配置表示类名中含有'keep-px'都不会被转换
selectorBlackList: ['keep-px'],
minPixelValue: 1, // 设置最小的转换数值,如果为1的话,只有大于1的值会被转换
mediaQuery: false, // 媒体查询里的单位是否需要转换单位
replace: true, // 是否直接更换属性值,而不添加备用属性
exclude: [/node_modules/], // 忽略某些文件夹下的文件或特定文件,例如 'node_modules' 下的文件
include: [/src/], // 如果设置了include,那将只有匹配到的文件才会被转换
landscape: false, // 是否添加根据 landscapeWidth 生成的媒体查询条件 @media (orientation: landscape)
landscapeUnit: 'vw', // 横屏时使用的单位
landscapeWidth: 1338, // 横屏时使用的视口宽度
},
},
},
});
自定义 unocss class
// nuxt.config.js
export default defineNuxtConfig({
unocss: {
nuxtLayers: true,
rules: [
[
'text-ellipsis',
{ 'text-overflow': 'ellipsis', overflow: 'hidden', 'white-space': 'nowrap' },
],
[
'text-2-ellipsis',
{
overflow: 'hidden',
' word-break': 'break-all',
'text-overflow': 'ellipsis',
display: '-webkit-box',
'-webkit-line-clamp': '2',
'-webkit-box-orient': 'vertical',
},
],
],
},
});
指令转换器
一般我们是将 unocss 名定义在 class 属性中,但是可能会导致 class 属性很长,这时候我们就可以用到指令转换器。
<div class="fixed w-375 box-border top-0 z-999 px-25 py-10 flex flex-items-center justify-center bg-#fff h-46"></div>
UnoCSS 的指令转换器,用于 @apply
、@screen
和 theme()
指令:@unocss/transformer-directives
。
- 安装:
yarn add -D @unocss/transformer-directives
- 设置:
// uno.config.ts
import { defineConfig } from 'unocss'
import transformerDirectives from '@unocss/transformer-directives'
export default defineConfig({
// ...
transformers: [
transformerDirectives(),
],
})
- 使用:
.custom-div {
@apply text-center my-0 font-medium;
}
// 被转换为:
.custom-div {
margin-top: 0rem;
margin-bottom: 0rem;
text-align: center;
font-weight: 500;
}
图片预设
Unocss 可以为任何图标提供纯 css 解决方案,即可以将 svg 图片转为 class。
可以使用 FileSystemIconLoader
方法从文件系统加载自定义图标。
- 配置
// uno.config.ts
import { defineConfig, presetIcons } from 'unocss';
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders';
export default defineConfig({
presets: [
presetIcons({
collections: {
//把自己的svg文件转换为class,这里的my-icon名称随便取,使用的时候通过i-custom:[filename]。
custom: FileSystemIconLoader('./src/assets/imgs'),
},
}),
],
});
- 使用
<div class="i-custom:[imgs中的文件名(不需要加后缀)]"></div>
ssr
项目路径中不能有中文,否则会报错“500 Internal Server Error”。
获取接口数据时,定义的接口方法不需要放在生命周期中执行,直接放在 <script>
里执行
如果页面渲染有问题,可以先去看是不是html标签使用不当。
title 标签中的 / 在源代码中被转码成 /
原因:Nuxt3 默认会对特殊字符进行 HTML 实体编码,以防止 XSS 攻击
解决方法: 使用服务端中间件(在 /server/middleware/
目录下)
// fixTitle.ts
export default defineEventHandler((event) => {
// 获取原始的 end 方法
const originalEnd = event.node.res.end;
// 重写 end 方法
event.node.res.end = function (chunk, ...args) {
if (typeof chunk === 'string') {
// 处理 HTML 实体编码
chunk = chunk.replace(/<title[^>]*>(.*?)<\/title>/g, (match) => {
return match
.replace(///g, '/') // 处理斜杠
.replace(/&/g, '&') // 处理 &
.replace(/\/g, '\\') // 处理反斜杠
.replace(/%/g, '%'); // 处理百分号
});
}
// 调用原始的 end 方法
return originalEnd.call(this, chunk, ...args);
};
});
设置 TDK
Nuxt 提供了三种方式来设置 TDK:
nuxt.config.ts
文件中全局设置
export default defineNuxtConfig({
app: {
head: {
title: '页面标题',
meta: [
{
name: 'description',
content: '页面描述',
},
{
name: 'keywords',
content: '关键字1, 关键字2',
},
{
property: 'og:title',
content: '分享出去的页面标题',
},
],
}
}
})
- 使用
useHead
useHead({
title: '页面标题',
meta: [
{
name: 'description',
content: '页面描述',
},
{
name: 'keywords',
content: '关键字1, 关键字2',
},
{
name: 'og:title',
content: '分享出去的页面标题',
},
],
});
- 使用
useSeoMeta
:这种方式可以帮助开发者避免name
和property
的使用错误。
useSeoMeta({
title: '页面标题',
description: '页面描述',
keywords: '关键字1, 关键字2',
ogTitle: '分享出去的页面标题',
});
将 TDK 源码放在一起
我们在设置完 tdk 之后,他们的源码可能不在一起
如果SEO有要求的话,我们可能就需要手动的调整源码,同样,我们可以创建一个服务端插件,来挪动 TDK 在源码中的位置:
// server/middleware/fixTDK.ts
export default defineEventHandler((event) => {
// 获取原始的 end 方法
const originalEnd = event.node.res.end;
// 重写 end 方法
event.node.res.end = function (chunk, ...args) {
if (typeof chunk === 'string') {
const tdkTags: string[] = [];
const otherTags: string[] = [];
const arr = chunk.split('\n');
arr.forEach((line) => {
if (
line.includes('<title>') ||
line.includes('<meta name="description"') ||
line.includes('<meta name="keywords"')
) {
tdkTags.push(line);
} else {
otherTags.push(line);
}
});
chunk = [...otherTags.slice(0, 4), ...tdkTags, ...otherTags.slice(4)].join('\n');
}
// 调用原始的 end 方法
return originalEnd.call(this, chunk, ...args);
};
});
性能优化
页面缓存(lru-cache)
- 安装 lru-cache
yarn add lru-cache
- 在
server/middleware
目录下新建page-cache
文件做页面缓存中间件
Nuxt 将自动读取~/server/middleware
目录下的任何文件,为项目创建服务器中间件。import { LRUCache } from 'lru-cache'; import { fromNodeMiddleware } from 'h3'; // 合理配置 const options = { max: 10000, ttl: 1000 * 60 * 60, }; // 统一从路由配置文件读取需要cache的路由 const CACHE_URL = ['/product/', '/video/']; const isPageOne = (pathname: string) => { return CACHE_URL.includes(pathname); }; const cache = new LRUCache(options); export default fromNodeMiddleware((req, res, next) => { const pathname = req.originalUrl; if (isPageOne(pathname)) { const existsHtml = cache.get(pathname); if (existsHtml) { console.log('page cache hint---------------', pathname); // 不要忘了设置 Content-Type 不然浏览器有时候可能不会渲染而是触发下载文件 res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); return res.end(existsHtml.html, 'utf-8'); } else { res.original_end = res.end; res.end = function (data) { if (res.statusCode === 200) { cache.set(pathname, { html: data }); } res.original_end(data, 'utf-8'); }; } } next(); });
KeppAlive
KeppAlive 用来缓存动态切换的路由组件
// app.vue
<NuxtPage :keepalive="{ max: 2, exclude: 'pageA', include: 'pageB' }" />
安装百度统计
通过百度统计工具来收集和分析网站的访问数据,从而更好的了解用户行为、优化网站内容和提高用户体验.
// nuxt.config.ts
export default defineNuxtConfig({
app: {
head: {
script: [
{
type: 'text/javascript',
innerHTML: `var _hmt = _hmt || [];
(function () {
var hm = document.createElement('script');
hm.src = 'https://hm.baidu.com/hm.js?your_baidu_analytics_id';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(hm, s);
})();
`,
},
]
}
}
})
页面切换时触发百度统计:
// app.vue
const route = useRoute();
onMounted(() => {
// 触发百度统计的页面追踪
const trackPageview = () => {
if (window._hmt) {
window._hmt.push(['_trackPageview', route.fullPath]);
}
};
// 初次加载时调用一次
trackPageview();
// 监听路由变化
watch(
() => route.fullPath,
() => {
trackPageview();
},
);
});
接入阿里云监控服务
- 安装
@arms/rum-browser
- 在
plugins
文件夹下创建文件,注意该文件只在客户端运行,所以以.client
格式命名。
// plugins/rum-browser.client.js
import ArmsRum from '@arms/rum-browser';
export default defineNuxtPlugin((nuxtApp) => {
if (useRuntimeConfig().public.env === 'development') {
return;
}
ArmsRum.init({
pid: xxx,
endpoint: 'https://f0mbzrjjy9-default-cn.rum.aliyuncs.com',
});
nuxtApp.hook('app:mounted', () => {
console.log('App has been mounted!');
});
});
持续更新⏳