import type { Metadata } from "next" import { businessConfig } from "@/lib/seo-config" import { buildAbsoluteUrl, getSeoPageDefinition, ogImagePath, type StaticSeoPageKey, } from "@/lib/seo-registry" /** * Clean HTML entities from text (e.g., & -> &, " -> ") */ function cleanHtmlEntities(text: string): string { if (!text) return "" return text .replace(/&/g, "&") .replace(/"/g, '"') .replace(/'/g, "'") .replace(/</g, "<") .replace(/>/g, ">") .replace(/ /g, " ") .trim() } export interface SEOData { title: string description: string excerpt?: string date?: string modified?: string image?: string robots?: Metadata["robots"] path?: string keywords?: string[] } function createRobotsValue(noindex?: boolean): Metadata["robots"] | undefined { if (!noindex) { return undefined } return { index: false, follow: false, googleBot: { index: false, follow: false, }, } } /** * Generate SEO metadata for a WordPress page/post */ export function generateSEOMetadata(data: SEOData): Metadata { const { title, description, excerpt, date, modified, image, robots, path, keywords, } = data const cleanTitle = cleanHtmlEntities(title) const fullTitle = cleanTitle.includes(businessConfig.name) ? cleanTitle : `${cleanTitle} | ${businessConfig.name}` let seoDescription = cleanHtmlEntities(description || excerpt || "") .replace(/\s+/g, " ") .trim() if (!seoDescription) { seoDescription = "Rocky Mountain Vending provides vending machine placement, service, repairs, and support across Utah." } const ogImage = image || ogImagePath const canonicalUrl = path ? buildAbsoluteUrl(path) : undefined const openGraph: NonNullable = { title: fullTitle, description: seoDescription, type: date || modified ? "article" : "website", ...(canonicalUrl ? { url: canonicalUrl } : {}), images: [ { url: ogImage, width: 1200, height: 630, alt: cleanTitle, }, ], siteName: businessConfig.name, ...(date && { publishedTime: date }), ...(modified && { modifiedTime: modified }), } const metadata: Metadata = { title: fullTitle, description: seoDescription, keywords, openGraph, twitter: { card: "summary_large_image", title: fullTitle, description: seoDescription, images: [ogImage], }, alternates: canonicalUrl ? { canonical: canonicalUrl } : undefined, robots, } return metadata } export function generateRegistryMetadata( key: StaticSeoPageKey, overrides?: Partial ): Metadata { const page = getSeoPageDefinition(key) return generateSEOMetadata({ title: overrides?.title ?? page.title, description: overrides?.description ?? page.description, excerpt: overrides?.excerpt, date: overrides?.date, modified: overrides?.modified, image: overrides?.image, robots: overrides?.robots ?? createRobotsValue(page.noindex), path: overrides?.path ?? page.canonicalPath ?? page.path, keywords: overrides?.keywords ?? [...page.keywords], }) } export function generateRegistryStructuredData( key: StaticSeoPageKey, overrides?: { title?: string description?: string url?: string datePublished?: string dateModified?: string type?: "Article" | "WebPage" } ) { const page = getSeoPageDefinition(key) return generateStructuredData({ title: overrides?.title ?? page.title, description: overrides?.description ?? page.description, url: overrides?.url ?? buildAbsoluteUrl(page.canonicalPath ?? page.path), datePublished: overrides?.datePublished, dateModified: overrides?.dateModified, type: overrides?.type, }) } /** * Generate structured data (JSON-LD) for a page */ export function generateStructuredData(data: { title: string description: string url: string datePublished?: string dateModified?: string type?: "Article" | "WebPage" }) { const { title, description, url, datePublished, dateModified, type = "WebPage", } = data const structuredData: Record = { "@context": "https://schema.org", "@type": type, headline: cleanHtmlEntities(title), description: cleanHtmlEntities(description), url: url, } if (datePublished) { structuredData.datePublished = datePublished } if (dateModified) { structuredData.dateModified = dateModified } if (type === "Article") { structuredData.author = { "@type": "Organization", name: businessConfig.name, url: businessConfig.website, } structuredData.publisher = { "@type": "Organization", name: businessConfig.name, legalName: businessConfig.legalName, url: businessConfig.website, logo: { "@type": "ImageObject", url: `${businessConfig.website}/rmv-logo.png`, width: 180, height: 45, }, } structuredData.mainEntityOfPage = { "@type": "WebPage", "@id": url, } } return structuredData }