HarperFast / HarperFast/prerender-plugin

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

Abierto
#147 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
JavaScript
Estrellas
0
Forks
0
Merge medio
9 h 39 min
PR fusionados (30 d)
39

Descripción

## 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 `` 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.

Guía de contribución

Abrir la guía de contribución

Línea de trabajo

Start by reading the postProcess path and the existing pruneUnmatchedCss and minifyInlineCss behavior, then reproduce the byte breakdown from /prerender_admin/page-content with the stated browser v1.21.0 options. Compare the SVG, class-token, and repeated style-URL levers against external stylesheets and paint or geometry parity; do not remove the review widget CSS. Done requires a selected, measured optimization with unchanged rendered content and a clear path toward the 1 MB cap.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
javascript
Área
performance
Tipo de issue
Nueva funcionalidad
Dificultad
5/5
Tiempo estimado
Más de una semana
Estado de actividad
Activo
Claridad
Necesita aclaración
Aptitud para principiantes
35/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.