Build1 publisher3 min readPublished
Googlebot does execute JavaScript, but the shell it reads first was one document served 741 times, and Search Console scored the site on that document rather than on the rendered one.
The Engineer · Build desk
Follow any of these and your For You feed starts watching them — no settings page required.
Compiled by The EngineerSomething wrong?How this is made
Do the subtraction on the Search Console figures. 116 of 741 URLs had earned a single impression in 28 days [2], which leaves 625 URLs, 84.3 percent of the set, with none [18].
The writeup's explanation is that rendering is a separate, budgeted queue, and that a domain with little authority does not get much of that budget [4]. That inference is the author's; the post cites no Google documentation for it, and its own evidence is that several pages were indexed, so the renderer did run [4]. The duplicate-content half of the argument needs no such assumption. Before JavaScript ran, every one of the 741 URLs was the same 3,780 bytes, with the same title, no h1 and no body text [1]. Whatever the crawler concludes those pages are about, it concludes it from one document served 741 times [5].
The repair injects a real head into each generated HTML file after build and leaves the body exactly as the SPA served it [6]. Head-only was the load-bearing decision: no hydration flash, no static copy drifting away from what users see, and static markup that is a subset of the rendered markup rather than a different page, which is where the cloaking question is settled [7]. Values are read from the same source the React page reads, and a title that lives as a literal inside a component is extracted from that component's source rather than retyped [8].
Then react-helmet. On its first commit it removes every tag carrying `data-rh` and re-inserts its own, while tags without the attribute survive untouched [9]. So the static description in `index.html` stayed, Helmet appended its own, and pages shipped with two descriptions, the static one first in the head, which is the one crawlers read [10]. Read the attribute as a delete list that Helmet then re-populates, not as a marker of ownership: they marked `og:image`, Helmet does not re-insert it, and the share image vanished everywhere [11].
Their own injector failed for an unrelated reason. The replace was a regex keyed on attribute order, `<meta name="description"`, while the shell writes `data-rh="true"` first, so it matched nothing and said nothing [12]. Match on a tag containing the key attribute, never on the order the attributes happen to sit in [12].
The build bug is the one worth stealing. The prerender step takes its URL list from `sitemap.xml` [13], and `npm run build` ran vite, redirects, the crawl index and prerender, with the sitemap generator sitting outside as a command somebody had to remember [14]. A new route therefore deployed absent from the prerendered set, with no error, no warning, and exit 0, wearing the generic shell head [15]. Two builds went into suspecting the code before anyone suspected the input [16].
The same class of failure showed up in a dead ternary, `const key = m ? m[1] : route === '/some-route' ? null : null`, where both branches return null and the route fell through to a generic fallback body [17]. It has a route name in it, so it reads in review like a handled special case, which is exactly how it survived [17].
For the numbers to transfer, two things have to be true of your build: your metadata mounts client-side, and your prerender step reads a manifest generated somewhere outside the build command. The diagnostic is one request per route with a Googlebot user agent and a diff of the bytes [1].
Ranked by verification strength, evidence, and original report placement.
Head-only was deliberate: no hydration flash, no risk of a static copy drifting out of sync with what users see, and nothing readable as cloaking because the static markup is a subset of the rendered markup rather than a different page.
Checked with a Googlebot user agent, every URL on the site returned the same 3,780-byte shell: same <title>, zero <h1>, zero body text. The homepage, a blog post and a product page were byte-identical before JavaScript ran.
Search Console reported that of 741 URLs, 116 had earned a single impression in 28 days.
A landing page that had been live for five months was still reported by Search Console as "URL is unknown to Google".
The fix was a post-build script that injects a real <head> into each generated HTML file (title, description, canonical, robots, Open Graph, Twitter), with the body left exactly as the SPA served it.
Every injected value is read from the same source the React page reads; where a title is a literal inside a component, the script extracts it from that component's source rather than having anyone retype it.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Precise numbers, one private console
Two kinds of evidence sit side by side here and they are not equally strong. The mechanics are checkable by anyone: Helmet's data-rh delete-and-reinsert rule, a regex that fails on attribute order, a ternary with null on both branches. The quantities are not. The 3,780 bytes, the 741 URLs, the 116 impressions and the 309 canonicalised pages all come from one developer's own crawl and one Search Console property, published on a platform where the author is also the editor.
In production on exactly one site
The technique runs in one place, CVBooster, and the author is its operator. No repository, package or snippet is offered for reuse, no other team reports trying it, and the follow-up in the same post shows the approach being extended into a full body render rather than picked up elsewhere as it stood.
A working fix, with no recovery data to show for it
The headline is a build step that works, and the write-up never produces an after picture: there's no post-fix impression count or indexing change to place beside the 116-of-741 figure. The author supplies his own correction, which is why the gap is modest rather than wide. Head-only markup held for the pages the prerender step knew about and did nothing for the 309 URLs Google filed under the homepage canonical, and fixing those took rendering bodies after all.
The demo is the author's product
The closing paragraph sends readers to CVBooster as proof the output works, which is at once the honest way to show your work and a placement for a product the author sells. Set that against a self-publishing platform with no editorial layer and a set of numbers nobody else can pull, and the disclosure is doing most of the work of keeping this straight.
Internally consistent, externally unchecked
The account holds together and is specific in the way first-hand debugging usually is, including the parts that make the author look slow, such as two builds spent on the code before anyone looked at the stale input. What caps confidence is arithmetic on sourcing: a single publisher, domain and analytics account, and an argument about Google's render budget that rests on the author's inference rather than on documentation or logs.
build
React 19 puts the deleted head tags back before page.content() serializes them1 publisher
build
A build-tool swap that took 70 days and 166 files, and the build was the easy part1 publisher
build
A Nuxt catalog multiplies 169 base routes by four locales to fix its prerender list at 6761 publisher
build
A green check on the wrong surface: the DOM said Spanish, the served HTML said English1 publisher
Publishers with included, body-backed reporting in this cluster.
1 article · September 7, 2026