GoogleChrome / web-vitals

Essential metrics for a healthy site.
https://web.dev/vitals
Apache License 2.0
7.55k stars 414 forks source link

Lots of instances of TypeError: n is not a function on website when incorporating event handlers #550

Open rosesyrett opened 1 day ago

rosesyrett commented 1 day ago

Description and Background

Since implementing web-vitals into my website I've seen a significant amount of TypeError instances that seem to be originating from the transpiled web-vitals.js script on the browser.

This is probably an issue with my implementation of this library, however I would just like to see if there's any indication it could be a broader issue. I'm unsure how my implementation is incorrect, going from the useful instructions in the documentation.

How I've used web-vitals

I've recently added the web-vitals npm package to my website, and started monitoring a small percentage of my traffic with these event handlers. I have wrapped all of my react components with these, i.e. we have a setup.ts function that sets up e.g. connections to sentry, our analytics pipeline, and also sets up these event handlers (given the presence of an experimental flag, which is set to about 1% currently... so 1% of requests will go through this logic). Here is what the relevant part of that file looks like:

import * as Sentry from "@sentry/browser"; // eslint-disable-line import/no-namespace
import { Integrations } from "@sentry/tracing";

import { Metric, onCLS, onFCP, onINP, onLCP, onTTFB } from "web-vitals";

function setupSentry(): void {
    Sentry.init({...});
    ...
}

export function sendAnalyticsMetricEvent(metric: Metric, timeoutMs = 2000): void {
    const subtype: string = "core_web_vitals.".concat(metric.name.toLowerCase());

    // we need to ensure the metric is serialized properly first.
    // It's possible some metrics contain circular references. In this case, just discard those keys.
    // See this issue: https://github.com/GoogleChrome/web-vitals/issues/77
    // Note: I may also want to update this function to handle e.g. LCP entries elements,
    // which are HTML elements that can't be stringified and will otherwise be completely
    // ommitted.
    let cache: any[] = [];
    let data = JSON.stringify({ ...metric, attribution: {} }, function (key, value) {
        if (typeof value === "object" && value !== null) {
            if (cache.indexOf(value) !== -1) {
                // Circular reference found, discard key
                return;
            }
            // Store value in our collection
            cache.push(value);
        }
        return value;
    });

    // Timeout promise that rejects after `timeoutMs` milliseconds
    const timeoutPromise = new Promise((_, reject) => {
        setTimeout(() => {
            reject(new Error("Analytics event timed out."));
        }, timeoutMs);
    });

    const analyticsPromise = analytics.event(
        "page_load",
        "web_vitals",
        "",
        true,
        JSON.parse(data),
        subtype
    );

    Promise.race([analyticsPromise, timeoutPromise]).catch((error) => {
        console.error(error.message); // Handle timeout or other errors
    });
}

export function setup(components: ReactComponentManifest): void {
    setupSentry();

    ...

    analytics.init();

    ...

    if (environment.getFeature("my_experiment_flag")) {
        onCLS(sendAnalyticsMetricEvent);
        onINP(sendAnalyticsMetricEvent);
        onLCP(sendAnalyticsMetricEvent);
        onFCP(sendAnalyticsMetricEvent);
        onTTFB(sendAnalyticsMetricEvent);
    }
}

So all we're doing is setting up callbacks for the event handlers, which will take the event, add an 'attribution' to it (for now, this is empty; later on, we will actually use event handlers for metrics from the web-vitals/attribution part of the library), and sanitize it, i.e. serialize and deserialize it before sending it through our analytics pipeline.

When I was testing this out, everything was fine, I was seeing the callbacks being fired correctly. However since releasing this feature on my website we have noticed some interesting sentry errors. Here is what it looks like when I visit any part of our website with the experimental flag enabled:

image

I'm not sure why the error is shown twice in the above screenshot. After this has surfaced, the website correctly calls the event handlers, and I can see data correctly being routed through our analytics pipeline. Presumably there are some initial metrics on page load which are not reported on because of this error.

Error details/ stack trace

TypeError: n is not a function

../node_modules/web-vitals/dist/web-vitals.js in c at line 1:2842

)):(i=e.value,o=[e])}})),i>r.value&&(r.value=i,r.entries=o,t())},u=s("layout-shift",c);u&&(t=d(e,r,L,n.reportAllChanges),p((function(){c(u.t 

../node_modules/web-vitals/dist/web-vitals.js in at line 1:868

ceObserver((function(e){Promise.resolve().then((function(){n(e.getEntries())}))}));return r.observe(Object.assign({type:e,buffered:!0},t||{} 

Environment details

We are on node v18.20.4, npm version 10.7.0. We are running a django application with a react frontend mostly written in typescript. Please let me know if you'd like to see more details, I can attach a package.lock or something similar but many of our libraries are internal (our repository is hosted on github enterprise).

tunetheweb commented 1 day ago

Both are saying your sendAnalyticsMetricEvent callback function is not a function. Is it possible that it's not defined yet by the time the library is loaded?

philipwalton commented 1 day ago

Also, your sendAnalyticsMetricEvent() function has a different signature than what's expected by the onXXX() function in this library, so I think something's missing in the "how you use web-vitals" example above that would explain this issue.