122 lines
3.3 KiB
TypeScript
122 lines
3.3 KiB
TypeScript
/**
|
|
* schema.org JSON-LD builders for content pages.
|
|
*
|
|
* Emitting structured data gives Google rich-result eligibility and gives
|
|
* LLMs clean, linked facts (who authored it, which work it belongs to, when).
|
|
* Every page renders one of these via the <JsonLd> component
|
|
* (components/seo/json-ld.tsx).
|
|
*
|
|
* The author entity carries `sameAs` links to Wikipedia/Wikidata so search
|
|
* engines and LLMs can resolve it to the canonical Śrīla Prabhupāda entity.
|
|
*/
|
|
|
|
const SITE_URL =
|
|
process.env.NEXT_PUBLIC_SITE_URL || "https://prabhupadasays.org";
|
|
|
|
const AUTHOR = {
|
|
"@type": "Person",
|
|
name: "His Divine Grace A.C. Bhaktivedanta Swami Prabhupāda",
|
|
alternateName: "A.C. Bhaktivedanta Swami Prabhupāda",
|
|
sameAs: [
|
|
"https://en.wikipedia.org/wiki/A._C._Bhaktivedanta_Swami_Prabhupada",
|
|
"https://www.wikidata.org/wiki/Q318439",
|
|
],
|
|
} as const;
|
|
|
|
const PUBLISHER = {
|
|
"@type": "Organization",
|
|
name: "Prabhupāda Says",
|
|
url: SITE_URL,
|
|
} as const;
|
|
|
|
function abs(path: string): string {
|
|
return path.startsWith("http") ? path : `${SITE_URL}${path}`;
|
|
}
|
|
|
|
/** Trim a (possibly HTML) string to a clean plain-text description. */
|
|
function clean(text: string | undefined, max = 300): string | undefined {
|
|
if (!text) return undefined;
|
|
const plain = text
|
|
.replace(/<[^>]+>/g, "")
|
|
.replace(/\s+/g, " ")
|
|
.trim();
|
|
if (!plain) return undefined;
|
|
return plain.length > max ? plain.slice(0, max - 1).trimEnd() + "…" : plain;
|
|
}
|
|
|
|
/**
|
|
* An Article — used for every leaf content page (verses, letters, transcripts,
|
|
* prose chapters, essays, magazine/journal pages). `isPartOf` names the parent
|
|
* work (book/magazine); `datePublished` must be ISO 8601 (omit otherwise).
|
|
*/
|
|
export function articleJsonLd(params: {
|
|
path: string;
|
|
headline: string;
|
|
description?: string;
|
|
datePublished?: string;
|
|
isPartOf?: string;
|
|
}) {
|
|
const url = abs(params.path);
|
|
const obj: Record<string, unknown> = {
|
|
"@context": "https://schema.org",
|
|
"@type": "Article",
|
|
headline: params.headline,
|
|
author: AUTHOR,
|
|
publisher: PUBLISHER,
|
|
inLanguage: "en",
|
|
isAccessibleForFree: true,
|
|
url,
|
|
mainEntityOfPage: url,
|
|
};
|
|
const desc = clean(params.description);
|
|
if (desc) obj.description = desc;
|
|
if (params.datePublished) obj.datePublished = params.datePublished;
|
|
if (params.isPartOf) {
|
|
obj.isPartOf = { "@type": "Book", name: params.isPartOf, author: AUTHOR };
|
|
}
|
|
return obj;
|
|
}
|
|
|
|
/** A public profile page → ProfilePage wrapping the Person. */
|
|
export function profileJsonLd(params: {
|
|
path: string;
|
|
name: string;
|
|
handle: string;
|
|
image?: string | null;
|
|
}) {
|
|
const url = abs(params.path);
|
|
const person: Record<string, unknown> = {
|
|
"@type": "Person",
|
|
name: params.name,
|
|
alternateName: `@${params.handle}`,
|
|
url,
|
|
};
|
|
if (params.image) person.image = params.image;
|
|
return {
|
|
"@context": "https://schema.org",
|
|
"@type": "ProfilePage",
|
|
url,
|
|
mainEntity: person,
|
|
};
|
|
}
|
|
|
|
/** A Book — used for book landing pages. */
|
|
export function bookJsonLd(params: {
|
|
path: string;
|
|
name: string;
|
|
description?: string;
|
|
}) {
|
|
const obj: Record<string, unknown> = {
|
|
"@context": "https://schema.org",
|
|
"@type": "Book",
|
|
name: params.name,
|
|
author: AUTHOR,
|
|
publisher: PUBLISHER,
|
|
inLanguage: "en",
|
|
url: abs(params.path),
|
|
};
|
|
const desc = clean(params.description);
|
|
if (desc) obj.description = desc;
|
|
return obj;
|
|
}
|