forked from bchanot/claude
feat(seo-data,seo,geo): R2 — refuse to score what JS paints; no Playwright
Arbitrated (user): honest refusal on SPA, no headless browser. STEP 2 has recorded `RENDERING: SSR/SSG/SPA/hybrid` since forever and NOTHING ever acted on it. (The inventory claimed a "SPA severely limited" §0 flag compensated — it does not exist. Seventh subagent claim this branch has had to disprove.) So on a client-rendered site the FULL audit curls an empty shell, every meta/H1/JSON-LD check reports "missing", and the agent emits a page of false findings — plus a bundle that would "fix" tags which already exist. rendercheck reads the verdict from what the server SENT. package.json cannot tell a React SPA from a Next.js SSR app; the served bytes can. Stdlib only. The refusal is the point: - client-rendered → On-page is N/A, excluded from the weighted global, NOT scored zero. A zero says "your on-page is bad"; N/A says "we could not see it". Only one is true, and /client-handover gates on this number. - No bundle item may come from a live on-page check on such a site. - The report still says what IS auditable (robots, sitemap, headers, config, CrUX field data — real users, hydration included — GSC, legal, images) rather than returning an empty verdict. - geo refuses Content Shape the same way, and states the sharper fact: AI crawlers are WORSE at JS than Googlebot. GPTBot/PerplexityBot/ClaudeBot fetch HTML and largely do not execute it, so a client-rendered site is not merely unauditable by us — it is near-invisible to the engines this audit exists to serve. §0 alert + SSR/SSG as the top user action. Script/style text is not page text: a React shell with a fat inline window.__INITIAL_STATE__ measures 7 chars. Without that skip a 200 KB bundle reads as a rich page — the detector would fail exactly where it matters. Verified on both extremes, not just the happy path: zenquality 7650 chars/1 h1/9 jsonld and lavageangels356 13973/1/1 → server-rendered, no warning; a Vite/React shell fixture → client-rendered, 7/0/0, warned. seo-data 136 -> 144 pass, 0 fail; full suite green; shellcheck + py_compile clean.
This commit is contained in:
@@ -515,6 +515,17 @@ PRIORITY ACTIONS : <top 3-5>
|
||||
|
||||
## STEP 8 — CONTENT SHAPE FOR AI `[both]`
|
||||
|
||||
**Rendering gate first (R2).** `bash ~/.claude/lib/seo-data/fetch.sh
|
||||
rendercheck --url "https://$DOMAIN/"`. Verdict `client-rendered` → Content
|
||||
Shape is `N/A — content not in served HTML`, excluded from the weighted
|
||||
global, never scored zero. And say the thing that actually matters here: AI
|
||||
crawlers are **worse** at JS than Googlebot is. GPTBot, PerplexityBot and
|
||||
ClaudeBot fetch HTML and largely do not execute it, so a client-rendered site
|
||||
is not just unauditable by us — it is close to invisible to the engines this
|
||||
whole audit targets. That is a §0 alert and the top user action (SSR/SSG),
|
||||
not a schema tweak.
|
||||
Site-wide axes (crawler policy, llms.txt) are unaffected: those are files.
|
||||
|
||||
Load: `~/.claude/agents/resources/content-shape-for-ai.md`
|
||||
|
||||
Sample 5-10 key pages (homepage + top service/blog pages). For each:
|
||||
|
||||
@@ -472,6 +472,48 @@ Fetch rendered HTML. Extract and analyze:
|
||||
|
||||
## STEP 5 — ON-PAGE AUDIT `[both]`
|
||||
|
||||
### Rendering gate — run this BEFORE anything else in STEP 5 (R2)
|
||||
|
||||
```bash
|
||||
bash ~/.claude/lib/seo-data/fetch.sh rendercheck --url "https://$DOMAIN/"
|
||||
```
|
||||
|
||||
STEP 2 has always recorded `RENDERING: SSR/SSG/SPA/hybrid` and nothing ever
|
||||
acted on it. This is the rule that does. The verdict comes from what the
|
||||
server actually sent, not from reading package.json — a React SPA and a
|
||||
Next.js SSR app are indistinguishable there.
|
||||
|
||||
**`verdict: client-rendered` → REFUSE to score the On-page axis.** Do not
|
||||
score it low. Do not score it at all:
|
||||
- On-page → `N/A — content not in served HTML (client-rendered)`. Redistribute
|
||||
nothing; a missing axis is not a zero.
|
||||
- Every curl-based meta/H1/JSON-LD check would report "missing" against a site
|
||||
that may be perfectly correct once hydrated. Those are FALSE findings, and
|
||||
a bundle built on them would "fix" meta tags that already exist.
|
||||
- **No bundle item may come from a live on-page check on this site.** Source
|
||||
greps still apply — the JSX carries the tags — but you cannot tell which
|
||||
route renders what, so treat them as inventory, not as per-page findings.
|
||||
- `linkgraph` will refuse too (`no_links_in_html`) — the same blindness. Do
|
||||
not work around either refusal.
|
||||
|
||||
Still fully auditable, and worth saying so rather than returning an empty
|
||||
report: robots.txt, sitemap.xml, HTTP headers, redirects, `.htaccess` /
|
||||
framework config, CWV via CrUX (field data is real-user, hydration included),
|
||||
GSC queries + index coverage, legal pages, image weights.
|
||||
|
||||
**`verdict: partial`** → shell plus an SSR'd head, or a genuinely thin page.
|
||||
Score what is present, name what is not, and say which of the two you think
|
||||
it is.
|
||||
|
||||
**§0 line, mandatory when not server-rendered:**
|
||||
`Rendering: client-rendered — On-page NOT scored (content absent from served
|
||||
HTML). Global score excludes it. Fix: SSR/SSG (CLAUDE.md: public sites are
|
||||
never SPAs).`
|
||||
|
||||
This is the honest half of the R1/R2 call: we do not render JS (no Playwright,
|
||||
no Chromium), so we do not pretend to see what JS paints. Refusing is the
|
||||
finding.
|
||||
|
||||
**Record the denominator BEFORE sampling.** This step samples; the report
|
||||
says "audit". On a 500-page site a 12-page sample is 2.4% — the On-page score
|
||||
is an extrapolation from it, and the reader cannot know unless you print it.
|
||||
@@ -885,6 +927,14 @@ owns them (0-100 + Observatory/SecurityHeaders/SSL Labs). Run /harden
|
||||
Name what you saw. An omission has to stay legible — the same reason
|
||||
COVERAGE is mandatory in STEP 9.
|
||||
|
||||
**On-page axis note (R2).** `rendercheck` verdict `client-rendered` → this
|
||||
axis is `N/A — content not in served HTML`, excluded from the weighted global,
|
||||
NOT scored zero. A zero says "your on-page is bad"; N/A says "we could not
|
||||
see it", and only one of those is true. Renormalise the remaining weights over
|
||||
the axes actually scored and say so on the SEO GLOBAL line. The code ceiling
|
||||
must state that no code fix raises an axis we did not measure — the unlock is
|
||||
SSR/SSG, and that is a user action, not a bundle item.
|
||||
|
||||
**Off-page axis note (I1).** Score ONLY the unlinked brand mentions
|
||||
gathered in STEP 6 (`web_search "<business-name>" -site:<domain>`).
|
||||
Backlink profile and domain authority have NO data source here — no index,
|
||||
|
||||
Reference in New Issue
Block a user