Friday, May 24, 2024
HomeCSSIn Defence of DOM­Content material­Loaded – CSS Wizardry – Internet Efficiency Optimisation

In Defence of DOM­Content material­Loaded – CSS Wizardry – Internet Efficiency Optimisation

Written by on CSS Wizardry.

Desk of Contents
  1. Measure the Person Expertise
  2. Technically Significant
  3. What Does It Truly Imply?
  4. Placing It to Use
    1. Chrome Person Expertise Report (CrUX)
    2. Google Analytics
  5. Digging Deeper: The Navigation Timing API
  6. Even Extra Insights
  7. A Higher Method?
  8. Again to Work
    1. Bettering DOMContentLoaded

Actually, I began writing this text for no actual purpose, and considerably
with out context, in December 2022—over half a 12 months in the past! However, I left it in
_drafts/ till in the present day, when a genuinely compelling situation got here up that provides
actual alternative for rationalization. It now not seems like
trivia-for-the-sake-of-it due to a latest consumer mission.

I by no means thought I’d write an article in defence of DOMContentLoaded, however right here

For a lot of, a few years now, efficiency engineers have been making a concerted
effort to maneuver away from technical metrics akin to Load, and towards extra
user-facing, UX metrics akin to Pace

or Largest Contentful
Nevertheless, as an inner benchmark, there are compelling explanation why a few of you
may very well need to hold monitoring these ‘outdated’ metrics…

Measure the Person Expertise

The issue with utilizing diagnostic metrics like Load or DOMContentLoaded to
measure site-speed is that it has no bearing on how a consumer may really
expertise your web site. Certain, when you have Load instances of 18 seconds, your web site
in all probability isn’t very quick, however a superb Load time doesn’t imply your web site is
essentially very quick, both.

Which do you suppose supplies the higher consumer expertise?

Within the comparability above, which do you suppose supplies the higher consumer expertise?
I’m keen to guess you’d all say B, proper? However, primarily based on DOMContentLoaded,
A is definitely over 11s quicker!

Load and DOMContentLoaded are inner browser occasions—your customers haven’t any
thought what a Load time even is. I guess half of your colleagues don’t both. As
metrics themselves, they’ve little to no reflection on the actual consumer
expertise, which is precisely why we’ve moved away from them within the first
place—they’re a poor proxy for UX as they’re not emitted when something helpful to
the consumer occurs.

Or are they…?

Technically Significant

Not all metrics want to be user-centric. I’m keen to guess you continue to monitor
, though
you realize your prospects could have no idea of a primary byte in anyway. That is
as a result of some metrics are nonetheless helpful to builders. TTFB is an effective measure of
your server response instances and normal back-end well being, and points right here could have
knock-on results later down the road (particularly with Largest Contentful Paint).

Equally, each DOMContentLoaded and Load aren’t simply meaningless browser
occasions, and when you perceive what they really signify, you may get some
actual insights as to your web site’s runtime behaviour from every of them. Diagnostic
metrics akin to these can spotlight bottlenecks, and the way they could finally
influence the consumer expertise in different methods, even when in a roundabout way.

That is notably true within the case of DOMContentLoaded.

What Does It Truly Imply?

The DOMContentLoaded

fires as soon as all your deferpurple JavaScript has completed operating.

Subsequently, anybody leaning closely on defer—or frameworks that utilise
it—ought to instantly see the importance of this metric.

If you happen to aren’t (capable of) monitoring customized metrics round your utility’s
interactivity, hydration state, and many others., then DOMContentLoaded instantly
turns into a really helpful proxy. Understanding when your principal bundles have run is nice
perception in lieu of extra forensic runtime information, and it’s one thing I take a look at with
any consumer that leans closely on (frameworks that lean closely on) defer or

Extra precisely, DOMContentLoaded signifies that all blocking and
defer and kind=module code has completed operating. We don’t have any
visibility on whether or not it ran efficiently but it surely has a minimum of completed.

Placing It to Use

I’m working with a consumer in the intervening time who’s utilizing Nuxt
and has their client-side JavaScript break up into an eyewatering 121 deferpurple

View unabridged.

Above, the vertical pink line at 12.201s signifies the DOMContentLoaded occasion.
That’s late! This consumer doesn’t have any RUM or customized monitoring in place (but), so, aside from Core Internet Vitals, we don’t have a lot
visibility on how the location performs within the wild. Primarily based on a 12s
DOMContentLoaded occasion, I can’t think about it’s doing so properly.

The issue with Core Internet Vitals, although, is that its solely actual JavaScripty
metric, First Enter Delay, solely offers with consumer
interplay: what I wish to know is with 121 deferpurple information, when is
there one thing to truly work together with?!
Primarily based on the lab-based 12s
above, I’d like to know what’s taking place for actual customers. And by chance, whereas
DOMContentLoaded is now thought-about a legacy metric, we are able to nonetheless get discipline
information for it from two fairly respectable sources…

Chrome Person Expertise Report (CrUX)

Issues obtained quite a bit worse between March and April 2023

CrUX Dashboard is one among
only a few CrUX assets that surfaces
the DOMContentLoaded occasion to us. Above, we are able to see that, presently, solely 11%
of Chrome guests expertise a Good DOMContentLoaded—nearly 90% of individuals
are ready over 1.5s earlier than the app’s key performance is out there, with
nearly half ready over 3.5s!

DOMContentLoaded was 4.7s for 75% of Chrome guests
in Might 2023.

It might additionally appear that Treo (which is a very wonderful software)
surfaces DOMContentLoaded information for a given

Google Analytics

Till, properly,
in the present day, Google
Analytics additionally surfaced DOMContentLoaded info. Solely this time, we
aren’t restricted to only Chrome visits! That stated, we aren’t offered with
notably granular information, both:

Big and non-linear buckets make interrogating the information rather more troublesome.

After a little bit of including up (2.15 + 10.26 + 45.28 + 25.68 + 13.07
= 96.44), we see that the ninety fifth percentile of DOMContentLoaded
occasions for a similar time interval (Might 2023) is someplace between 5 and 10
seconds. Not massively useful, however an perception nonetheless, and a minimum of reveals
us that the lab-based 12s is unlikely to be felt by anybody aside from excessive
outliers within the discipline.

Takeaways listed here are:

  1. Solely about 10% of Chrome guests have what Google deem to be a Good
    All deferpurple JavaScript has run inside 1.5s for less than
    the huge minority of tourists.
  2. 3.56% of all customers waited over 10s for DOMContentLoaded. It is a 10
    second look ahead to key deferpurple JavaScript to run.

Provided that the DOMContentLoaded occasion fires after the final of our
deferpurple information has run, there’s each chance that key performance from
any previous information has already turn into accessible, however that’s not one thing we
have any visibility over with out trying into customized monitoring, which is
precisely the state of affairs we’re in right here. Keep in mind, that is nonetheless a proxy metric—simply
a way more helpful one than you might have realised.

Digging Deeper: The Navigation Timing API

If we need to seize this information extra intentionally ourselves, we have to lean on
the Navigation Timing
which supplies us entry to a set of milestone timings, lots of which you will
have heard of earlier than.

The DOMContentLoaded as measured and emitted by the Navigation Timing API is
really known as domContentLoadedEventStart—there is no such thing as a naked
domContentLoadedEvent in that spec. As a substitute, we have now:

  1. domContentLoadedEventStart: That is the one we’re excited about, and is
    equal to the idea we’ve been discussing on this article up to now. To
    get the metric we’ve been referring to as DOMContentLoaded, you want

    • As a result of deferpurple JS is assured to run after synchronous JS, this occasion
      additionally marks the purpose that each one synchronous work is full.
  2. domContentLoadedEventEnd: The top occasion captures the time at which all
    JS wrapped in a DOMContentLoaded occasion listener has completed operating:

    window.addEventListener('DOMContentLoaded', (occasion) => {
      // Do one thing
    • That is separate to deferpurple JavaScript and runs after our
      DOMContentLoaded occasion—if we’re operating a nontrivial quantity of code at
      DOMContentLoaded, we’re additionally on this milestone. That’s not in
      the scope of this text, although, so we in all probability gained’t come again to that
      once more.

Very, very crudely, with no syntactic sugar in anyway, you may get the web page’s
DOMContentLoaded occasion in milliseconds with the next:

console.log(window.efficiency.timing.domContentLoadedEventStart - window.efficiency.timing.navigationStart);

…and the length (if any) of the DOMContentLoaded occasion with:

window.efficiency.timing.domContentLoadedEventEnd - window.efficiency.timing.domContentLoadedEventStart

And naturally, we needs to be very used to seeing DOMContentLoaded on the backside
of DevTools’ Community panel:

They’re some satisfying numbers.

Even Extra Insights

Whereas DOMContentLoaded tells us when our deferpurple code completed
operating—which is nice!—it doesn’t inform us how lengthy it took to run. We would
have a DOMContentLoaded at 5s, however did the code begin operating at 4.8s? 2s? Who
is aware of?!

We do.

View unabridged.

Within the above waterfall, which is similar one from earlier, solely even shorter,
we nonetheless have the vertical pink line round 12s, which is DOMContentLoaded,
however we even have a vertical sort-of yellow line round 3.5s (really, it’s at
3.52s precisely). That is domInteractive. domInteractive is the occasion
instantly earlier than domContentLoadedEventStart. That is the second the browser
has completed parsing all synchronous DOM work: your HTML and all blocking
scripts it encountered on the best way. Principally, the browser is now on the
</html> tag. The browser is able to run your deferpurple JavaScript.

One crucial factor to notice is that the domInteractive occasion fired lengthy,
lengthy earlier than the request for file 133 was even dispatched. Instantly this tells
us that the delta between domInteractive and DOMContentLoaded contains code
execution and any remaining fetch.

Fortunately, the browser wasn’t simply idling on this time. As a result of deferpurple code
runs in sequence, the browser sensibly fetches the information so as and
instantly executes them once they arrive. This stage of orchestration may be very
elegant and helps to utilise and preserve assets in essentially the most useful approach. Not
flooding the community with responses that may’t but be used, and in addition ensuring
that the principle thread is saved busy.

That is the JavaScript we have to measure how lengthy our deferpurple exercise took:

console.log(window.efficiency.timing.domContentLoadedEventStart - window.efficiency.timing.domInteractive);

Now, utilizing the Navigation Timing API, we have now visibility on when our deferpurple
completed operating, and the way lengthy it took!

This demo under accommodates:

  1. A slow-to-load, fast-to-run deferpurple JavaScript file.
  2. A quick-to-load, slow-to-run inline script set to run at DOMContentLoaded.
  3. Logging that out to the console on the Load occasion.
<!-- [1] -->
<script src= defer></script>

<!-- [2] -->
  window.addEventListener('DOMContentLoaded', (occasion) => {

    // Cling the browser for 1s on the `DOMContentLoaded` occasion.
    operate wait(ms) {
      var begin =,
      now = begin;
      whereas (now - begin < ms) {
        now =;



<!-- [3] -->
  window.addEventListener('load', (occasion) => {

    const timings = window.efficiency.timing;
    const begin   = timings.navigationStart;

    console.log('Prepared to begin operating `defer`ed code: ' + (timings.domInteractive - begin + 'ms'));
    console.log('`defer`ed code completed: ' + (timings.domContentLoadedEventEnd - begin + 'ms'));
    console.log('`defer`ed code length: ' + (timings.domContentLoadedEventStart - timings.domInteractive + 'ms'));
    console.log('`DOMContentLoaded`-wrapped code length: ' + (timings.domContentLoadedEventEnd - timings.domContentLoadedEventStart + 'ms'));


The `defer`ed code completed: 3129ms traces up
with DevTools’ personal reported 3.13s DOMContentLoaded.

Or check out the dwell demo on Glitch.

A Higher Method?

That is all genuinely thrilling and fascinating to me, however we’re operating into
points already:

  • DOMContentLoaded is a proxy for when all of your deferpurple JavaScript has run,
    but it surely doesn’t notify you if issues ran efficiently, or spotlight any key
    milestones as performance is consistently turning into accessible for the length.
  • DOMContentLoaded tells us how lengthy every little thing took, however that might embrace
    fetch, and there’s no approach of isolating the fetch from pure runtime.
  • If you happen to’re capturing these technical timings, you may as properly use the Person
    Timing API.

I need to broaden on the final level.

If we’re going to go to the trouble of measuring Navigation Timing occasions, we
may as properly use the rather more helpful Person Timing
With this, we are able to emit high-resolution timestamps at arbitrary factors in our
utility’s lifecycle, so as an alternative of proxying availability through a Navigation
Timing, we are able to drop, for instance, efficiency.mark('app booted') in our code.
In actual fact, that is what Subsequent.js

to let you realize when the app has hydrated, and the way lengthy it took. These Person
Timings robotically seem within the Efficiency panel:

I take advantage of efficiency.mark() and efficiency.measure() in a number of locations on this
web site
mainly to watch how lengthy it takes to parse the <head> and its CSS.

The Person Timing API is way extra suited to this sort of monitoring than one thing
like DOMContentLoaded—I’d solely take a look at DOMContentLoaded if we don’t but
have applicable metrics in place.

Nonetheless, the important thing and most fascinating takeaway for me is that if all we have now
entry to is DOMContentLoaded (or we aren’t already utilizing one thing extra
appropriate), then we do even have some visibility on app state and
availability. If you’re utilizing defer or kind=module, then DOMContentLoaded
is likely to be extra helpful to you than you realise.

Again to Work

I discussed beforehand that the DOMContentLoaded occasion fires as soon as all
deferpurple JavaScript has run, which implies that we may probably be
trickling performance all through the whole time between domInteractive and

In my consumer’s case, nonetheless, the location is totally nonfunctional till the
final file (response 133 within the waterfall) has efficiently executed. In
truth, blocking the request for file 133 has the very same impact as disabling
JavaScript completely. This implies the DOMContentLoaded occasion for them is an
nearly actual measure of when the app is out there. Because of this monitoring
and enhancing DOMContentLoaded could have a direct correlation to an improved
buyer expertise

Bettering DOMContentLoaded

Provided that DOMContentLoaded marks the purpose at which all synchronous HTML and
JavaScript has been handled, and all deferpurple JavaScript has been fetched
and run, this leaves us many various alternatives to enhance the metric: we
may scale back the scale of our HTML, we may take away or scale back costly
synchronous JavaScript, we may inline small scripts to take away any community
price, and we are able to scale back the quantity of deferpurple JavaScript.

Additional, as DOMContentLoaded is a milestone timing, any time we are able to shave from
previous timings needs to be realised in a while. For instance, all issues being
equal, a 500ms enchancment in TTFB will yield a 500ms enchancment in subsequent
milestones, akin to First Contentful Paint or, in our case, DOMContentLoaded.

Nevertheless, in our case, the delta between domInteractive and
DOMContentLoaded was 8.681s, or about 70%. And whereas their TTFB definitely does
want enchancment, I don’t suppose it might be the best place to spend
time whereas tackling this explicit drawback.

Nearly all of that 8.7s was misplaced to queuing and fetching that sheer variety of
bundles. Not essentially the scale of the bundles—simply the sheer amount of
information that want scheduling, and which every carry their very own latency price.

Whereas we haven’t labored out the candy spot for this mission, as a rule,
a smaller variety of bigger bundles would normally obtain a lot quicker than many
tiny ones:

My recommendation on this case is to tweak their construct to output possibly 8–10 bundles and
re-test from there. It’s essential to stability bundle dimension, variety of bundles,
and caching methods, but it surely’s clear to me that the difficulty right here is overzealous

With that carried out, we must always have the ability to enhance DOMContentLoaded, thus having
a noticeable influence on performance and subsequently buyer expertise.

DOMContentLoaded has proved to be a really, very helpful metric for us.

Did this assist? We are able to do far more!



Please enter your comment!
Please enter your name here

Most Popular

Recent Comments