JackyLove 的技术人生

人生艰难,唯有一技傍身才能慢慢走向通途。个人博客,记录生活,分享技术,记录成长。专注全栈技术,next技术。

第56章—实战篇-博客-性能分析

首次发表于 2024-07-29, 更新于 2024-07-29

前言

本篇讲解如何对 Next.js 应用的性能进行监控。

Bundle Analyzer

首先是 bundle 包的管理。Next.js 提供了 @next/bundle-analyzer 插件,它会生成每个模块大小及其依赖的可视化报告。你可以据此删除较大的依赖项或者拆分代码从而减少客户端 bundle 的大小。

安装插件:

npm i @next/bundle-analyzer

修改 next.config.mjs,代码如下:

import { withContentlayer } from 'next-contentlayer'
import bundleAnalyzer from '@next/bundle-analyzer'

const withBundleAnalyzer = bundleAnalyzer({
  enabled: process.env.ANALYZE === 'true',
})

export default withBundleAnalyzer(withContentlayer({}))

修改 package.json,添加脚本命令:

{
  // ...
  "scripts": {
    "analyze": "ANALYZE=true next build"
  },
  // ...
}

命令行运行 npm run analyze,它会陆续生成 3 个 HTML 文件,并同时为你打开浏览器展示文件:

一个是 nodejs.html,展示 nodejs server bundle:

截屏2024-05-15 22.10.32.png

一个是 edge.html,展示 edge server bundle:

截屏2024-05-15 22.11.22.png

一个是 client.html,展示客户端浏览器 js bundle:

截屏2024-05-15 22.11.57.png

性能测量

如果你只是要在上线前,测一下应用的性能,看看有哪些性能优化工作要做,履行一下前端的“职责”,那你可以:

1. Chrome Lighthouse 插件

安装 Chrome Lighthouse 插件,安装完成后,开发者工具会有一个 lighthouse 选型,用于测量页面性能。以掘金为例:

image.png

lighthouse 会列出需要具体改善的点,参照建议完善即可。

2. PageSpeed Insights

PageSpeed Insights 是一款由 Google 开发的网页性能评估工具,可以帮助开发者评估网页的性能,并提供优化建议。

打开 https://pagespeed.web.dev/,输入网址查看页面测试结果即可:

image.png

3. 其他性能测试网站

当然这种性能测试网站还是蛮多的,比如 https://www.webpagetest.org/https://tools.pingdom.com/ 等等

Web Vitals Analytics

但是这种上线前测试一下性能的做法问题也很明显,样本有限导致数据不够准确,而且无法准确反应真实用户的体验。

如果真的要解决这个问题的话,就需要在页面实现测量和上报性能相关的数据。可是要测量哪些性能相关的数据呢?性能相关的数据又该如何上报、搜集、分析、输出图标呢?

这里的解决方案有很多种,我提供一种 Prometheus + Grafana 的方式,这套技术选型也是常见的用于数据采集、分析的解决方案。我们且慢慢说来。

1. 性能测量

其实 Next.js 支持对性能进行测量和上报。Next.js 提供了 useReportWebVitals hook 自行管理数据。

新建 components/WebVitals.js,代码如下:

'use client'
 
import { useReportWebVitals } from 'next/web-vitals'
 
export function WebVitals() {
  useReportWebVitals((metric) => {
    console.log(metric)
  })
}

修改 next-blog/app/[lng]/layout.js,添加代码如下:

// ...
import { WebVitals } from '@/components/WebVitals.js'
// ...

export default function RootLayout({ children, params: { lng } }) {
  return (
    <html lang={lng} dir={dir(lng)} suppressHydrationWarning>
      <body>
        <WebVitals />
        <ThemeProviders>
          <header className="flex justify-end">
            <ThemeSwitch />
            <LangSwitch />
          </header>
          {children}
        </ThemeProviders>
      </body>
    </html>
  );
}

访问页面如 http://localhost:3000/posts/first,打印效果如下:

image.png

这 6 个指标就是 Web Vitals(网页指标),它是 Google 的一项计划,旨在针对网页质量信号提供统一指南,这些信号对于提供出色的网页用户体验至关重要。它的目标是简化各种可用的性能测量工具,并帮助网站所有者专注于最重要的指标。

注:其实业界尝试过非常多用于衡量性能和体验的指标,但很多指标由于难以测量、逻辑复杂等原因逐渐消亡。Web Vitals 背靠 Google,算是大浪淘沙,经过时间的检验。但这些指标也会随着时间的推移而演变,目前是这 6 个指标。

为了更直观的展示这些指标,我为大家总结了一个表格:

指标 中文名 目的 标准
LCP(Largest Contentful Paint) 最大内容绘制 衡量加载性能 image.png
Core Web Vitals 这组指标侧重于用户体验的三个方面:加载、互动和视觉稳定性 INP(Interaction to Next Paint) 交互到绘制延迟 衡量互动 image.png
CLS(Cumulative Layout Shift) 累积布局偏移 衡量视觉稳定性 image.png
FCP(First Contentful Paint) 首次内容绘制 衡量加载体验,有助于诊断 LCP 问题(服务器响应时间过长或阻塞渲染的资源) image.png
Core Web Vitals 的代理或补充指标 这组指标有助于捕获更广泛的体验或诊断特定问题 FID(First Input Delay) 首次输入延迟 衡量互动,未来可能完全被 INP 替代 image.png
TTFB(Time to First Byte) 第一字节时间 衡量加载体验,有助于诊断 LCP 问题 image.png

那如何测量这些指标呢?

Chrome 团队提供了 web-vitals 开源工具库,它基于统一的浏览器 Performance API 获取标准化的用户体验数据。

Next.js 提供的 useReportWebVitals hook 背后也是用这个库来实现的。

2. 性能上报

1.1. Prometheus 介绍

采集到了数据,数据该如何上报并可视化呢?

我们的技术方案是用 Prometheus 和 Grafana。我们先来介绍一下这两个技术选型:

Prometheus(普罗米修斯)是一个用于监控和报警的开源系统,它自带一个基于时间序列的数据库。

它的架构设计如下:

image.png

看到这个架构图先不要害怕,其实很简单。简单来说,Prometheus 由这几部分组成:

  1. Prometheus Server 负责收集数据,将数据存放到数据库中
  2. 数据怎么发给 Prometheus Server 呢?有两种方式上报数据:
    1. 一种是 pull 模式,待监控的服务暴露指标接口,由 Prometheus server 定期拉取采集。
    2. 另一种是 push 模式,待监控的服务直接将数据推给 Prometheus server(通常会借助 Pushgateway 待监控服务推给 Pushgateway,Prometheus Server pull Pushgateway)
  3. Alertmanager 负责监控指标进行报警,使用 email 等方式通知
  4. PromQL(Prometheus Query Language)是 Prometheus 内置的数据查询语言,可以结合 Grafana 等数据可视化工具将数据更好的展现出来

关于本篇我们采用的方式:

我们会使用 Express 自建一个服务,用于处理 Next.js Web Vitals 数据的上报。然后由 Prometheus Server 定期拉取指标数据,最后结合 Grafana 做数据可视化。

1.2. Grafana 介绍

Grafana 是一款开源的数据可视化工具,使用它是因为:

  1. 与 Prometheus 兼容良好
  2. 可视化模板多,界面高大上
  3. 可免费私有部署

image.png

1.3. Express Server

让我们运行:

# 创建文件夹
mkdir node-monitor && cd node-monitor
# 初始化
npm init
# 安装依赖项,其中 prom-client 是 Prometheus 的 Node 客户端
npm i express cors prom-client

新建 app.js,代码如下:

import express from 'express';
import client, { collectDefaultMetrics } from 'prom-client';
import cors from 'cors';

const register = new client.Registry();
collectDefaultMetrics({ register });

const app = express();

app.use(express.text())
app.use(cors())

app.get('/metrics', async (_req, res) => {
  try {
    res.set('Content-Type', register.contentType);
    res.end(await register.metrics());
  } catch (err) {
    res.status(500).end(err);
  }
});

app.post('/report', function (req, res) {
  const { name, rating} = JSON.parse(req.body);
  let counter = register.getSingleMetric(name);
  if (!counter) {
    counter = new client.Counter({
      name,
      help: req.body,
      registers: [register],
      labelNames: ['rating'],
    });
  }
  counter.inc({
    rating
  }, 1);
  res.status(200).json({ success: true });
});

app.listen(4001, '0.0.0.0');

在这段代码中,我们提供了一个 /metrics接口用于 Prometheus Server 拉取数据,一个 /report接口用于 Next.js 应用上报数据。至于代码具体为什么这样写,参考 prom-client

修改 package.json,添加代码如下:

{
  // ...
  "type": "module",
  "scripts": {
    "start": "node app.js"
  },
  // ...
}

运行 npm start,打开 http://localhost:4001/metrics,效果如下:

image.png

数据看不懂?没有关系,这些是 Node 应用相关的数据,可用于监控 Node 性能。我们真正要监控和记录的是 Web Vitals 相关的数据。

修改我们的 Next.js 应用 components/WebVitals.js:完整代码如下:

'use client'
 
import { useReportWebVitals } from 'next/web-vitals'
 
export function WebVitals() {
  useReportWebVitals((metric) => {
    console.log(metric)

    const body = JSON.stringify(metric)
    const url = 'http://localhost:4001/report'
   
    // Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
    if (navigator.sendBeacon) {
      navigator.sendBeacon(url, body)
    } else {
      fetch(url, { body, method: 'POST', keepalive: true })
    }
    
  })
}

关于 navigator.sendBeacon

navigator.sendBeacon() 方法可用于通过 HTTP POST 将少量数据 异步 传输到 Web 服务器。 它主要用于将统计数据发送到 Web 服务器,同时避免了用传统技术(如:XMLHttpRequest)发送分析数据的一些问题。

我们刷新一下 Next.js 的页面,上报一些数据,再查看 http://localhost:4001/metrics,可以看到产生了一批 Web Vitals 相关的数据指标:

image.png

这说明我们的 Next.js 应用(3000 端口)和 Express 应用(4001 端口)已经打通了!

1.4. Docker

为了方便起见,我们使用 Docker 来开启 Prometheus 和 Grafana。

在 node-monitor 项目下新建 prometheus.yml文件(当然放在其他地方也是可以的),用于 Prometheus 的配置文件,代码如下:

global:
  scrape_interval: 5s
scrape_configs:
  - job_name: "next-app"
    static_configs:
      - targets: ["docker.for.mac.host.internal:4001"]

配置文件告诉 Prometheus 每 5 秒抓取一次所有目标。目标在 scrape_configs 下定义。在 Mac 上,需要使用 docker.for.mac.host.internal 作为主机,以便 Prometheus Docker 容器可以抓取本地 Node.js HTTP 服务器的指标。在 Windows 上,使用 docker.for.win.localhost;在 Linux 上,使用 localhost

然后运行:

docker run --rm -p 9090:9090 \
  -v `pwd`/prometheus.yml:/etc/prometheus/prometheus.yml \
  prom/prometheus

Windows 用户需要将 pwd 替换为当前工作目录的路径。

注:这里我们将端口开发了默认的 9090 端口,如果你开了 Clash,可能会产生端口冲突。关掉 Clash 或者修改 Clash 的配置文件,将其更改到其他端口。

如果正常开启,此时访问 http://localhost:9090/,可以看到:

image.png

对于 Prometheus 的初学者,可以通过访问 http://localhost:9090/api/v1/label/name/values 来查看有哪些数据指标:

image.png

我们输入 FCP,点击 Execute 按钮,如果能查询到数据就说明 Prometheus 应用(开在 9090 端口)与我们的 Express 应用(开在 4001 端口)连接成功:

image.png

接下来开启 Grafana,运行:

docker run -d --name=grafana -p 3001:3000 grafana/grafana

正常 Grafana 开启在 3000 端口,但跟我们的 Next.js 应用冲突了,所以开在了 3001 端口。如果正常开启,访问 http://localhost:3001/,会跳转到登录页面:

image.png

账号和密码都是 admin,登录后:

截屏2024-05-17 22.19.56.png

选择 Prometheus,然后点击 Add new data source按钮,添加 Prometheus 作为数据源,进入设置页面:

截屏2024-05-17 22.22.13.png

Prometheus server URL 这里填写 http://host.docker.internal:9090,这是因为 Prometheus 和 Grafana 都是通过 Docker 开启,环境与本机有隔离,所以无法通过 localhost 直接访问。如果这个地址不行,填写 http://prometheus:9090 试试。

如果能够配置成功,此时我们的各个应用就算联调成功了。一共涉及 4 个应用,Next.js 应用是我们开发的博客项目,博客页面会上报 Web Vitals 数据,Express 应用会接收上报的 Web Vitals,并提供 /metrics 接口供 Prometheus 应用拉取采集数据。最后 Grafana 应用会连接 Prometheus,用可视化的图表代替 Prometheus 的 PromQL 语句查询。

1.5. Grafana 图表

接下来我们开始建立一个 Grafana 图表:

截屏2024-05-17 22.30.38.png 截屏2024-05-17 22.31.46.png截屏2024-05-17 22.32.21.png

截屏2024-05-17 22.34.37.png

这样我们就建立了一个 FCP 评分占比的饼图:

image.png

可以看到评分为 poor 的有多少个,评分为 good 的有多少个。(数据量太少了,所以没有 need improvement)

接下来我们就可以根据自己的需要自定义需要的图表。

最后

目前整体的打通非常简陋,比如 Prometheus 和 Grafana 都没有做数据持久化,一旦重启,数据就会丢失。只监控了自定义的 Web Vitals 指标,Node 性能相关的指标没有做自定义上报。Grafana 也只实现了简单的百分比,而没有实现各种高大上的图表。

Prometheus 和 Grafana 其实内容非常多,碍于作者能力有限,只能为大家简单介绍下这套技术方案,更多的内容还需要大家自己去探索。

参考链接

  1. https://web.dev/articles/vitals?hl=zh-cn
  2. https://web.dev/articles/fcp?hl=en
  3. https://codersociety.com/blog/articles/nodejs-application-monitoring-with-prometheus-and-grafana
  4. https://brew.sh/
  5. https://github.com/vercel/next.js/discussions/16205
  6. https://hub.docker.com/r/prom/prometheus
  7. https://github.com/grafana/grafana/issues/46434
© Copyright 2025 JackyLove 的技术人生. Powered with by CreativeDesignsGuru