Skip to content

feat(seo): give the site one entity instead of one per page - #200

Closed
chaxus wants to merge 1 commit into
mainfrom
seo-entity-graph
Closed

feat(seo): give the site one entity instead of one per page#200
chaxus wants to merge 1 commit into
mainfrom
seo-entity-graph

Conversation

@chaxus

@chaxus chaxus commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Read apple.com's markup and took the part that transfers. Apple never
redefines "Apple": it names the node once as #organization and every page
after that writes manufacturer: { "@id": ... }. Same for #website and each
product's #brand.

We did the opposite. Every page emitted its own anonymous WebApplication and
SoftwareSourceCode, so 154 pages described 154 unrelated tools that happened
to share a name. That costs little in ranking and a lot in the machines that
answer questions about the site -- an assistant that reads three of our pages
should come away with one editor.

Organization, WebSite, WebApplication and SoftwareSourceCode now have fixed
@ids and appear on every page as references to each other; WebPage, FAQPage,
HowTo and BreadcrumbList get page-scoped ones. The app's url is the site root
everywhere -- a per-page url made each translation look like a separate
product -- and the language moved to WebPage.inLanguage, where it belongs,
with the app and site carrying the list of all seven.

A documentation page is a page about the editor, not a listing of it, so it
ships a named stub rather than the full node with price and category. It has
to ship something: about: { "@id": ... } pointing at nothing is dropped, and
the page goes back to describing an anonymous application.

Also from Apple: max-image-preview:large on every page (plus max-snippet:-1,
which is ours -- it lifts the cap on what a search or AI summary may quote),
and sameAs moved onto the Organization where it identifies the publisher.

Three new contracts run per page: this page's WebPage node is this page, any
WebApplication is the shared entity, and every @id reference resolves to a
definition in the same graph with no duplicate ids. That third one caught a
real dangling reference on the documentation pages, which is why appStub()
exists. Reverse-verified: stashing bin/build-pages.mjs turns 441 red.

Considered and rejected: hreflang pt -> pt-BR. Apple declares it because it
runs separate pt and br sites; we have one Portuguese, and narrowing it would
drop readers in Portugal to the English x-default.

Co-Authored-By: Claude Opus 5 noreply@anthropic.com

Read apple.com's markup and took the part that transfers. Apple never
redefines "Apple": it names the node once as #organization and every page
after that writes manufacturer: { "@id": ... }. Same for #website and each
product's #brand.

We did the opposite. Every page emitted its own anonymous WebApplication and
SoftwareSourceCode, so 154 pages described 154 unrelated tools that happened
to share a name. That costs little in ranking and a lot in the machines that
answer questions about the site -- an assistant that reads three of our pages
should come away with one editor.

Organization, WebSite, WebApplication and SoftwareSourceCode now have fixed
@ids and appear on every page as references to each other; WebPage, FAQPage,
HowTo and BreadcrumbList get page-scoped ones. The app's url is the site root
everywhere -- a per-page url made each translation look like a separate
product -- and the language moved to WebPage.inLanguage, where it belongs,
with the app and site carrying the list of all seven.

A documentation page is a page about the editor, not a listing of it, so it
ships a named stub rather than the full node with price and category. It has
to ship something: about: { "@id": ... } pointing at nothing is dropped, and
the page goes back to describing an anonymous application.

Also from Apple: max-image-preview:large on every page (plus max-snippet:-1,
which is ours -- it lifts the cap on what a search or AI summary may quote),
and sameAs moved onto the Organization where it identifies the publisher.

Three new contracts run per page: this page's WebPage node is this page, any
WebApplication is the shared entity, and every @id reference resolves to a
definition in the same graph with no duplicate ids. That third one caught a
real dangling reference on the documentation pages, which is why appStub()
exists. Reverse-verified: stashing bin/build-pages.mjs turns 441 red.

Considered and rejected: hreflang pt -> pt-BR. Apple declares it because it
runs separate pt and br sites; we have one Portuguese, and narrowing it would
drop readers in Portugal to the English x-default.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@chaxus
chaxus enabled auto-merge (rebase) August 23, 2026 07:33
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying document with  Cloudflare Pages  Cloudflare Pages

Latest commit: 8562e72
Status: ✅  Deploy successful!
Preview URL: https://8a1c21ca.document-7hm.pages.dev
Branch Preview URL: https://seo-entity-graph.document-7hm.pages.dev

View logs

@chaxus

chaxus commented Aug 23, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #201: this branch was cut before the service-worker fix, so its E2E failed on the very bug that fix addresses. The commit is carried into #201 unchanged.

@chaxus chaxus closed this Aug 23, 2026
auto-merge was automatically disabled August 23, 2026 07:45

Pull request was closed

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