Structured data for article - #6052
Conversation
ae4b4e5 to
8f428aa
Compare
`getSiteConfigForDomain`/`getSiteConfigs` are plain env lookups, but they
lived in `siteConfig.ts`, whose top-level `import { headers } from "next/headers"`
taints every importer. That prevents them from being used in code that ends up
in the browser bundle (e.g. block loaders reachable from the block-preview route).
Move the two pure accessors into a dedicated `getSiteConfigs.ts` with no
request-scoped imports, and re-export them from `siteConfig.ts` so existing call
sites keep working unchanged. The header-dependent helpers stay in `siteConfig.ts`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HJR1y9TUydWJ4ddW8cjG3q
The Article structured data (added next) needs the site's `Organization` as a nested `author`/`publisher` node, i.e. the same mapping from the site config that `OrganizationJsonLd` already does, but without the `@context` wrapper. Move the mapping into `buildOrganizationNode`, add a context-wrapped `buildOrganization` for use at the page root, and have `OrganizationJsonLd` render that. No change to the emitted output. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJR1y9TUydWJ4ddW8cjG3q
Add the builders that produce the JSON-LD for the News surfaces: - `buildArticle` maps a news item to a schema.org `Article` (headline, image, datePublished/dateModified, author/publisher, mainEntityOfPage). Author and publisher reuse the site-wide `Organization` node from the site config. - `buildNewsItemList` maps a list of news items to an `ItemList` of `ListItem`s with absolute detail URLs. Both take the content scope and resolve the site config (absolute base URL and organization) from it. `damImageToAbsoluteUrl` resolves a `DamImageBlock` to the absolute image URL the `Article` needs. Wiring into the surfaces follows in separate commits. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJR1y9TUydWJ4ddW8cjG3q
Emit schema.org `Article` structured data on the news detail route so search and generative engines can identify each news article. Extend the detail fragment with the fields the Article needs (`date`, `updatedAt`, `slug`). Author and publisher come from the site config's organization, so no additional query is needed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJR1y9TUydWJ4ddW8cjG3q
Emit a schema.org `ItemList` for the curated news list rendered by NewsListBlock. The list is built in the loader rather than the component because the block renders inside a client component, which has no access to the site config needed for the absolute item URLs. The loader now returns both the news items and the prepared structured data. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJR1y9TUydWJ4ddW8cjG3q
Emit a schema.org `ItemList` for the `/news` index. Only the initially rendered page of items is encoded; items appended client-side via "Load more" are intentionally not part of the list, which is noted with a comment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HJR1y9TUydWJ4ddW8cjG3q
8f428aa to
345a882
Compare
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yml Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryAdds JSON-LD structured data for Demo news detail pages, news index pages, and curated news-list blocks.
|
| Filename | Overview |
|---|---|
| demo/site/src/util/structuredData/buildArticle.ts | Builds contextual Article JSON-LD from news content, site configuration, and the shared organization node. |
| demo/site/src/util/structuredData/buildNewsItemList.ts | Builds absolute ItemList entries while preserving the accepted single-scope behavior of the news surfaces. |
| demo/site/src/news/blocks/NewsListBlock.loader.ts | Returns news and server-built structured data for curated news-list blocks. |
| demo/site/src/organization/OrganizationJsonLd.tsx | Delegates the existing Organization JSON-LD mapping to the reusable organization builder. |
| demo/site/src/util/getSiteConfigs.ts | Extracts environment-backed site-configuration access so structured-data builders avoid server header dependencies. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[News detail page] --> B[buildArticle]
B --> C[Article JSON-LD]
D[News index page] --> E[buildNewsItemList]
F[NewsListBlock loader] --> E
E --> G[ItemList JSON-LD]
H[Site configuration] --> B
H --> E
H --> I[buildOrganizationNode]
I --> B
Reviews (3): Last reviewed commit: "demo: Add ItemList JSON-LD to the news i..." | Re-trigger Greptile
|
@greptile review |
| author: organization, | ||
| publisher: organization, |
There was a problem hiding this comment.
imho we should not include the same organisation object, that we already include on every page, here as author and publisher. I'd use auther/publisher ONLY if we would have real values (and we would store the author per news - and display them in the site)
|
|
||
| let siteConfigs: PublicSiteConfig[]; | ||
|
|
||
| export function getSiteConfigs() { |
There was a problem hiding this comment.
please keep changes in a PR to a minimum and move this refactor (move of getSiteConfigs/getSiteConfigForDomain to it's own file) to an individual PR
There was a problem hiding this comment.
I'm not sure if this refactor makes sense. Here's the rationale from the PR description:
getSiteConfigForDomain/getSiteConfigs moved into getSiteConfigs.ts so the NewsListBlock loader can build absolute URLs without pulling next/headers into the browser bundle. Existing call sites keep working via a re-export.
But process.env.PUBLIC_SITE_CONFIGS isn't available in the browser, why do we need to move it then?
| ))} | ||
| </ol> | ||
| <> | ||
| {structuredData && <JsonLd<ItemList> data={structuredData} />} |
There was a problem hiding this comment.
I know it was required in the ticket, but I'm questioning this.
I don't think we should use an ItemList JsonLd here when we have a detail page that has a full Article JsonLd
| if ("urlTemplate" in props && props.damFile?.image) { | ||
| const { width, height } = props.damFile.image; | ||
| return generateImageUrl({ src: props.urlTemplate, width }, width / height); | ||
| } |
There was a problem hiding this comment.
This generates an image in its original size and aspect ratio. Google recommends to generate multiple images:
For best results, we recommend providing multiple high-resolution images (minimum of 50K pixels when multiplying width and height) with the following aspect ratios: 16x9, 4x3, and 1x1.
– https://developers.google.com/search/docs/appearance/structured-data/article
| news: { | ||
| title: string; | ||
| image: DamImageBlockData; | ||
| date: string; | ||
| updatedAt: string; | ||
| slug: string; | ||
| }; |
There was a problem hiding this comment.
It would be nice if we could pick the types from GQLNews here or use a fragment.
Adds
ArticleandItemListstructured data (JSON-LD) to the News surfaces in the Demo, so search and generative engines can identify news articles and listings. There was no structured data for News before. Demo only — the Starter has no News feature./news/[slug])ArticleNewsListBlock(curated list on a CMS page)ItemList/news)ItemListauthorandpublisherreuse the site-wideOrganizationfrom the site config (#6038). To nest it, that mapping moved out ofOrganizationJsonLdintobuildOrganizationNode; the component now renders the same node wrapped in@context, so its output is unchanged.Two smaller notes:
/news, only the initially rendered page is encoded — items appended via "Load more" are intentionally left out.getSiteConfigForDomain/getSiteConfigsmoved intogetSiteConfigs.tsso theNewsListBlockloader can build absolute URLs without pullingnext/headersinto the browser bundle. Existing call sites keep working via a re-export.Example — actual output from
/en/news/tego-coepi-valde-cattus:{ "@context": "https://schema.org", "@type": "Article", "headline": "tego coepi valde cattus", "image": "http://localhost:3000/dam/images/b2bdb7ce.../resize:1024:1024/comet", "datePublished": "2026-09-07T22:40:36.720Z", "dateModified": "2026-09-09T11:43:11.444Z", "author": { "@type": "Organization", "name": "Vivid Planet Software GmbH", "url": "https://www.vivid-planet.com", "…": "" }, "publisher": { "…": "same Organization node" }, "mainEntityOfPage": "http://localhost:3000/en/news/tego-coepi-valde-cattus" }Testing
Tested locally by checking structured data in the dom:

Task: https://vivid-planet.atlassian.net/browse/DEX-2968