Our 79 pages contain 3,083 divs and 3,053 script tags, and after hydration the scripts win

2026-11-15来源 dev.toEnglish

Lighthouse has an audit called "Avoid an excessive DOM size" that starts complaining at 1,500 elements, 32 levels of depth or 60 children on one parent. I have never once seen it fire on our site, so I never thought about the shape of the DOM at all. This week I measured it properly: every element on all 79 pages in the sitemap, the depth of every branch, every text node and every comment node.

const all = [...document.querySelectorAll('*')]; // elements let max = 0; (function walk(el, d) { if (d > max) max = d; for (const c of el.children) walk(c, d + 1); })(document.documentElement, 1); const tw = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_COMMENT); 

Size

elements, all 79 pages 23,767
of those, inside <body> 19,230
elements per page 117 min, 259 median, 929 max
body elements per page 65 min, 202 median, 862 max
distinct element names site-wide 55
attributes 38,350 (1.61 per element)
text nodes with content 9,001
characters in them 382,466 (42.5 per node)
comment nodes 1,276

Against Lighthouse's thresholds: 0 pages over 1,500 elements, 0 over depth 32, and 2 pages with a parent holding more than 60 children. The heaviest page on the site, the homepage, is 62 per cent of the way to the warning. The lightest, the cookie policy, is 117 elements, which is small enough to read in the inspector without scrolling.

The ratio I find most useful is at the bottom of that table: 5.03 elements per 100 characters of text. A content site is in the business of turning markup into reading, and that is the exchange rate.

The tag histogram, and a tie

Counted in the HTML we actually serve, all 79 pages:

element count
div 3,083
script 3,053
a 2,699
p 2,431
span 2,307
meta 1,932
li 1,254
path 1,004
link 866
svg 638
section 556
h2 552
nav 534
h3 439
dt / dd 233 each
button 96
td 92

The ratio of script tags to divs is 0.99. On a static marketing site, for every structural div we serve, we serve a script tag.

Exactly one of those 3,053 is ours, a single <Script> in the root layout. The rest are the framework: the chunk loaders, and above all the React Server Components flight payload, which arrives as a long series of inline push calls rather than as one blob. 200 of them are application/ld+json structured data, which is markup wearing a script costume.

And in the live DOM, after hydration, script overtakes div: 3,134 against 3,083, because next/script injects one more element per page once the load event has passed. The most common element on this site is a script element, and nobody chose a single one of them.

Check it yourself on any page:

document.querySelectorAll('script').length // 43 on /faq document.querySelectorAll('div').length // 36 on /faq 

The FAQ is 268 elements and carries 43 script tags against 36 divs.

Depth: 78 pages agree, one does not

max depth pages
9 to 11 4
12 50
13 17
14 6
15 1
30 1

50 of 79 pages bottom out at exactly 12 levels, which is what a consistent layout component plus a content renderer produces. One page goes to 30, and here is its deepest branch:

html > body > div > main > section > div > div > div > div > div > div > div > div > div > div > div > div > div > div > div > div > div > button > svg > g > g > g > g > g > ellipse 

Seventeen divs, then a button, then five nested g elements, then an ellipse. That ellipse is part of the mascot on the homepage, drawn as inline SVG inside the demo panels. SVG groups account for 6 of the 30 levels and the illustration is not the problem; the 17 divs in front of it are.

It is not wrapper soup, except where it is

The metric I expected to be bad. A single-child wrapper is an element with exactly one element child and no text of its own, which is the unit of accidental div nesting:

single-child wrappers 1,689 (7.1 per cent of all elements)
of those, a div whose only child is a div 111
longest unbroken wrapper chain, 78 pages 2
longest unbroken wrapper chain, homepage 9

78 of 79 pages never stack more than two wrappers. The homepage stacks nine, in the same component as the depth-30 branch. That is the third measurement this week to land on those demo panels: they also hold four phantom buttons in the tab order and three extra h1 elements. One component, three unrelated audits, same answer.

The generic element share tells the same story from the other side:

page div + span share of body elements
/legal/privacy 10.1 per cent
/features 14.2 per cent
/solutions 15.1 per cent
site-wide 28.0 per cent
busiest question page 37.7 per cent

A privacy policy is 90 per cent semantic markup because it is genuinely just headings and paragraphs. The card-heavy pages are a third divs and spans, which is the honest cost of a card.

One more share worth knowing: 1,835 body elements, 9.5 per cent of all of them, are inside an svg. Paths, groups, ellipses. That is 19 times more elements than all our buttons put together, and it is the single largest category of non-semantic markup on the site. Icons are not free, they are just cheap in bytes.

What React leaves in the document

1,276 comment nodes, 16.2 per page, in exactly three flavours:

content count what it is
a single space 960 React's text separator, the <!-- --> between adjacent text expressions
$ 158 opening marker of a Suspense boundary
/$ 158 closing marker of the same

Two Suspense boundaries per page, opened and closed, on every one of the 79. They are invisible, they cost about 4 bytes each, and they are the only trace in the delivered document of the streaming machinery that put it there.

The widest parent on every page is the head

I looked for the element with the most children, expecting a grid or a list. It is <head>, on all 79 pages, with 49 to 65 children. The widest parent inside <body> is usually a div with 6 children or an ol with 12, and the biggest one on the whole site is a dl with 30 children on the round ideas guide.

So the most crowded part of this site's structure is its metadata: 1,932 meta tags, 866 link tags, 79 titles and all those script tags, against a body whose busiest node is a definition list.

What I am changing

  1. Flatten the demo panel wrappers. Nine levels of single-child div is a layout written by accretion, and the same component is now implicated in three separate audits.
  2. Nothing else. 259 elements on a median page, 12 levels deep, 7.1 per cent wrappers and 28 per cent generic elements is a healthy document, and I would not have been able to say that before counting.

If you want the one-line version of this whole post for your own site, it is this:

(() => { let m = 0; (function w(e, d) { if (d > m) m = d; for (const c of e.children) w(c, d + 1); })(document.documentElement, 1); return { elements: document.querySelectorAll('*').length, depth: m, scripts: document.querySelectorAll('script').length }; })() 

Run it on a guide, then on your own app. If the script count is anywhere near the div count, you are serving a framework, not a page, and that is worth knowing either way.

原文:https://www.a8q9.cn/en/article/our-79-pages-contain-3-083-divs-and-3-053-script-tags-and-after-hydration-the-sc-9994b7.html

相关文章