🌻 一、背景
这段时间看到掘金有个关于性能优化的活动,刚好前段时间开发了两个官网。本来想参与一下,但是工作比较忙,我写文章又不愿意敷衍了事。所以本来打算不参加了的。不过这两天稍微空闲下来了,所以还是简单说说我开发官网时所做的一些优化工作。
本文只是分享下我做项目一般的优化流程,分享一下我个人的经验。至于具体优化后的指标到底性能提升了多少,当时并没有详细记录。
🪴 二、技术选型、项目架构
因为本文主体并不是教学nuxt3,所以很多操作我就不过多阐述了。
创建nuxt3基础项目模版后,需要先配置一些必要的插件和环境等。前期一定要把基础打好,虽然工作排期紧张,但是也不能贸然开始写业务代码。
封装请求、封装composables自定义API、配置store、配置环境变量、配置响应式方案等:
- 用$fetch封装请求,并在composables文件夹中注册为api,方便调用封装的请求方法;
- 因为考虑到官网很多数据都是各处通用的,所以状态管理几乎全权交给了pinia,页面中只管理一些处理交互逻辑的数据,我通过业务归类把store分为了以下四个模块,并且必要的模块设置了持久化方案。
- 配置环境变量:利用vite的
loadEnv()
方法加载.env
文件中配置的环境变量,并通过nuxt3的runtimeConfig
配置项设置为运行时变量,环境变量里配了例如接口地址、埋点上报地址、其他配置id等; - 响应式方案:我采用pc端用rem方案,移动端用vw
其他太多的封装我就不说了,不是本文的重点
🍭 三、所做的优化
对于一个项目开发来说,我一般的做法是,业务开发中的复杂度前置,项目整体优化后置。
- 为什么要复杂度前置? 就是先把复杂的,繁琐的工作在初期就全部搞好,在后期调用时就能事半功倍。比如封装请求,如果你只是简单封装个请求,那每次用都需要引入,如果在composables里注册为API,就可以直接用了,节省了很多后续的使用成本。
- 为什么要优化后置? 既然都叫优化了,肯定是在项目完成之后,采取一定手段,提高项目性能。过早关注优化不仅会耽误开发进度,而且还无法找到性能瓶颈,以至于做一些无用功,甚至是反向优化。
下面围绕开发周期来说说官网所做了哪些优化:
(一)开发中的“优化”
在搭建起完善的项目架构后,就开始着手业务开发了。在日常开发中,以下这些操作与其说是优化,其实应该说是养成良好的开发习惯,因为这些渗透到业务开发里的操作,只要你开发时有这些习惯,就不需要后期去优化什么,例如:
🍀 通用的代码习惯
-
图片资源使用更加高效的编码格式webp,一般设计图系统都会提供各种格式的图片,下载webp即可;
-
比较大的图片可以用TinyPNG压缩下,一般小图本身wepg格式的压缩算法已经压缩到比较小了,没有多少压缩空间;
-
图片都要设置宽高,防止页面塌陷造成回流,同时要设置alt,提高seo能力和无障碍能力;
-
网页中存在大量图片,可以设置懒加载,减少首次加载时间,例如本官网中存在ai bot列表,列表中的图片可以设置懒加载;
-
网站内部跳转尽量使用a标签跳转代替编程式导航,更有利于网站路由形成网状结构,有利于seo;
-
写动画尽量不要修改元素的left、right、top、bottom值,避免回流,多用transform可以开启GPU硬件加速;
-
及时清理定时器,避免内存泄漏;
-
其他对象的引用或临时对象使用完后最好主动设置为null,避免无意造成内存泄漏;
🍀 vue的代码习惯
-
要贯彻模块化开发的思想,通用的方法要抽离出来,合理封装公共组件;
-
拒绝过度封装,不能为了封装而封装,导致代码过于复杂,难以理解和维护。就像我之前接手过的一个项目,一个函数里面在深度上能套用五六个方法,理解起来极为困难,甚至为了把一串代码简化引用,都要封装一个函数,纯纯是欺骗自己的封装行为;
-
合理选用v-if和v-show,这些都是最基本的;
-
v-for循环必须绑定key,而且这个key必须是唯一且稳定的;
-
v-for和v-if不能设置到同一个标签上。如果你想v-for后执行v-if,那么可以用computed代替,如果你想v-if之后执行v-for,可以在v-for的标签外套一个template标签应用v-if;
(二)架构的优化
现在常用的前端编译工具vite、webpack其实在打包层面已经默认内置了很多优化手段,不需要我们手动配置。当然,你需要了解它们,或者在此基础上,适当的调整一些优化策略。
因为nuxt3使用的是vite,这里说下vite本身带来的一些优化:
- 小图片转base64:vite默认开启了图片小于4m编译为base64格式,这样可以减少额外的http请求,一般不需要调整这个数值;
- css代码拆分:vite 默认配置了
cssCodeSplit
开启css代码拆分,这样异步组件的css可以异步加载; - Tree Shaking:Vite 生产构建时默认开启 Tree Shaking,可以排除未引用的代码,减少最终的包大小;
- 预构建依赖:Vite 会在开发服务器首次启动时自动进行依赖预构建,主要是为了防止使用es module加载依赖时,嵌套依赖过多,发送太多请求,所以预编译可以把一些第三方库整合到一起,另外,预编译时还会处理一些兼容性问题;
- 使用缓存: Vite 默认会使用文件系统缓存来加速重复构建;
(三)开发后的优化
官网开发完成后,分析一下还有哪些可以优化的点。
🍀 基础操作
项目开发完成后,该删的东西删一下。例如:
- 删除一些不必要的注释;
- 关闭正式环境sourcemap,把 vite 构建配置里的
build.sourcemap
设置为false,sourcemap不仅不安全而且还会增大打包体积; - 去除正式环境的
console.log
打印信息,利用vite内置的terser
压缩插件可以关闭。console.log到底会不会导致内存泄漏还存在争议,管它呢,关了什么事没有; console.log 一定会导致内存泄漏?不打开 devtools 就不会
build: {
sourcemap: false, // 关闭sourcemap
terserOptions: {
compress: {
drop_console: true, // 去除console.log
},
},
}
🍀 网络优化
-
检查是否开启了HTTP/2;
-
配置CDN,本项目开启了CDN回源策略,自动将资源同步到CDN上;
-
开启gzip压缩,使用
vite-plugin-compression
插件在打包时生成压缩文件,同时还可以开启br
压缩,br
压缩力度更大,但是存在兼容性问题,而且开启后就得同时存储两种压缩格式,具体应不应用看具体项目了; -
检查是否配置了静态资源缓存,因为之前我已经让运维配置了强缓存策略,所以是没有问题的,关于浏览器缓存的实践,可以看我之前的文章:浅谈 强制缓存/协商缓存 怎么用?
🍀 首屏加载优化
基本的东西搞好后,就要排查下问题了,先看看network网络日志的瀑布流,看看有没有大文件阻塞加载,以及安装一个rollup-plugin-visualizer
插件,分析下budle sizes,合理配置下分包策略。
分包是比较合理的,只是有一些没有用到的插件也被打包进去了,然后删了一下。 另外我发现引入的第三方sdk都被优先下载了,因为在nuxt.config.ts的head配置项里引入,默认会被添加到head标签里,所以需要做一下优化:
- 官网接入了facebook、google、appleid登录,需要引入它们的sdk,nuxt3引入外部js是在
nuxt.config.ts
的app.head.script
里配置,为了防止引入这些sdk时阻塞页面渲染,应该把它们放到 body 标签底部引入,还可以加上defer
属性,延迟执行,配置如下:
app: {
head: {
link: [],
script: [
{
src: 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js',
defer: true,
tagPosition: 'bodyClose'
}
]
...
随后在devtools的内存和性能面板排查一下内存泄漏问题,对症下药。这块儿本身我项目里没看出来哪来,倒是发现nuxt3框架有些泄漏问题。
然后用lighthouse测一下网站性能,根据上面的建议适当的进行优化;
之后就是做一些细节的调整
-
利用nuxt3的水合特性,把一些后置组件都改成client仅客户端的组件,页面中的一些大图片什么的也用
clint-only
标签包裹,可以大大提高显示速度,不然服务端也请求一次,太慢了; -
因为本官网有pc和移动端自适应的两套UI,所以我把移动端的样式抽离出来,通过
@import
引入,这样项目结构更加清晰。如果你使用原生的css写样式,最好是少用@import,因为使用@import引入的文件需要在加载和解析主CSS文件后才会加载,存在性能问题,但我使用的是scss
,在编译时,会把@import
引入的代码合并到主 CSS 文件中,所以没有这个问题; -
对于比较大的图片,可以用
srcset
和sizes
配置下响应式,根据屏幕大小和分辨率,显示适合倍数的图片; -
这些第三方域名,还有其他的一些域名可以预解析一下,提高后续加载速度;
link: [
{ rel: 'dns-prefetch', href: 'https://play.google.com' }
...
- 一些组件中或者其他页面中的资源可以在mounted后预加载下;
- 官网首页有个大的banner图,为了减少LCP(屏幕内最大内容显示时间),可以用
preload
link标签预加载一下; - 另外,利用新特性
fetchpriority="high"
,给banner图的img标签加上这个属性,可以提高该静态资源的加载优先级;
🍀 seo优化
选择nuxt3最主要的原因就是利于首屏加载以及更好的seo。
SEO是个长期的过程,不是说代码层面做好就可以的,但是咱也得把基础搞好。关于seo,可以看我的另一篇文章,⛳前端进阶:SEO 全方位解决方案
-
正如前面开发中的优化所说,在开发时就要注意html语义化,还要注意img标签的alt属性一定要写上;
-
然后首先就是tdk信息了,所谓tdk,就是title、describe、keyword这几个元标签信息,不仅要在
nuxt.config.ts
中统一配置网站的tdk信息,在一些重要页面最好用useSeoMeta
API单独设置其tdk; -
除了普通的tdk信息外,可以额外设置一些open graph协议标签,这样网站在被分享到社交媒体时,就可以控制分享的卡片信息了;
- 设置网页语言,这些设置很特别,也在
app.head
里设置,因为按常理来讲,head就应该设置head标签里的内容;
head: {
htmlAttrs: {
lang: 'en'
}
- 站内跳转的外链,或者不重要链接,都要加上
rel="nofollow external"
属性,阻断搜索引擎蜘蛛抓取; - 设置
canonical url
规范化网址,统一网址,提高地址权重; - 网站根目录配置
robots.txt
文件,指定抓取范围,因为目前官网还没对外开放,所以 配置了禁止抓取任何路径;
// robots.txt
User-agent: *
Disallow:
- 因为本网站是ai角色对话网站,每个机器人都需要有独立的seo信息,就比如csdn或者掘金的文章,为什么都可以在搜索引擎中单独搜到,就是因为每篇文章都有单独的动态seo配置。
思路是使用useAsyncData
API在服务端获取到单个机器人的信息后,使用useSeoMeta
设置其tdk信息。simtmap站点地图利用@nuxtjs/sitemap
模块动态生成。然后在server/api
文件夹下新建sitemap.js
文件,接口获取到机器人列表,然后push到sitemap里。
🎁 四、最后
其实很多东西成体系后自己需要动手做的优化很少了。所以不需要刻意的做优化,而是对症下药,知道如何评测性能指数,定位性能瓶颈,这一点我认为其实比优化更加困难✊✊✊
学如逆水行舟,不进则退~👊👊👊
先看后赞,养成习惯👍
收藏吃灰,不如学会🍗
点个关注,不要迷路🪤