In Lighthouse kennt man eventuell die Warnung „Avoid an excessive DOM size“ gefolgt von der Anzahl der Elemente. Ich habe mal ein wenig getestet.

Screenshot von der Ergebnisseite eines Tests mit PageSpeed Insights, der eine Anmerkung von Lighthouse bezüglich der DOM-Größe zeigt.
Screenshot aus PageSpeed Insights: Avoid an excessive DOM size – 4818 elements

Test-Aufbau

Als Basis diente mir eine simple HTML-Seite mit einem ausschweifenden DOM in Länge und Tiefe, wobei im Beispiel <31-nested-DOM-Elements> stellvertretend für 31 weitere verschachtelte DOM-Elemente ohne eigenen Inhalt steht.

<body>
  <header>...</header>
  <article>
    <figure>
      <img>
      <figcaption>...</figcaption>
    </figure>
    <h1>...</h1>
    <h2>...</h2>
    <p>...</p>
  </article>

  <main>
    <section>
      <figure>
        <img>
        <figcaption>...</figcaption>
      </figure>
      <div>
        <31-nested-DOM-Elements>
          <p>...</p>
        </31-nested-DOM-Elements>
      </div>
    </section>

    <section>
      <figure>
        <img>
        <figcaption>...</figcaption>
      </figure>
      <div>
        <31-nested-DOM-Elements>
          <p>...</p>
        </31-nested-DOM-Elements>
      </div>
    </section>

    <section>
      <div>
        <31-nested-DOM-Elements>
          <p>...</p>
        </31-nested-DOM-Elements>
      </div>
    </section>

    (142 weitere identische Blöcke davon)

    <section>
      <figure>
        <img>
        <figcaption>...</figcaption>
      </figure>
      <div>
        <31-nested-DOM-Elements>
          <p>...</p>
        </31-nested-DOM-Elements>
      </div>
    </section>
  </main>
</body>

Dazu gab es im <head> ein kleines Inline-CSS ohne jegliche Verschachtelung oder Aufruf externer Ressourcen, eine externe CSS-Datei die mit Hilfe von <link media="print" onload="this.media='screen'"> mit geringer Priorität geladen wird. Das externe CSS gibt es in einer Variante ohne jegliche Verschachtelung im Selektor (0,223kb) und einmal mit Verschachtelung (0,284kb). Die Varianten erzeugen das selbe Stying.

Zusätzlich habe ich auch eine größere CSS-Datei (72kb) einer anderen Website testweise eingebunden (lokale Kopie), um einen eventuellen Einfluss eines umfangreicheren Stylesheets zu prüfen.

Des Weiteren gibt es im <head> noch ein Inline-JavaScript, dass im DOM alle <img>-Tags zählt und die Zahl am Ende auf der Seite ausgibt.

<script>
  document.addEventListener('DOMContentLoaded', function(event) {
    let imgSum = document.querySelectorAll('img').length;
    document.getElementById('imgsum').innerHTML = imgSum;
  });
</script>

Als Gegenpart zum HTML (538kb) habe ich dann noch Tests mit geändertem DOM durchgeführt. Bei einer Variante (genannt Simple), sind zwar 142 Blöcke geblieben, aber die DOM-Tiefe war drastisch reduziert.

Aus 143 Blöcken davon…

<section>
  <div>
    <31-nested-DOM-Elements>
      <p>...</p>
    </31-nested-DOM-Elements>
  </div>
</section>

…wurden 143 Blöcke hier von…

<section>
  <div>
    <section>
      <article>
        <p>...</p>
      </article>
    </section>
  </div>
</section>

…was zu einer HTML-Datei mit einer Größe von 131kb führte.

Zu guter Letzt gibt es noch eine HTML-Variante (genannt Simple Deep), die zwar die DOM-Tiefe erhalten hat, aber statt…

<section>
  <div>
    <31-nested-DOM-Elements>
      <p>...</p>
    </31-nested-DOM-Elements>
  </div>
</section>

(142 weitere identische Blöcke davon)

…gab es nur 34 weitere identische Blöcke davon.

Die vier im HTML eingebundenen Bilder sind alle das selbe Bild (je 1,5Mb JPEG) mit unterschiedlichem Namen.

Wie und was wurde gemessen?

Zur Vergleichbarkeit habe ich die Tests alle mit PageSpeed Insights durchgeführt. Notiert habe ich die Ergebnisse des Lab-Tests, also

  • FCP
  • Speed Index
  • LCP
  • TTI
  • TBT
  • CLS

Zusätzlich habe ich mit der Performance API gemessen, wie lange der Browser…

  • …für den <head>-Bereich braucht,
  • …für den <body>-Bereich braucht,
  • …für die vier Bilder braucht und
  • …für das Zählen der <img>-Tags im DOM braucht.

Zum Zeitpunkt meiner Messungen lief PageSpeed Insights immer mit Lighthouse 8.0.0 und einem Chrome 90.0.4430.97.

Da Lighthouse-Tests immer mal schwanken habe ich pro Seite und Variante 5 Tests durchgeführt und zum Vergleich den Median der Testreihe genommen.

Erkenntnisse

Spannend war zu sehen, dass Total Blocking Time (TBT) die mit Abstand am meisten schwankenste Metrik war, ohne dass irgendetwas auf der Seite geändert wurde. Beispielsweise lagen die Testergebnisse beim in der Tiefe reduzierten DOM mit dem verschachtelten CSS zwischen 40 ms und 1020 ms, der Median lag bei 580 ms.

JavaScript

Das Zählen der <img>-Tags durch das JavaScript blieb immer unter 1ms, aber auch hier traten bereits Unterschiede von bis zu 0,2ms auf. Das mag geringfügig erscheinen, aber wenn man die Einfachheit der Operation bedenkt und dass ein JavaScript-Task ab 50ms bereits als „Long Running Task“ gilt finde ich das schon bemerkenswerter, da die normale Website heutzutage natürlich weit aus mehr und anspruchsvollere DOM-Operationen durchführt als das hier in meinem Test der Fall war.

VarianteZeit zum Zählen der
<img>s in Millisekunden
DOM0,52
DOM – Simple0,46
mit verschachteltem CSS
DOM0,52
DOM – Simple0,41
DOM – Simple Deep0,33
mit umfangreichem CSS
DOM0,56
DOM – Simple0,36
DOM – Simple Deep0,37
Legende: DOM = lang und tief; Simple = lang, aber keine Tiefe; Simple Deep = kurz, aber tief

Body lesen

Mittels der Performance API habe ich gemessen, wie lange der Browser vom Anfang des <body> zu seinem Ende braucht. Auch hier ist im Prinzip nicht verwunderlich, dass er länger braucht, aber es sind teils über 100ms, die sich dann auch in einem entsprechend verspäteten First Contentful Paint (FCP) nieder schlagen.

VarianteZeit für den <body>
in Millisekunden
FCP in Sekunden
DOM164,640,8
DOM – Simple74,390,7
mit verschachteltem CSS
DOM151,670,8
DOM – Simple64,190,7
DOM – Simple Deep41,550,7
mit umfangreichem CSS
DOM133,910,8
DOM – Simple60,060,7
DOM – Simple Deep40,350,7
Legende: DOM = lang und tief; Simple = lang, aber keine Tiefe; Simple Deep = kurz, aber tief

Fazit

Neben den hier gezeigten habe ich noch diverse andere Varianten ausprobiert. Beispielsweise die Bilder nicht mit decoding="async" zu laden, das letzte Bild explizit mit einem loading="lazy" zu laden oder auch nur ein Bild im First-View integriert zu haben und sonst gar keine. Diese Varianten hatten allerdings keinen Einfluss auf die Messungen, außer den offensichtlich erwartbaren, weshalb ich sie hier nicht weiter aufgeführt habe.

Festzuhalten bleibt, dass der Umfang des DOMs bereits bei einfachen Standard-Operationen des Browsers und der JavaScript-Engine einen messbaren Einfluss hat und es sich daher definitiv lohnen kann die Verschachtelungstiefe gering und die Menge der direkten Kind-Elemente niedrig zu halten.