import type { Metadata } from "next" import { businessConfig } from "@/lib/seo-config" import { buildAbsoluteUrl, ogImagePath } 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[] } /** * 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." } else if (seoDescription.length > 165) { seoDescription = `${seoDescription.slice(0, 162).trim()}...` } 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 } /** * 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 }