我的 Nuxt、Storyblok 和 Netlify 网站性能优化之旅
在这篇文章中,我将向你展示我在构建网站时遇到的主要网站性能问题,以及 Jamstack 架构如何帮助我们解决这些问题。
为了构建我的网站,我使用了以下技术:Nuxt(我的静态网站生成器)、Storyblok(我的无头CMS,带有图像服务提供商)和Netlify(用于托管我的完整静态网站)。
我使用 Lighthouse 作为工具,它将向我们展示所有可以改进或修复项目性能的机会。
资源处理
1. 预加载密钥请求
我们将始终考虑使用link rel=preload来优先获取当前在页面加载后期请求的资源。
解决方案(使用 rel preload ) → 预加载关键资源以提高加载速度。
在 HTML 中声明预加载链接,以指示浏览器尽快下载关键资源。
<head>
<link rel="preload" href="critical.css" as="style">
<link rel="preload" href="critical.js" as="script">
</head>
我使用的工具→ 由于我使用Nuxt作为我的静态网站生成器,它已经通过这项技术提升了我的性能,请查看“速度极快的静态应用程序”以了解更多它是如何为我们做到这一点的。
2. 预先连接到所需的源
考虑添加preconnect或dns-prefetch资源提示,以便尽早与重要的第三方源建立连接。
解决方案(使用 rel preconnect ) → 向浏览器表明您的意图非常简单,只需在页面中添加一个 link preconnect标签即可:
<link rel="preconnect" href="https://example.com">
这样浏览器就知道该页面打算连接到 example.com 并从那里检索内容。
一般来说,最好使用 link rel="preload",因为它能更全面地优化性能,但我们可以保留 link rel="preconnect" 以应对特殊情况,例如:
链接dns-prefetch 是另一种与连接相关的类型。它仅处理 DNS 查询,但浏览器支持范围更广,因此可以作为不错的备选方案。它的使用方法完全相同:
<link rel="dns-prefetch" href="https://example.com">.
我使用的工具→ 一个很好的例子是链接到 Google Fonts,就我而言,由于我的项目本身就包含字体文件,所以我不需要考虑这一点。
但 Nuxt 已经考虑到了这一点,他们创建了一个模块来提高字体加载性能:@nuxtjs/google-fonts。
3. 使用外观模式延迟加载第三方资源
某些第三方嵌入代码可以延迟加载。建议在需要时再使用它们,并考虑使用外观模式(facial)代替。
解决方案(第三方外观) → 不要直接在 HTML 中添加第三方嵌入代码,而是加载一个与嵌入的第三方代码外观相似的静态元素。交互模式应如下所示:
- 页面加载时:向页面添加外观(作为视频封面)。
- 鼠标悬停时:外观预先连接到第三方资源。
- 点击后:外观将替换为第三方产品。
我使用的工具→ 对于YouTube 视频,我开始使用lite-youtube-embed包,这是听从了Debbie O'brien和web.dev的建议!
页面加载时间的差异非常大,更不用说最初你并没有很多 iframe 来延长交互时间。
4. 减少第三方代码的影响/尽量减少第三方使用
第三方代码可能会显著影响页面加载性能。请限制冗余的第三方提供商数量,并尽量在页面主体加载完成后再加载第三方代码。
解决方案(加载第三方 JavaScript ) → 如果第三方脚本导致页面加载速度变慢,您可以尝试以下几种方法来提升性能:
- 使用async或defer属性加载脚本,以避免阻塞文档解析。
- 如果第三方服务器速度慢,则自行托管脚本。
- 如果该脚本对您的网站没有明显的价值,请考虑将其移除。
- 资源提示 ,例如 link rel=preconnect 或 link rel=dns-prefetch ,用于对托管第三方脚本的域执行 DNS 查询。
我使用的工具→ 我使用的是第三方分析工具 Google Analytics,但一个名为vue-gtag的软件包可以帮助我仅在用户同意后加载分析数据,并且一旦激活,它就会预先连接到 googletagmanager 并异步加载分析数据:
<link href="https://www.googletagmanager.com" rel="preconnect">
<script type="text/javascript" async src="https://www.google-analytics.com/analytics.js"></script>
由于我使用的是 Storyblok 图片服务提供商,所以我已经预先连接好了,这样可以加快图片加载速度:
<link rel="preconnect" href="//img2.storyblok.com">
// Nuxt config
head: {
link: [
{ rel: 'preconnect', href: '//img2.storyblok.com' },
],
}
5. 消除阻塞渲染的资源
资源占用导致页面首次渲染失败。请考虑将关键的 JS/CSS 内联加载,并将所有非关键的 JS/样式延迟加载。
您可以通过仅上传所需的代码和样式来减小页面大小。点击 URL 即可在“源代码”面板中查看该文件。CSS 文件中的样式和 JavaScript 文件中的代码分别以两种颜色标记:
- 绿色(关键): 首次绘制所需的样式;对页面核心功能至关重要的代码。
- 红色(非关键): 应用于不立即可见的内容的样式;未在页面核心功能中使用的代码。
解决方案(渲染阻塞资源) → 让我们深入了解如何消除阻塞页面加载的脚本或样式表。
-
如何消除阻塞渲染的脚本
一旦你确定了关键代码,就将该代码从阻塞渲染的 URL 移动到 HTML 页面中的内联脚本标签。
如果渲染阻塞 URL 中包含非关键代码,则可以将其保留在 URL 中,然后使用 async 或 defer 属性标记该 URL。
完全未使用的代码应该删除。
-
如何消除阻塞渲染的样式表
在 HTML 页面头部样式块中,加载首次绘制所需的关键内联样式。然后使用预加载链接异步加载其余样式。
考虑使用Critical 工具自动提取和内联“首屏”CSS 的过程。
我使用的插件→ 在 Netlify 中,我们有一个名为netlify-plugin-inline-critical-css的用于关键 CSS 的插件。
另一个很棒的技巧是将样式表拆分成不同的文件,并按媒体查询进行组织。然后为每个样式表链接添加媒体属性。加载页面时,浏览器只会阻塞首次渲染,以便检索与用户设备匹配的样式表。
保持 CSS/JS 文件较小
1. 压缩 CSS/JavaScript
代码压缩是指删除空格和任何不必要的代码,从而创建一个更小但完全有效的代码文件的过程。
压缩 CSS 文件→ 减少网络负载大小(了解更多关于压缩 CSS 的信息)
CSS 解决方案→ 使用 webpack 等工具进行压缩https://web.dev/minify-css/#css-minification-with-webpack。
压缩 JavaScript 文件→ 减少有效载荷大小和脚本解析时间(阅读更多关于压缩 JS 的信息)
解决方案 JS → 新的 uglify-js:https://github.com/terser/terser或继续使用webpack,Teser已包含在 prod.js 文件中。
我使用的版本→ Nuxt 的构建配置中已经使用了Terser webpack 插件,它会自动处理压缩问题。
2. 删除未使用的 CSS/JavaScript
从样式表中删除无效规则,并延迟加载未用于首屏内容的CSS ,以减少网络活动消耗的不必要字节。
解决方案(未使用的 CSS 规则) → 根据渲染阻塞样式表部分考虑关键/非关键 CSS 技术,但结合使用删除页面中未使用的 CSS 的工具,例如著名的PurgeCSS。
移除未使用的 JavaScript 代码,以减少网络活动消耗的字节数。(未使用的 JS)
解决方案(详细信息和工具对比) → 让我们看看如果我们的框架无法满足我们的需求,我们可以使用哪些技术:
记录代码覆盖率,以便开始分析特定文件中未使用的代码:
DEV Tools 中的覆盖率选项卡:
- 单击 “开始检测代码覆盖率并重新加载页面” 以查看加载页面所需的代码。
- 点击 “代码覆盖率” 即可查看与页面交互后使用的代码。
用于支持删除未使用代码的构建工具
Webpack 通过以下技巧使避免或删除未使用的代码变得更加容易:
-
代码拆分- 将公共依赖项提取到共享包中。
将捆绑代码拆分成多个较小的包,以便根据需要独立加载和执行。
Nuxt 会使用 webpack 来处理应用程序的代码分割!
-
未使用代码消除-死代码消除是指删除当前应用程序未使用的代码的过程。
有很多工具可供使用,其中最受欢迎的是Terser和Closure Compiler。Webpack的死代码消除是通过移除未使用的模块导出来实现的,然后依赖于Terser。
-
未使用的导入代码-模块导出功能的使用方式难以进行静态分析的棘手优化案例。
动态导入就是其中一个例子。Webpack无法理解用于排除无用代码的特殊解构语法:
const { transformImage } = await import('./image.utils.js');
但它允许通过 魔法注释手动列出所使用的导出项:
const { transformImage } = await import(/* webpackExports: "transformImage" */ './image.utils.js');
我用的Nuxt 已经帮我实现了这个功能,它底层使用了 webpack。它会按页面拆分我的代码,所以我不用再为每个使用动态导入的路由添加 webpackChunkName 注释了。
3. 启用文本压缩
应该使用压缩(gzip、deflate 或 brotli)来提供基于文本的资源,以最大限度地减少网络总字节数。
解决方案(使用文本压缩) → 在您的服务器上启用文本压缩。
当浏览器请求资源时,它会使用 Accept-Encoding HTTP 请求标头来指示它支持哪些压缩算法。
Accept-Encoding: gzip, compress, br
如果浏览器支持 Brotli ( br ),则应使用 Brotli,因为它比其他压缩算法更能减小资源的文件大小。
我使用的设备→ 我的主机Netlify默认已经使用 Brotli 压缩。
Brotli 压缩已获得浏览器的广泛支持,并且对 HTML、JavaScript 和 CSS 资源等基于文本的文件尤其有效。
Netlify Edge 已经使用 Brotli 对合适的资源进行编码和缓存,并根据请求的浏览器提供正确的压缩资源。
根据 Akamai 的测试,Brotli 和 gzip 的中位数比较如下:
- 使用 Brotli 压缩的 JavaScript 文件 比使用 gzip 压缩的文件小 14% 。
- HTML 文件 比 gzip 压缩后的文件小 21% 。
- CSS 文件 比 gzip 压缩后的文件小 17% 。
4. 删除 JavaScript 包中的重复模块
从软件包中删除大型、重复的 JavaScript 模块,以减少网络活动消耗的不必要字节数。
解决方案→ 使用 webpack,你可以使用https://www.npmjs.com/package/webpack-bundle-analyzer来检查 JS 包并开始清理你的项目。
我使用的方法→ 在 Nuxt 中,我已经有了那个包,我只需要在我的构建命令中添加--analyze标志,瞧!
缩短执行时间
1. JavaScript 执行时间
考虑减少解析、编译和执行 JavaScript 所花费的时间。您可能会发现,提供更小的 JavaScript 有效负载有助于解决这个问题。
解决方案(启动时间) → 代码分割、精简和压缩、删除未使用的代码以及缓存技术的结合将大大提高执行时间。
我使用的工具→ Nuxt 一如既往地领先一步,你可以在这段视频中亲眼看到他们使用的技术:https://www.youtube.com/watch?v= J6airiY8e84
2. 最大程度减少主线程工作
考虑减少解析、编译和执行 JavaScript 所花费的时间。您可能会发现,提供更小的 JavaScript 有效负载有助于解决这个问题。
解决方案(主线程工作分解)→ 最后,它是我们在本文中已经看到或以后将要看到的许多内容的汇编。
总而言之,我们的目标是优化JS和CSS代码,尽可能减少代码量,移除未使用的代码以及我们使用的第三方库。始终优先加载当前页面所需的关键CSS和JS ,其余部分则延后加载。
3. 用户计时标记和测量(一个很棒的工具,但不是问题)
考虑使用用户计时 API 为您的应用添加性能指标,以衡量应用在关键用户体验阶段的实际性能。了解更多关于用户计时的信息。
4. 服务器初始响应时间较短
务必保持主文档的服务器响应时间短,因为所有其他请求都依赖于它。
解决方案(首字节时间)→ 选择主机时必须考虑到这一点,如果是静态主机,一切都已正确配置,CDN 将具有许多优势。
我使用的工具→ 就我而言,Netlify 的响应时间为 33 毫秒。您可以使用这个速度测试工具查看我的测试结果,并用您的网站进行测试:testmysite.io/dawntraoz.com
DOM 问题
1. 避免大幅调整布局
这些 DOM 元素对页面的 CLS 贡献最大。
累积布局偏移 (CLS) 是 Core Web Vitals 的一项指标,它是通过对所有非用户交互引起的布局偏移进行求和计算得出的。
我使用的工具→ 这个网站https://webvitals.dev/cls可以提供有关您的网站 CLS 性能的详细信息。
2. 避免 DOM 占用过多空间
过大的 DOM 会增加内存使用量,导致样式计算时间延长,并产生代价高昂的布局重排。
解决方案 (DOM 大小) → 总的来说,要寻找仅在需要时才创建 DOM 节点的方法,并在不再需要时销毁节点。
我们可以使用Nuxt 中的组件懒加载功能。
但保持 HTML 代码简洁或采用滚动加载方式也可能有所帮助。
图片,我们更大的烦恼
1. 正确调整图片尺寸
提供大小合适的图片,以节省移动数据流量并提高加载速度。
解决方案(使用响应式图片) → 让我们来看看谷歌推荐的不同方法:
- Srcset:提供合适尺寸图片的主要策略称为“响应式图片”。使用响应式图片,您可以为每张图片生成多个版本,然后通过媒体查询、视口尺寸等方式在 HTML 或 CSS 中指定要使用的版本。
<img src="flower-large.jpg" srcset="flower-small.jpg 480w, flower-large.jpg 1080w" sizes="50vw">
-
图片 CDN:是另一种提供合适尺寸图片的主要策略。您可以将图片 CDN 理解为用于转换图片的 Web 服务 API。
我使用的工具→ 我使用的是Storyblok中提供的工具:storyblok 图片服务,并始终请求正确的尺寸。
-
SVG:另一种策略是使用基于矢量的图像格式。只需少量代码,SVG 图像即可缩放至任意大小。请参阅“ 使用 SVG 替换复杂图标” 了解更多信息。
2. 延迟显示屏幕外图像
考虑在所有关键资源加载完毕后延迟加载屏幕外和隐藏的图像,以减少交互时间。
解决方案(屏幕外图像) → 延迟加载图像。您可以按照 MDN 建议将loading属性设置为 lazy:延迟加载。
我使用的工具→ 我使用的是 Vue Lazyload 来懒加载图片和背景图片:https://github.com/hilongjw/vue-lazyload#demo
3. 高效地对图像进行编码
优化后的图片加载速度更快,消耗的移动数据流量更少。
解决方案(使用优化后的图片) → 如果您使用了本文中介绍的所有不同技术,则此问题应该可以解决。使用图片 CDN 服务或对图片进行压缩应该就足够了。
如果您不使用任何图像 CDN,可以使用这个在线工具:https://squoosh.app/
4. 以下一代格式提供图像
JPEG 2000、JPEG XR 和 WebP 等图像格式通常比 PNG 或 JPEG 提供更好的压缩效果,这意味着更快的下载速度和更少的数据消耗。
解决方案(使用 webp 图片) → 如果你像我一样使用图片服务,它们通常也提供格式筛选功能,可以将图片转换为 webp/jpeg 格式。这样你就可以上传任何类型的图片,但始终下载到的是优化后的版本!
我使用的方法→ 我使用img2.storyblok服务,并添加了 filters:format(webp) 参数。但前提是浏览器支持这种格式。
我发现的问题→ 我需要按客户端的 canvas 渲染方式进行过滤,以避免在不支持 webp 格式的浏览器(如 Safari)中显示 webp 图像(webp 格式将在未来的版本中得到支持):
format = this.canUseWebP() ? '/filters:format(webp)' : '/filters:format(/*jpeg OR png*/)'
// In methods
canUseWebP() {
if (window.canUseWebP) {
return window.canUseWebP
}
const el = document.createElement('canvas')
if (el.getContext && el.getContext('2d')) {
window.canUseWebP =
el.toDataURL('image/webp').indexOf('data:image/webp') === 0
return window.canUseWebP
}
window.canUseWebP = false
return window.canUseWebP
},
感谢@passionPeopleNL的同事们,他们为我解答了这个问题!
5. 图像元素具有明确的宽度和高度
为图像元素设置明确的宽度和高度,以减少布局偏移并改善CLS。
解决方案(优化 CLS ) → 始终在图像和视频元素中包含宽度和高度尺寸属性。
或者,使用CSS 宽高比框预留所需的空间。
我使用的方法→ 我创建了一个通用的图像组件。
这样,每次我定义图像时,我都会调用这个组件,它不仅会使用 v-lazy 优化我的图像并过滤格式,而且属性也不会允许你不传递宽度和高度。
这样我们就能始终确保符合标准。
6. 使用视频格式制作动画内容
大型 GIF 图像不适合传输动画内容。为了节省网络流量,建议使用 MPEG4/WebM 视频传输动画,使用 PNG/WebP 传输静态图像,而不是 GIF 格式。
解决方案(高效的动画内容) → 许多图片 CDN 支持 GIF 转 HTML5 视频。您将 GIF 上传到图片 CDN,图片 CDN 会返回一个 HTML5 视频。
如果您需要自行完成此操作,我建议您阅读文章《使用 HTML5 视频提高动画 GIF 性能》 。
7. 预加载最大内容绘制图像
预加载 LCP 元素使用的图像,以改善 LCP 时间。
解决方案(优化 LCP ) → 如果您知道某个特定资源应该优先获取,请使用link rel="preload"提前获取它。
可以预加载多种类型的资源,但首先应该关注预加载关键资源,例如字体、首屏图像或视频以及关键路径 CSS 或 JavaScript。
我使用的方法→ 在文章页面中,我使用Nuxt 提供的head 方法,将文章的特色图片作为预加载链接放置在 head 标签中。
head() {
return {
link: [
{
rel: 'preload',
as: 'image',
href: transformImage(this.story.content.featured_image, '672x0'),
},
],
}
}
字体
1. 网页字体加载期间所有文本均保持可见
利用font-display CSS 功能,确保在网页字体加载时文本对用户可见。
解决方案(字体显示) → 避免自定义字体加载时显示不可见文本的最简单方法是暂时显示系统字体。通过在 @font-face样式 中 添加font-display: swap ,您可以避免在大多数现代浏览器中出现 FOIT 问题:
@font-face {
font-family: 'Pacifico';
font-style: normal;
font-weight: 400;
src: local('Pacifico Regular'), local('Pacifico-Regular'), url(https://fonts.gstatic.com/s/pacifico/v12/FwZY7-Qmy14u9lezJ-6H6MmBp0u-.woff2) format('woff2');
font-display: swap;
}
字体 显示 API 指定字体的显示方式。swap 函数 告诉浏览器,使用该字体的文本应立即使用系统字体显示。自定义字体准备就绪后,它会替换系统字体。
例如,对于 Google 字体,只需在 Google 字体网址末尾添加&display=swap 参数即可 :
<link href="https://fonts.googleapis.com/css?family=Roboto:400,700&**display=swap**" rel="stylesheet">
我目前使用的是@font-face替换技术,字体文件直接包含在我的项目中。
应该避免什么?
1. 避免多次页面重定向
重定向会在页面加载之前引入额外的延迟(避免多次重定向)。
我避免使用重定向。
2. 避免向现代浏览器提供旧版 JavaScript 代码
Polyfill 和转换函数使旧版浏览器能够使用新的 JavaScript 特性。然而,对于现代浏览器来说,其中许多特性并非必需。
解决方案(详细信息) → 对于捆绑的 JavaScript,采用使用模块/非模块特性检测的现代脚本部署策略,以减少发送到现代浏览器的代码量,同时保留对旧版浏览器的支持。
我的用法→ 在 Nuxt 中,构建命令里有`--modern`参数以及一些其他选项。对我来说,生成命令时使用 `--modern` 就足够了。
请查看这篇精彩的教程https://dev.to/debs_obrien/modern-build-in-nuxt-js-17lc以了解更多信息。
3. 避免巨大的网络负载
大型网络负载会给用户带来实际的经济损失,并且与较长的加载时间高度相关。
解决方案(总字节重量) → 有一些方法可以最大限度地减小有效载荷的大小:
- 等到需要时再提交请求。Nuxt会处理这些请求。
- 尽可能优化请求大小,进行最小化和压缩,并尽可能使用WebP 格式的图片。我们会始终使用图片 CDN来保持性能稳定!
-
缓存请求,这样页面在重复访问时就不会重新下载资源。
4. 避免使用 document.write()
对于网络连接速度较慢的用户,通过document.write()动态注入的外部脚本可能会使页面加载延迟数十秒。
解决方案(不写入文档) → 在您自己的代码中,您可以完全控制是否添加它,但我建议,每当您使用第三方时,都要检查它是否使用了 document.write()。
5. 避免使用非合成动画
未合成的动画可能会出现卡顿,并增加CLS。
解决方案(非合成动画) → 目前我没有太多动画,但我仅有的几个动画都应用了浏览器运行成本低的属性:平移和缩放。
阅读这篇教程https://www.html5rocks.com/en/tutorials/speed/high-performance-animations/将会明白其中的原因。
关于这个主题的有趣文章
https://wildbit.com/blog/2020/09/30/getting-postmark-lighthouse-performance-score-to-100
文章来源:https://dev.to/dawntraoz/my-web-performance-journey-with-nuxt-storyblok-netlify-2bl4