Skip to content

SEO Phase 3/4 + fix broken Holders queries shipped in #226 - #227

Merged
buddies2705 merged 3 commits into
mainfrom
docs/seo-phase3-and-holders-audit-fixes
Jul 30, 2026
Merged

SEO Phase 3/4 + fix broken Holders queries shipped in #226#227
buddies2705 merged 3 commits into
mainfrom
docs/seo-phase3-and-holders-audit-fixes

Conversation

@buddies2705

Copy link
Copy Markdown
Contributor

Follow-up to #226 (merged). Three commits that were not included in it.

Read the first section before merging — this PR repairs queries that #226 already shipped to main.

🔴 Fixes defects already live in main

2a583a0c (merged via #226) migrated 16 TokenHoldersHolders sites. That commit's message claimed the migration was verified; in fact only 1 of the 16 had been executed. Running the rest afterwards showed 7 failures — the migration renamed the cube and rewrote its arguments but left selection sets and arguments that do not exist on Holders.

Defect Files Fix
snapshot: argument pepe-api date:. Holders accepts only where / orderBy / limitBy / limit / date.
BalanceUpdate { … } selection set rwa-api, stablecoin-payments-api ×3, ai-agent-base-data That sub-object belongs to the old TokenHolders cube. Mapped CountUpdateCount, FirstDateFirstChangeTime, LastDateLastChangeTime and merged into the existing Balance block.
InCount / OutCount / InAmount / OutAmount same No Holders equivalent. Removed.
Jammed braces }Balance { and }Holder { stablecoin-payments-api, rwa-api Formatting bugs from the repair script itself. Valid GraphQL, wrong in generated docs.
Stale prose pepe-api Told readers to "change the snapshot date".

Two prose claims were over-promising. They advertised "including inflows, outflows" and "in/out counts" — precisely the fields with no Holders equivalent. Both now state what the query actually returns and point at the Transfers cube for directional totals.

Mitigating context: these queries were already dead before the migration, because EVM.TokenHolders itself fails on both realtime (no table can query TokenHolder) and archive (Database eth does not exist). Nothing working regressed. But they were left broken while looking migrated, which is worse than leaving the deprecation visible.

Also corrected: 2 of the 16 sites sit inside an HTML-commented region of pepe-api.md (lines 681–800, one of 6 such regions), so they are not reader-visible. Live migrations are 14, not 16.

Phase 3 — titles and descriptions

Every indexable page now passes both length checks:

Metric Before After
Sitemap pages with title > 60 21 0
Sitemap pages with description > 160 5 0
Duplicate titles among the 573 0
Duplicate descriptions among the 573 0

Titles. Adopted Google's own framing on the three pages where it discarded ours and synthesised a better one (Orca, Four Meme, Aerodrome — the last also fixes a plain bug, the title read Base Aerodrome Base API). Shortened the two genuinely over-length Robinhood titles.

Did not adopt the other ten SERP rewrites: those are cases where Google stripped our chain prefix (Ethereum Uniswap APIUniswap API) for one query in one country. Deleting a real qualifier on that evidence risks more than it gains, and those titles were already well within limits.

New plugin — plugins/title-suffix-trim.js. Docusaurus composes <title> as ${frontMatter.title} | ${siteConfig.title} with no per-page opt-out. That suffix costs exactly 16 chars, so a frontmatter title must be ≤44 to render under 60 — which would mean deleting keywords purely to make room for branding. Branding stays as "Bitquery Docs", so this postBuild pass drops the suffix only from titles rendering over 60 chars. Trims 175 of 1050 built titles.

Deliberately narrow: <title> only — og:title and twitter:title keep the full branded form, which is what social cards should show. Only the exact suffix, anchored at the end. Never leaves an empty title. Verified it does not desync the JSON-LD: tech-article-jsonld derives headline from frontmatter, so it now matches the trimmed <title> exactly.

This turned out to be a dependency rather than a nice-to-have — enriching the three titles pushed them to 62/66/67 chars, so without the plugin the change would have traded one warning for another.

Descriptions. Trimmed the 4 over 160. Hand-wrote 15 for the highest-traffic pages still carrying a templated description, each grounded in that page's actual sections rather than boilerplate, prioritised by real Ahrefs traffic rather than guesswork. Rewrote two generated-index descriptions under 110 (Crypto Price API and Metrics hubs — both have explicit slugs, so unlike /docs/category/* auto-slugs they are intentional hub URLs).

Scale correction. The templated-description problem is larger than previously reported. An earlier regex covered only 3 of the 8 boilerplate tails and gave 145. The true figure was 282 files carrying description: "<exact title>: <one of ~8 tails>"; 267 remain. Ahrefs never flags these because each passes the length check individually.

Left alone deliberately: docs/chinese.md (42 CJK characters carry far more than 42 Latin ones — Ahrefs' character rule miscounts CJK) · 34 /docs/category/* stubs (verified to rank for exactly one zero-volume keyword across all 34) · three pages at 104–109 chars (marginally under an arbitrary threshold, not flagged by Ahrefs, not padding for a counter) · 26 pages still over 60 after trimming, all /docs/graphql-reference/, which is Disallowed in robots.txt and excluded from the sitemap.

Phase 4 — V1 handoff

Deliverable is V1_DOCS_SEO_HANDOFF.md, gitignored alongside v1_to_v2_plan.md per this repo's convention for internal planning docs. All findings verified live.

Priority Finding
P0 All 409 /v1/** pages emit canonical → /v1/. Verified across 5 unrelated pages — identical tag. Self-inflicted de-indexing of the entire legacy set.
P1 One shared 108-char title and one shared 205-char description across all 409. Sampling 4 pages yields exactly one distinct title string.
P2 /v1/sitemap.xml (409 URLs) undeclared in robots.txt; /v1/ has 624 internal inlinks but 0 entries in the main sitemap; /v1/blog is listed yet returns 301.
P3 GA4 double-counts. V1 loads gtag.js for G-ZWB80TDH9J and GTM-5GC69JH6; V2 loads only GTM. Any V1-vs-V2 traffic comparison is ~2× wrong on the V1 side.

Includes a sequencing constraint (do not declare the V1 sitemap until P0 lands, or we advertise 409 de-indexed URLs), a 5-point verification checklist with a runnable one-liner, and the tax-calculator defects.

Verification

  • yarn build passes; build output confirmed to reflect the changes, not a cache hit
  • scripts/check-links.mjs: no link errors (533 pages, 1295 routes, strict)
  • 44 true Holders( call sites, 0 remaining invalid arguments or unknown fields
  • 573/573 indexable pages carry og:type and og:site_name
  • No Solana BalanceUpdates line was touched — verified against the diff. Solana has no Balances cube and its BalanceUpdates is not deprecated.
  • Pumpfun cluster: 22 distinct keywords, none claimed by two pages
  • The API token never entered tracked files or commit history

Verification limit, stated plainly: this session exhausted the account's API query points, so 36 extracted Holders queries could not be re-executed. 13 of the 20 run before the quota died passed; the two confirmed defect classes were verified fixed by targeted parsing rather than execution. Worth re-running when points reset — note the token-holder-api.mdx sites target USDT, which hits the large-token timeout documented in docs/cubes/balances-cube.md and may need a balance floor to return at all.

Needs an owner outside this PR

  1. Eight pages claim APIs were removed that still work. EVM.BalanceUpdates ("removed 15 June 2026") and Tron.BalanceUpdates ("removed 18 July 2026") both still execute — EVM.BalanceUpdates returned data stamped 2026-07-30T11:38:59Z. The notices are right about TokenHolders and wrong about both BalanceUpdates variants. Not edited, because only the API team can say whether removal was reverted, delayed, or the notices were written ahead of a plan.
  2. Tron.Holders aggregate statistics return wrong valuessum(of: Balance_Amount) and median both 0 for a token whose top holder holds 18M. The equivalent EVM query is correct (verified on PEPE: total / holders matched average exactly). Tron-specific; deliberately undocumented until confirmed.
  3. pepe-api.md has 6 HTML-commented regions (170–265, 309–351, 353–392, 502–548, 610–651, 681–800) — a large share of an indexable page sitting dead. Someone should decide restore vs delete.
  4. robots.txt/v1/sitemap.xml is ready but blocked on V1 P0.

🤖 Generated with Claude Code

Gaurav agarwal and others added 3 commits July 30, 2026 17:44
…de-template top pages

Phase 3 of the Ahrefs remediation. Every indexable page now passes both length
checks: 0 of 573 sitemap pages have a title over 60 chars or a description over
160 (previously 21 and 5).

Titles
- Adopted Google's own framing on the three pages where it discarded ours and
  synthesised a better one: Orca DEX API, Four Meme API, Aerodrome Finance API.
  The last also fixes a plain bug -- the title read "Base Aerodrome Base API".
- Shortened two over-length titles: robinhood-transactions-receipts-api (71 ->
  37 frontmatter chars) and robinhood-token-holders-api (61 -> 53).
- Did NOT adopt the other ten SERP rewrites. Those are cases where Google
  stripped our chain prefix ("Ethereum Uniswap API" -> "Uniswap API") for a
  single query in a single country. Deleting a real qualifier on that evidence
  risks more than it gains, and those titles were already well within limits.

New plugin: plugins/title-suffix-trim.js
Docusaurus composes <title> as `${frontMatter.title} | ${siteConfig.title}` with
no per-page opt-out. That suffix costs exactly 16 chars, so a frontmatter title
must be <=44 to render under 60 -- which would mean deleting keywords purely to
make room for branding. Branding is staying as "Bitquery Docs", so this postBuild
pass drops the suffix only from titles that render over 60 chars. Trims 175 of
1050 built titles. Deliberately narrow: <title> only (og:title and twitter:title
keep the full branded form, which is what social cards should show), only the
exact suffix, and never leaves an empty title.

Descriptions
- Trimmed the 4 over 160: three Robinhood pages and the homepage.
- Hand-wrote 15 descriptions for the highest-traffic pages that still carried a
  templated one, each grounded in that page's actual sections rather than
  boilerplate. Prioritised by real traffic from Ahrefs top-pages rather than
  guesswork: solana-photon-api (102), Solana-Raydium-DEX-API (38), intro (20),
  pancake-swap-api (16), token-supply-cube, token-holder-api, internal-
  transactions, Solana-DexPools, base-dextrades, OpenBook, Phoenix, Lifinity,
  matic-balance-api, sunswap-api, uniswap-api.
- Rewrote two generated-index descriptions that were under 110: the Crypto Price
  API and Metrics hubs (both have explicit slugs, so unlike /docs/category/*
  auto-slugs they are intentional hub URLs).
- Fixed a 167-char description introduced in the previous commit.

Scale correction: the templated-description problem is larger than earlier
reported. A narrower regex counted only 3 of the 8 boilerplate tails and gave
145. The true figure was 282 files carrying `description: "<exact title>: <one
of ~8 tails>"`; 267 remain. Ahrefs never flags these because each passes the
length check individually.

Left alone deliberately:
- docs/chinese.md (42 chars): 42 CJK characters carry far more meaning than 42
  Latin ones. Ahrefs' character rule miscounts CJK.
- 34 /docs/category/* stubs: verified to rank for exactly one zero-volume
  keyword across all 34.
- Three pages at 104-109 chars: marginally under an arbitrary threshold and not
  flagged by Ahrefs. Not padding for a counter.
- 26 pages still over 60 chars after trimming are all /docs/graphql-reference/,
  which is Disallow'd in robots.txt and excluded from the sitemap.

Phase 4 deliverable is V1_DOCS_SEO_HANDOFF.md, gitignored alongside
v1_to_v2_plan.md per this repo's convention for internal planning docs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Self-audit of the previous commit's TokenHolders -> Holders migration. I claimed
16 sites migrated having executed only one of them. Running the rest showed 7
failures: the migration renamed the cube and rewrote its arguments but left
selection sets and arguments that do not exist on Holders.

Fixed:
- `snapshot:` -> `date:` (pepe-api). Holders accepts only where / orderBy /
  limitBy / limit / date.
- `BalanceUpdate { ... }` selection sets removed from Holders blocks
  (rwa-api, stablecoin-payments-api x3, ai-agent-base-data). That sub-object
  belongs to the old TokenHolders cube. Mapped the fields that have an
  equivalent -- Count -> UpdateCount, FirstDate -> FirstChangeTime,
  LastDate -> LastChangeTime -- and merged them into the existing Balance block.
- InCount / OutCount / InAmount / OutAmount have NO equivalent on Holders, so
  they are gone. Two prose claims promised exactly that data ("including
  inflows, outflows", "in/out counts"), so both now say what the query actually
  returns and point at the Transfers cube for directional totals.
- Stale prose telling readers to "change the `snapshot` date".

These queries were already broken before the migration, since TokenHolders
itself fails on both realtime and archive -- so nothing working regressed. But
they were left broken while looking migrated, which is worse than leaving the
deprecation visible.

Audit results on the rest of the change set:
- 44 true Holders( call sites, 0 remaining invalid arguments or unknown fields.
- 573/573 indexable pages carry og:type and og:site_name.
- 0 duplicate titles and 0 duplicate meta descriptions among the 573.
- 0 titles over 60 chars and 0 descriptions over 160 in the sitemap.
- No Solana BalanceUpdates line was touched (verified against the diff);
  Solana has no Balances cube and its BalanceUpdates is not deprecated.
- Pumpfun cluster: 22 distinct keywords, none claimed by two pages.
- No http:// internal links reintroduced; alt text intact on all 4 images.
- The API token never entered tracked files or commit history.
- title-suffix-trim does not desync JSON-LD: tech-article-jsonld derives
  headline from frontmatter, so it now matches the trimmed <title> exactly.

Verification limit: further execution checks are blocked -- this session
exhausted the account's query points, so 36 extracted Holders queries could not
be re-run. The two confirmed defect classes were verified fixed by targeted
parsing instead.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Second formatting defect from the same repair script: it merged adjacent
Balance blocks but not `}Holder {`, leaving the closing brace jammed against
the next field. Valid GraphQL (whitespace is insignificant) but wrong in
generated docs. Reordered to Holder then Balance.

Also noted while showing the diffs: both TokenHolders sites in pepe-api.md sit
inside an HTML-commented region (lines 681-800 of 6 such regions), so those two
of the sixteen are not reader-visible. Live migrations are 14, not 16.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@buddies2705
buddies2705 merged commit 964e02c into main Jul 30, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant