import type { Metadata } from "next"; import { SITE_URL } from "@/lib/routes"; /** Metadata for an entity / index page: title, description, canonical + OG/Twitter mirror (OG image comes from the segment's opengraph-image.tsx). */ export function pageMetadata({ title, description, path, absoluteTitle }: { title: string; description: string; path: string; absoluteTitle?: boolean }): Metadata { const desc = description.length > 300 ? `${description.slice(0, 297)}…` : description; return { title: absoluteTitle ? { absolute: title } : title, description: desc, alternates: { canonical: path }, openGraph: { title, description: desc, url: `${SITE_URL}${path}`, type: "website" }, twitter: { card: "summary_large_image", title, description: desc }, }; } /** Metadata for an entity that does not exist (still renders 404 body via notFound()). */ export const NOT_FOUND_METADATA: Metadata = { title: "Not found", robots: { index: false, follow: false } }; export function absoluteUrl(path: string): string { return `${SITE_URL}${path.startsWith("/") ? path : `/${path}`}`; } /** Drop null / undefined / empty values from a JSON-LD object (Google ignores empty props but it keeps the payload tidy). */ export function compactJsonLd>(obj: T): Partial { const out: Record = {}; for (const [k, v] of Object.entries(obj)) { if (v === null || v === undefined || v === "") continue; if (Array.isArray(v) && v.length === 0) continue; out[k] = v; } return out as Partial; } export function breadcrumbJsonLd(items: Array<{ name: string; href: string }>) { return { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: items.map((it, i) => ({ "@type": "ListItem", position: i + 1, name: it.name, item: absoluteUrl(it.href) })), }; }