GoogleChrome / web-vitals

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

Do I need to wait for DOMContentLoaded? #38

Closed talsafran closed 4 years ago

talsafran commented 4 years ago

Hi there,

I'm unsure from your docs about whether or not it's necessary to wait for DOMContentLoaded, or some other pageload event?

In most of your examples you don't seem to wait:

import {getCLS, getFID, getLCP} from 'web-vitals';

function sendToAnalytics(metric) {
  const body = JSON.stringify(metric);
  // Use `navigator.sendBeacon()` if available, falling back to `fetch()`.
  (navigator.sendBeacon && navigator.sendBeacon('/analytics', body)) ||
      fetch('/analytics', {body, method: 'POST', keepalive: true});
}

getCLS(sendToAnalytics);
getFID(sendToAnalytics);
getLCP(sendToAnalytics);

But then further down you do:

<!-- Load `web-vitals` using a classic script that sets the global `webVitals` object. -->
<script defer src="https://unpkg.com/web-vitals@0.2.2/dist/web-vitals.es5.umd.min.js"></script>
<script>
addEventListener('DOMContentLoaded', function() {
  webVitals.getCLS(console.log);
  webVitals.getFID(console.log);
  webVitals.getLCP(console.log);
});
</script>

I couldn't figure this out on my own, since I feel like there's a chance we wouldn't want to wait for the content load, because that would affect the timing?

Also, would be great to have the library do this automatically šŸ˜Š


webVitals.ready(function() {
  webVitals.getCLS(console.log);
  webVitals.getFID(console.log);
  webVitals.getLCP(console.log);
})
Zizzamia commented 4 years ago

Generally, all Performance Observer values are buffered (check https://github.com/GoogleChrome/web-vitals/blob/master/src/lib/observe.ts#L38), to be even accessible at a later stage of the application.

The specs says

There are special considerations regarding initial page load when using the PerformanceObserver interface: a registration must be active to receive events but the registration script may not be available or may not be desired in the critical path. To address this, user agents buffer some number of events while the page is being constructed, and these buffered events can be accessed via the buffered flag when registering the observer. When this flag is set, the user agent retrieves and dispatches events that it has buffered, for the specified entry type, and delivers them in the first callback after the observe() call occurs.

Also I know TTFB and in particular performance.getEntriesByType('navigation') is not available right away, that's why we do the afterLoad callback internally.

Mention these, I personally have some confusion on this topic as well. @philipwalton what's your thoughts on this, and does the Chrome team have more research on this topic?

philipwalton commented 4 years ago

You do not need to wait for DCL to use any of these APIs.

As @Zizzamia mentions, PerformanceObserver now has buffering, which means you can observe entries both before or after they happen and still have the callback invoked.

But then further down you do:

The only reason that example waits for DOMContentLoaded is because it's loading the UMD version of the script via a separate script tag (note how there are two script tags in that example). The DOMContentLoaded check is not to wait to call the functions, it's waiting to ensure the first script is loaded and the webVitals global variable is ready.

Note, you do not need to wait if you're loading via <script type=module> or using a module bundler (either of which are recommended over using the UMD version).

philipwalton commented 4 years ago

Also I know TTFB and in particular performance.getEntriesByType('navigation') is not available right away, that's why we do the afterLoad callback internally.

Mention these, I personally have some confusion on this topic as well. @philipwalton what's your thoughts on this, and does the Chrome team have more research on this topic?

In general, the recommended approach for the future is to not use performance.getEntries... and to use PerformanceObserver insteadā€”for exactly the issue you raised.

The only reason I used performance.getEntriesByType('navigation') in this library is to get wider browser support, but as you noted that requires waiting until after the load event.