Skip to content

feat(seo): rich JSON-LD — ProfilePage, enriched Person, BreadcrumbList#7

Merged
CybotTM merged 1 commit intomainfrom
feat/structured-data
May 10, 2026
Merged

feat(seo): rich JSON-LD — ProfilePage, enriched Person, BreadcrumbList#7
CybotTM merged 1 commit intomainfrom
feat/structured-data

Conversation

@CybotTM
Copy link
Copy Markdown
Owner

@CybotTM CybotTM commented May 10, 2026

Why

Today every page ships an identical flat `Person` JSON-LD. That is valid and helps Knowledge Graph entity disambiguation, but it doesn't describe the page itself (Google supports `ProfilePage` for personal-profile pages since Sep 2023 and recognises `BreadcrumbList` for navigation), and the `Person` was missing several attributes that exist in plain text in the CV body.

What

Replaces the flat `Person` payload with a per-page `@graph` payload that the canonical Schema.org pattern uses for typed, deduplicated linked data.

Every page emits:

  • `ProfilePage` — one per page; `@id` is `#profilepage`, with `name`, `inLanguage`, `dateModified` (build date), `isPartOf → WebSite`, `about / mainEntity → Person`.
  • `Person` — shared singleton (`@id = …/#person`). Existing fields kept; added:
    • `description` (one-paragraph profile)
    • `alumniOf` (Staatliche Berufsschule Erlangen, with address)
    • `knowsLanguage` (de + en as Schema.org `Language` objects)
    • `knowsAbout` — ~30 explicit skill/topic keywords drawn from the CV content (Engineering Governance, Platform Architecture, TYPO3, Magento, DevOps, AWS, Agentic Development, …)
    • `hasOccupation` (`Occupation` with `occupationLocation`, `experienceRequirements`, `skills`)
    • `worksFor` now also carries Netresearch DTT's address
  • `WebSite` — `publisher → Person`, `inLanguage = de`.
  • `BreadcrumbList` — only on `cv-executive.de.html` and `cv-technical.de.html`: `Übersicht → CV`.

Cross-references use `@id` so Person and WebSite are written once per page even though every node references them. Standard linked-data hygiene.

Why this matters

Use case Old New
Google Knowledge Graph (entity disambiguation by name + employer + sameAs) works works, with richer signal (skills, languages, occupation, education)
Google profile-page rich result eligibility not eligible (no `ProfilePage`) eligible
Breadcrumb display in SERPs for the variant URLs not eligible eligible
Per-page identification (`dateModified`) absent present

The realistic effect for a CTO's CV is mostly better entity matching in Google's Knowledge Graph and the chance of a breadcrumb in SERPs — visible Person rich-snippets aren't generally shown for non-celebrities. The structured data is also useful for any AI agent or crawler indexing the page.

Pure refactor of one file

Only `scripts/build.py` changed. New module-level constants `PERSON_JSONLD`, `WEBSITE_JSONLD`, plus `page_jsonld()` and `variant_breadcrumb()` builders. `render_html()` and `render_index()` call those before `json.dumps` — no template change.

Verification

Local Lighthouse desktop preset, all three pages:

Metric Value
Performance 100
Accessibility 100
Best Practices 100
SEO 100
CLS 0

Lighthouse's `structured-data` audit is `scoreDisplayMode=manual` (it just reminds you to validate externally; doesn't score). Recommend running the deployed `https://cybottm.github.io/cv/\` through Google's Rich Results Test once this lands to confirm Google's parser is happy and which rich-result types it recognises.

…List

The previous JSON-LD was a flat Person object, identical on every page.
This rewrites it as a per-page @graph with three (or four) named nodes:

ProfilePage  (one per page, the canonical schema for personal profile
             pages — supported by Google rich results since Sep 2023)
  - @id, url, name, inLanguage, dateModified
  - isPartOf -> WebSite
  - about / mainEntity -> Person
  - breadcrumb -> BreadcrumbList (variant pages only)

Person       (shared by @id across all pages, written once)
  - existing: name, givenName, familyName, jobTitle, address, worksFor,
    url, sameAs (LinkedIn + GitHub)
  - added: description, alumniOf (Berufsschule Erlangen),
    knowsLanguage (de + en as Schema.org Language objects),
    knowsAbout (~30 explicit skill/topic keywords drawn from the CV
    content), hasOccupation (Occupation node with location, experience,
    skills), and a richer worksFor with Netresearch DTT's address.

WebSite      (publisher = Person, in_language = de)

BreadcrumbList (only on cv-executive.de.html and cv-technical.de.html)
  - Übersicht -> <Variant> CV

Each ProfilePage now references the shared Person and WebSite by @id
rather than duplicating them inline, which is the canonical pattern for
linked data and keeps the payload deduplicated for crawlers.

build.py refactor:
- New PERSON_JSONLD, WEBSITE_JSONLD module constants.
- New page_jsonld() builder that returns a per-page @graph payload.
- New variant_breadcrumb() builder.
- render_html() and render_index() construct the per-page payload
  before calling json.dumps (no template change needed; the template
  still receives a serialized string via the jsonld variable).

Verified locally with Lighthouse desktop preset on all three pages:
- Performance / A11y / Best practices / SEO 100 / 100 / 100 / 100
- CLS 0
- Lighthouse 'structured-data' audit is scoreDisplayMode='manual', so
  it always shows the placeholder card — Lighthouse does not validate
  JSON-LD itself. Validate via Google's Rich Results Test or
  validator.schema.org once deployed.

Signed-off-by: Sebastian Mendel <info@sebastianmendel.de>
Copilot AI review requested due to automatic review settings May 10, 2026 16:05
@CybotTM CybotTM merged commit 439737a into main May 10, 2026
5 checks passed
@CybotTM CybotTM deleted the feat/structured-data branch May 10, 2026 16:07
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR upgrades the site’s structured data from a single flat Person JSON-LD blob to per-page Schema.org @graph JSON-LD, adding ProfilePage (per page), a richer shared Person, a shared WebSite, and BreadcrumbList for CV variant pages to improve rich-result eligibility and entity understanding.

Changes:

  • Replace the single Person JSON-LD payload with a per-page @graph payload that includes ProfilePage, shared Person, and shared WebSite.
  • Enrich the Person entity with additional attributes (e.g., description, alumniOf, knowsLanguage, knowsAbout, hasOccupation, and expanded org address).
  • Add BreadcrumbList generation for CV variant pages and wire JSON-LD generation into render_html() / render_index().

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

2 participants