如何在移动设备上获得 Google PageSpeed Insights 100 分
Google PageSpeed Insights是一款用于测量网站感知延迟的工具。获得良好的评分至关重要,因为 Google 已宣布将把这些评分作为其搜索排名算法的输入。
我们着手研究如何在移动设备上获得 PageSpeed Insights 100 分。开始这项工作时,我们在桌面端的得分已经达到了 100 分,但现代商务是移动商务,而我们的得分只有 65 分左右。在这篇博文中,我们将分享一些方法,让您的网站在移动设备上也能获得 100 分。许多公司都声称在桌面端能达到 100 分,但在移动设备上达到 100 分则有些难以实现。让我们深入探讨一下。
Builder.io 是一个标准的 Next.js 网站。由于该网站本身运行在 Builder 内容平台上,因此内容已经遵循了图像大小、预加载等方面的所有最佳实践。然而,它的得分仍然只有 60 多分。为什么呢?
它有助于查看构成分数的细目分类。
该问题可以分解为:
- TBT/TTI:JavaScript 导致页面阻塞时间过长。
- FCP/LCP:页面内容太多,无法在移动浏览器上呈现。
因此,我们的目标是:
- 减少 JavaScript 的数量。
- 减少初始渲染的内容量。
为什么有这么多 JavaScript?
我们的主页本质上是一个静态页面。为什么它需要 JavaScript?因为主页是一个 Next.js 网站,这意味着它是一个 React 应用程序(我们使用Mitosis将拖放编辑器的输出转换为 React 格式)。虽然网站的绝大部分内容都是静态的,但有三项需要 JavaScript:
- 导航系统:菜单需要交互性,因此需要 JavaScript。此外,桌面设备和移动设备使用的菜单也有所不同。
- 我们需要加载一个聊天小部件
- 我们需要谷歌分析。
让我们分别深入探讨每一个。
应用程序引导
尽管这主要是一个静态网站,但它仍然是一个应用程序。为了使菜单正常工作,应用程序需要引导。具体来说,它需要运行 rehydration,框架会将模板与 DOM 进行比较,并安装所有 DOM 监听器。此过程使现有框架具有可重放性。换句话说,即使页面 95% 的内容是静态的,框架也必须下载所有模板并重新执行它们以确定监听器是否存在。这意味着该网站被下载了两次,一次以 HTML 格式,另一次以 JavaScript 中的 JSX 格式。
更糟糕的是,rehydration 过程非常缓慢。框架必须访问每个 DOM 节点并将其与 VDOM 进行协调,这需要时间。而且 rehydration 过程不能延迟,因为它与安装 DOM 监听器的过程相同。延迟 rehydration 会导致菜单无法正常工作。
我们上面描述的是所有现有框架的根本限制。你看,它们都是可重放的。这也意味着,没有一个现有框架能让你在真实网站的移动端跑出 100 分。HTML 和 JavaScript 的数量实在太多,无法塞进 PageSpeed 为移动端分配的极小空间里。
我们需要从根本上重新思考这个问题。由于网站的大部分内容都是静态的,我们不应该用 JavaScript 重新下载这部分内容,也不应该为不需要的内容付费。这正是Qwik真正闪耀的地方。Qwik 是可续播的,而不是可重播的,这正是它与众不同之处。因此,Qwik 无需:
- 在页面加载时进行引导
- 遍历 DOM 来确定监听器的位置
- 急切地下载并执行 JavaScript 以使菜单正常工作
以上所有内容意味着实际上不需要 JavaScript 来执行站点加载,但我们可以保留站点的所有交互性。
对讲机
Intercom 是我们网站上运行的第三方小部件,它使我们能够与客户互动。安装它的标准方法是将一段 JavaScript 代码放入 HTML 中,如下所示:
<script type="text/javascript" async="" src="https://widget.intercom.io/widget/abcd1234"></script>
<script async defer>
Intercom('boot', {app_id: 'abcd1234'}
</script>
但是,上述情况存在两个问题:
- 它添加了需要下载和执行的 JavaScript。这将计入我们的 TBT/TTI。
- 这可能会导致布局偏移,从而违反 CLS。这是因为 UI 最初渲染时没有使用 Widget,而随着 JavaScript 的下载和执行,再次渲染时会使用 Widget。
Qwik 同时解决了这两个问题。
首先,它抓取 Intercom 用于渲染小部件的 DOM。接下来,将 DOM 插入到实际页面中,如下所示:
<div class="intercom-lightweight-app" aria-live="polite">
<div
class="intercom-lightweight-app-launcher intercom-launcher"
role="button"
tabIndex={0}
arial-abel="Open Intercom Messenger"
on:click='ui:boot_intercom'
>
...
</div>
<style id="intercom-lightweight-app-style" type="text/css">...</style>
</div>
这样做的好处是,小部件可以与应用程序的其他部分即时渲染。浏览器下载 Intercom JavaScript 并执行小部件创建时,不会出现任何延迟或闪烁。这会带来更好的用户体验和更快的网站启动速度。(它还能节省移动设备的带宽。)
然而,我们仍然需要一种方法来检测对 Widget 的点击,以及一些代码,以便在用户与 Widget 交互时将模拟 Widget 替换为实际的 Intercom Widget。这可以通过属性实现。该属性指示 Qwik在用户点击模拟 Widget 时on:click="ui:boot_intercom"
进行下载。boot_intercom.js
内容:boot_intercom.js
export default async function(element) {
await import('https://widget.intercom.io/widget/abcd1234');
const container = element.parentElement;
const body = container.parentElement;
body.removeChild(container);
Intercom('boot', { app_id: 'abcd1234' });
Intercom('show');
}
上面的文件会下载真正的 Intercom 小部件,移除模拟文件,并启动 Intercom。所有这一切自然发生,用户甚至不会察觉到切换。
谷歌分析
到目前为止,我们在延迟 JavaScript 执行方面取得了显著成效,从而提升了网站的性能。但分析功能则不同,因为我们无法延迟它,必须立即启动它。单靠启动分析功能,我们无法在移动版 PageSpeed Insights 上获得 100 分。为了解决这个问题,我们将使用PartyTown在 Web Worker 中运行 GoogleAnalytics 。稍后我会详细介绍。
JavaScript 延迟
上述工作将网站需要下载和执行的 JavaScript 数量降低到约 1KB,执行时间仅为 1 毫秒。本质上,几乎无需等待。如此少的 JavaScript 代码量使我们能够在 TBT/TTI 测试中获得满分。
HTML 延迟
然而,即使基本上没有 JavaScript,如果我们不解决发送到客户端用于首屏渲染的 HTML 数量问题,我们仍然无法在移动端获得 100 分。为了改进 FCP/LCP,我们必须将其缩减到最低限度。这可以通过仅发送首屏渲染的 HTML 来实现。
这并非新想法,但执行起来却颇为艰难。现有的框架让这变得困难,因为没有简单的方法将应用程序拆分成首屏上方和下方的部分。VDOM 在这方面毫无用处,因为即使只投影了其中的一部分,应用程序也会生成整个 VDOM。如果部分 VDOM 缺失,框架会在 rehydration 时重新创建整个网站,这会导致初始引导工作量进一步增加。
理想情况下,我们希望不发布首屏以下的 HTML,同时在首屏以上保持一个完全交互的菜单系统。但实际上,这很难做到,因为目前这类最佳实践的缺乏就足以证明这一点。这太难了,所以没人这么做。
Qwik 以 DOM 为中心,这让它与众不同。整个页面在服务器上渲染。然后,页面中不需要加载的部分会被定位并移除。随着用户滚动,缺失的部分会被延迟下载并插入。Qwik 不介意这类 DOM 操作,因为它是无状态的,并且以 DOM 为中心。
以下是我们服务器上的实际代码,可实现网站首屏以下的延迟加载:
async render(): Promise<void> {
await (this.vmSandbox.require('./server-index') as ServerIndexModule).serverIndex(this);
const lazyNode = this.document.querySelector('section[lazyload=true]');
if (lazyNode) {
const lazyHTML = lazyNode.innerHTML;
lazyNode.innerHTML = '';
(lazyNode as HTMLElement).style.height = '999em';
lazyNode.setAttribute('on:document:scroll', 'ui:/lazy');
this.transpiledEsmFiles['lazy.js'] = `
export default (element) => {
element.removeAttribute('on:document:scroll');
element.style.height = null;
element.innerHTML = ${JSON.stringify(lazyHTML)};
};`;
}
}
代码简单明了,但使用任何现有框架都很难实现。
查看下面的折叠延迟加载的实际操作:
请注意,页面首次加载时,首屏以下内容尚未加载;用户滚动页面后,内容便会填充。由于无需执行任何复杂的代码,因此加载过程几乎是即时的。只需一个快速而直接的innerHTML
。
尝试一下
您可以在此处亲自体验该页面:https://www.builder.io/? render=qwik 。(并查看PageSpeed上的分数)我们仍然缺少分析功能,但很快就会推出。
喜欢吗?我们计划让每一位 Builder.io 用户都能使用 Qwik,让他们的网站开箱即用,速度飞快。你从未见过如此快速的平台。
以上内容让你兴奋不已?那就加入我们的团队,帮助我们加速网络吧!
- 在StackBlitz上尝试
- 在github.com/builderio/qwik上为我们加星
- 在@QwikDev和@builderio上关注我们
- 在Discord上与我们聊天
- 加入builder.io