Getting Started With Web Vitals in Next.js

Diogo Diogo Souza on

In this article, I’ll try to guide you through some examples and definitions that aim to clarify the Web Vitals landscape from a Next.js perspective. Let’s dive in!

How Google Grades Your Websites

User experience is something that Google appreciates when its robots scan your website. They perform checks to make sure your website deserves a good spot on Google’s famous search engine results page.

They look for quality indicators such as performance, interactivity, the structure of the pages, responsiveness, safety (e.g. HTTPS), etc.

If you’ve ever ventured into SEO waters, chances are that at first, you felt overwhelmed by the amount of stuff to worry about.

For this reason, Google came to the rescue with Web Vitals. They are a new way of analyzing your web pages and checking beforehand for things you might need to address and improve.

Web vitals are a guide made with everybody in mind so that you can easily find out how your website performs. In case there are issues, you should be able to figure out how to address them with ease.

What Are Web Vitals?

To describe this a bit better, let’s check out a Chrome tool called Lighthouse. If you’ve never heard about it, it’s an open-source automated tool that analyzes and collects quality metrics of web pages; and, yes, it makes use of Web Vitals principles.

The tool is pretty straightforward. On the page that you want to analyze, right-click -> inspect -> look for Lighthouse in the top bar. From there, there are a few options you can choose from:

Lighouse Preferences

Picking up your Lighthouse preferences.

When we run the tool against the AppSignal homepage, we’ll get similar results to these:

AppSignal metrics

AppSignal’s performance metrics.

Here, we’re just showing the metrics related to the Performance section because they embrace more of what Web Vitals do. However, Lighthouse does more.

In short, Web Vitals are divided into six main categories, among which, three are classified as Core Web Vitals to know:

Metric Description Graph reference
Largest Contentful Paint (LCP) Here, Google tries to analyze the page loading time as it is perceived by the user, i.e., how long does it take for the main content of your page to load?
If the answer is >2.5s then it needs to be improved. Of course, the lower the better.
LCP
First Input Delay (FID) / Total Blocking Time (TBT) Now, let’s measure how long it takes for the first user interaction to happen within your page. Whether it is through a button click, a page scroll, your browser needs to be ready to respond to those commands in no time, even if the page is not fully loaded. If the time is greater than 100ms, Google asks you to work on that.
Oh, yes, the second indicator, TBT, is an auxiliary measurement that analyzes the difference between the FCP and the TTI 一 more on them soon. In the end, they’re birds of a feather.
FID
Cumulative Layout Shift (CLS) This one counts the number of times that stuff or components within a page move (shift) around during the loading.
Have you ever entered a website in which the elements start to “dance” alone before we can even figure out what’s the website about? So, the more you have this, the poorer the experience.
CLS

Graph’s source: https://web.dev/vitals/

As you may have perceived, The Core Web Vitals are worried about three main points: the loading time, the responsiveness (interactivity), and the stability of the page.

The other three non-Core Web Vitals are:

Those Vitals are considered more as of auxiliary since they help the Core ones (as well as Lighthouse) to calculate their scores.

Now that you understand a bit more about them, you can refer back to AppSignal’s Lighthouse metrics in the previous figure and recognize what all the indicators, along with their values, are for.

For more details on how Google calculates these metrics behind-the-scenes, please refer to the official docs.

Web Vitals With Next.js

Yes, since version 9.4.0, Next.js comes with a built-in layer that understands Web Vitals and allows you to collect such metrics within your Next apps.

Let’s see how it works. First, we need a Next.js app. We’ll be making use of Yarn as the package manager.

Run the following command:

1
yarn create next-app

When it prompts you to fill in the app name, give it the value “web-vitals-next”. Once the creation is finished, open your project with VS Code and run the build command:

1
yarn build

Finally, run the project with the yarn dev command. It’ll be available, by default, at http://localhost:3000/.

Introducing Web Vitals to your Next.js app is very simple. You just need to make sure you have a custom App component (which our yarn creation command already did) and add the following function to your pages/_app.js (or .ts, for TypeScript) file:

1
2
3
export function reportWebVitals(metric) {
  metric.label === "web-vital" && console.log(metric);
}

This function alone will execute whenever one of the Web Vitals metrics takes place. In the image below, you get to see how it works by logging to the browser’s console, the metrics as they happen:

Browser console logs

Web Vitals metrics logged in the browser’s console.

The structure of a metric object follows this pattern:

Web Vitals metric object

Web Vitals metric object.

It’s important to sort your logs by the label value, especially because Next.js also logs some custom metrics that you may not need.

However, metrics in the browser’s console are not useful. We need to send them to an analytics platform so they can be processed and digested to generate real-time and accountable information.

If you use Google Analytics as such tool, sending the data would be as simple as this:

1
2
3
4
5
6
7
8
9
10
export function reportWebVitals({ id, name, label, value }) {
  ga('send', 'event', {
    eventCategory:
      label === 'web-vital' ? 'Web Vitals' : 'Next.js custom metric',
    eventAction: name,
    eventValue: Math.round(name === 'CLS' ? value * 1000 : value), // values must be integers
    eventLabel: id, // id unique to current page load
    nonInteraction: true, // avoids affecting bounce rate.
  })
}

Sending Metrics to AppSignal

To have a better view of the metrics, let’s take our example to send Web Vitals metrics to AppSignal dashboards as shown below:

AppSignal dashboards

Pushing metrics to AppSignal is super easy and fast!

First, you need an AppSignal account 一 you have a 30-days free trial. Once you’ve logged in, you’ll be redirected to the startup page, on which you get to choose the language of the app you desire AppSignal to be installed to.

Select Node.js. The page that follows will show the instructions to install AppSignal to your Node.js project. Keep the APPSIGNAL_PUSH_API_KEY key, since it’s going to be important later on.

Let’s move on to the app changes. You first need to add the AppSignal npm packages by running:

1
2
yarn add @appsignal/nodejs @appsignal/nextjs
yarn install

Pay attention to the log results and check if it finished successfully.

Secondly, AppSignal integration doesn’t work with Next CLI, which is the tool that our example app was built with. That means that we have to create our own custom server script file and start the application through it.

Go ahead and create a new file called server.js into the app’s root folder. This is the content it must have:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
const { Appsignal } = require("@appsignal/nodejs");
var http = require('http');

const appsignal = new Appsignal({
  active: true,
  name: "web-vitals-next",
  apiKey: "PUT_YOUR_KEY_HERE", // <<-- Change this!!
});

const {
  getRequestHandler,
  EXPERIMENTAL: { getWebVitalsHandler },
} = require("@appsignal/nextjs");

const url = require("url");
const next = require("next");

const PORT = parseInt(process.env.PORT, 10) || 3000;
const dev = process.env.NODE_ENV !== "production";
const app = next({ dev });

const handle = getRequestHandler(appsignal, app);
const vitals = getWebVitalsHandler(appsignal);

app.prepare().then(() => {
  http.createServer((req, res) => {
    // Be sure to pass `true` as the second argument to `url.parse`.
    // This tells it to parse the query portion of the URL.
    const parsedUrl = url.parse(req.url, true);
    const { pathname, query } = parsedUrl;

    if (pathname === "/__appsignal-web-vitals") {
      vitals(req, res);
    } else {
      handle(req, res, parsedUrl);
    }
  }).listen(3000, (err) => {
    if (err) throw err;
    console.log("> Ready on http://localhost:3000");
  });
});

Attention: Don’t forget to change the apiKey in the code listing to yours.

Note that this is creating a new server with custom settings, but it’s still Next-only based. You can also configure the whole thing with Express if you want to.

Special attention to the Appsignal object, which is where AppSignal’s Node.js library tries to connect to the remote API for the first time. If anything goes wrong, this is the place where you should inspect errors.

We’re setting up the app with minimal configs, but you can find a list with all the available config options here.

Note also that, within the server setup, we’re checking if the pathname equals to /appsignal-web-vitals. This is necessary because the feature works by providing a handler function, which is designed to be used as an endpoint in your application.

This handler function, in turn, is the reportWebVitals we’ve seen before. Here’s its new content:

1
2
3
4
5
6
7
8
9
10
export function reportWebVitals(metric) {
  metric.label === "web-vital" && console.log(metric);

  const body = JSON.stringify(metric);
  const url = "/__appsignal-web-vitals";

  // Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
  (navigator.sendBeacon && navigator.sendBeacon(url, body)) ||
    fetch(url, { body, method: "POST", keepalive: true });
}

Alternatively, you may remove the console logging if you don’t want to see the logs in the browser.

The implementation makes use of the sendBeacon function with a fallback to a POST request to the AppSignal API. As simple as that!

Finally, let’s go to the package.json file and change our generated scripts:

1
2
3
4
5
"scripts": {
   "dev": "node server.js",
   "build": "next build",
   "start": "NODE_ENV=production node server.js"
},

Now, start the project locally via yarn dev command, get back to the AppSignal setup wizard and click Next step. After that, another page will appear with a 60s timer waiting for the requests to arrive from your Next app.

Always good to remember that the usage of this feature is EXPERIMENTAL and may change or be deprecated in future releases. So, be aware!

Summary

In terms of web quality measuring, Web Vitals represent the best of what’s available in the community. It’s Google’s child and has been largely embraced by the community as a trustworthy metric system to check whether your apps are good enough or still need more work.

Obviously, it’s continuously evolving, that’s why closely watching the official docs and repo is always recommended.

Apart from that, since it’s not bulletproof, be sure to make other tests and/or check out the results over different platforms to ensure that the quality is what you expected. Good luck!

P.S. If you liked this post, subscribe to our new JavaScript Sorcery list for a monthly deep dive into more magical JavaScript tips and tricks.

P.P.S. If you’d love an all-in-one APM for Node or you’re already familiar with AppSignal, go and check out AppSignal for Node.js.

10 latest articles

Go back
Javascript sorcery icon

Subscribe to

JavaScript Sorcery

A true sorcerer combines ancient wisdom and new discoveries. We'll provide you with both. Sign up for our JavaScript Sorcery email series and receive deep insights about JavaScript, error tracking and other developments.

We'd like to set cookies, read why.