58 lines
2.0 KiB
TypeScript
58 lines
2.0 KiB
TypeScript
import type { Breadcrumb, PageDocument } from '#web-runtime/types';
|
|
import { toPublicHref } from '#i18n/content-href';
|
|
|
|
export type JsonLd = Record<string, unknown>;
|
|
|
|
/**
|
|
* schema.org `BreadcrumbList` for a resolved breadcrumb trail. Positions are
|
|
* 1-based. The current page (the last crumb) intentionally omits `item` — per
|
|
* Google's guidance it is where the user already is, so it carries no URL.
|
|
*/
|
|
export function breadcrumbListJsonLd(items: Breadcrumb[]): JsonLd {
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'BreadcrumbList',
|
|
'itemListElement': items.map((item, index) => ({
|
|
'@type': 'ListItem',
|
|
'position': index + 1,
|
|
'name': item.label,
|
|
...(index < items.length - 1 ? { item: toPublicHref(item.href) } : {})
|
|
}))
|
|
};
|
|
}
|
|
|
|
/**
|
|
* schema.org `Article` for an editorial page. Only the fields the backend actually knows are
|
|
* emitted — a date or image invented here would be markup that contradicts the page.
|
|
*
|
|
* `dateModified` is omitted when it equals `datePublished`, since repeating it tells a crawler
|
|
* nothing, and the whole object is skipped by the caller when there is no publication date:
|
|
* an `Article` without one is markup Google will ignore anyway.
|
|
*/
|
|
export function articleJsonLd(page: PageDocument): JsonLd | null {
|
|
if (!page.publishedAt) {
|
|
return null;
|
|
}
|
|
|
|
const image = page.hero?.media?.url;
|
|
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'Article',
|
|
'headline': page.seo.title || page.title,
|
|
...(page.seo.description ? { description: page.seo.description } : {}),
|
|
'datePublished': page.publishedAt,
|
|
...(page.updatedAt && page.updatedAt !== page.publishedAt
|
|
? { dateModified: page.updatedAt }
|
|
: {}),
|
|
...(image ? { image: [image] } : {}),
|
|
'inLanguage': page.locale
|
|
};
|
|
}
|
|
|
|
export function JsonLdScript({ data }: { data: JsonLd }) {
|
|
return (
|
|
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(data) }} />
|
|
);
|
|
}
|