Files

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;
}