Skip to content

Byte attribution of a served product snapshot: inlined CSS is 13%, attributes are 75% #147

Description

@harper-joseph

Summary

A served product snapshot was reported as having a low text-to-HTML ratio, attributed to the CSS flattenShadowDom inlines for the review widget. Measured against a real served page, that attribution is wrong: inlined CSS is 13.4% of the document and attributes are 74.7%. Deleting every byte of injected styling moves the ratio 2.66% → 3.07%.

This issue records the byte attribution so the numbers aren't re-derived, and separates the levers that are provably safe from the one that isn't.

Method

One review-heavy product page (desktop), read from the serving cache via /prerender_admin/page-content, so these are the bytes a crawler receives. Rendered by browser v1.21.0 with flattenShadowDom, pruneUnmatchedCss and minifyInlineCss all on — i.e. the CSS below is already the residue after ~74% of style rules were pruned and the rest CSSOM-minified.

Class-token reference counts were computed against the inline <style> blocks and the four external stylesheets the snapshot still links. Measuring against inline CSS alone overstates the unreferenced total by ~6× (359 KB vs the true 61 KB) — the page keeps a 588 KB external utility stylesheet, so most tokens are referenced from a sheet that is not in the document.

Where 1,468,123 bytes go

bytes % of doc
class attributes 451,713 30.8%
inlined <style> CSS 197,160 13.4%
style= attributes 140,957 9.6%
href / src / srcset 214,751 14.6%
SVG d path data 81,344 5.5%
aria-label 52,604 3.6%
tag names + brackets 55,700 3.8%
ld+json 17,486 1.2%
visible text 39,078 2.66%

10,137 elements. 74.7% of the document is HTML attributes.

The review widget

The flattened review container is 385,482 B — 26.3% of the page — carrying 7,707 B of review text (2.0% of itself). Eight review cards; one card is 22,446 B / 235 elements for 956 B of text.

inside the review container bytes % of block
inlined <style> (host sheet + nested shadow hosts) 152,624 39.6%
SVG markup (d alone = 62,473) 108,954 28.3%
class 60,930 15.8%
review text 7,707 2.0%

Two candidates measured and rejected

  • Deduplicating identical shadow stylesheets across hosts. 9 distinct sheets across 10 hosts; total bytes sitting in duplicate copies: 196 B. Not a lever.
  • minifyInlineCss on the flattened blocks. Already CSSOM output, so re-emission is a no-op — consistent with the existing docstring.

Levers that remain, with sizes

  1. Repeated SVG path data — ~64,093 B (4.4% of the document), concentrated in the review block. 193 d occurrences, only 61 distinct; a single 1,033-byte path appears 45 times (46,485 B). Hoisting exact-duplicate path data into <defs><symbol> + <use> renders identically. No such pass exists in postProcess today.
  2. Class tokens no surviving rule references — 61,277 B page-wide, 34,467 B inside the review block. Same soundness argument that already licenses pruneUnmatchedCss: with stripScripts on, nothing can add a class after serialization, so a token no sheet mentions is inert. Must be evaluated against external sheets, not just inline ones.
  3. A repeated background-image URL in style= attributes — ~93,000 B. One sprite URL repeated 489 times across style= attributes. Origin-authored, not introduced by post-processing.

Cloaking: what actually carries risk here

Worth stating explicitly, because "fidelity" has been used to decline byte-cutting levers that do not actually implicate it.

Cloaking is presenting different content to users and crawlers in order to manipulate rankings. The unit is content — text, links, images, prices, availability, structured data — not markup. Removing an attribute no rule reads, deduplicating identical SVG path data, or pruning a rule that matches nothing changes zero content: a user and a crawler still perceive the same page. Levers 1–3 are outside the policy, and rendering-fidelity checks (paintParity, geometry) are the right bar for them for a different reason — Google indexes the rendered DOM and devalues content it determines is not visible, so a layout regression costs indexing quality on its own merits.

The risk is asymmetric, and it runs the other way: revealing content to a crawler that a user does not see is the cloaking-shaped move. That is exactly what dropping the review widget's CSS would do — the widget's own rules hold collapsed panels, modals and secondary UI at display:none, and stripping them injects text into the indexable page that no user is shown. It would also reintroduce the inbound-encapsulation defects fixed across v1.19–v1.21. Do not pursue "just drop the review CSS" for byte savings.

Note for answer/generative engines

Structured data carries aggregateRating (ratingValue, ratingCount) but no individual review objects. So the 385 KB review DOM is the only place individual review content exists in the snapshot — it is load-bearing for extraction, not redundant with the JSON-LD, and should not be treated as trimmable content. Emitting review items into the JSON-LD would serve extraction far better per byte than the DOM does, but that is an origin change, not a post-processing one.

Separately: text-to-HTML ratio is not a documented ranking signal — it is a third-party audit-tool metric. The documented hard constraint is the 1 MB soft cap a crawler applies to a document, and this page is at 1.47 MB. Sizing work against "get under 1 MB" is better defined than "raise a ratio", and levers 1–3 together are ~218 KB of the ~470 KB needed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions