Rocky_Mountain_Vending/lib/seo.ts
DMleadgen 46d973904b
Initial commit: Rocky Mountain Vending website
Next.js website for Rocky Mountain Vending company featuring:
- Product catalog with Stripe integration
- Service areas and parts pages
- Admin dashboard with Clerk authentication
- SEO optimized pages with JSON-LD structured data

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-12 16:22:15 -07:00

143 lines
3.5 KiB
TypeScript

import type { Metadata } from 'next';
/**
* Clean HTML entities from text (e.g., &amp; -> &, &quot; -> ")
*/
function cleanHtmlEntities(text: string): string {
if (!text) return '';
return text
.replace(/&amp;/g, '&')
.replace(/&quot;/g, '"')
.replace(/&#039;/g, "'")
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&nbsp;/g, ' ')
.trim();
}
export interface SEOData {
title: string;
description: string;
excerpt?: string;
date?: string;
modified?: string;
image?: string;
}
/**
* Generate SEO metadata for a WordPress page/post
*/
export function generateSEOMetadata(data: SEOData): Metadata {
const { title, description, excerpt, date, modified, image } = data;
// Clean title (max 60 chars for SEO)
const cleanTitle = cleanHtmlEntities(title);
const seoTitle = cleanTitle.length > 60
? cleanTitle.substring(0, 57) + '...'
: cleanTitle;
const fullTitle = `${seoTitle} | Rocky Mountain Vending`;
// Clean description (150-160 chars)
let seoDescription = description || excerpt || '';
if (seoDescription.length > 160) {
seoDescription = seoDescription.substring(0, 157) + '...';
} else if (seoDescription.length < 120) {
seoDescription = seoDescription || 'Rocky Mountain Vending provides quality vending machine services in Utah.';
}
// Default image
const ogImage = image || '/images/rocky-mountain-vending-service-area-926x1024.webp';
const metadata: Metadata = {
title: fullTitle,
description: seoDescription,
openGraph: {
title: fullTitle,
description: seoDescription,
type: 'website',
images: [
{
url: ogImage,
width: 1200,
height: 630,
alt: cleanTitle,
},
],
siteName: 'Rocky Mountain Vending',
},
twitter: {
card: 'summary_large_image',
title: fullTitle,
description: seoDescription,
images: [ogImage],
},
};
// Add dates if available (via openGraph)
if (date || modified) {
metadata.openGraph = {
...metadata.openGraph,
...(date && { publishedTime: date }),
...(modified && { modifiedTime: modified }),
};
}
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: any = {
'@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: 'Rocky Mountain Vending',
url: 'https://rockymountainvending.com',
};
structuredData.publisher = {
'@type': 'Organization',
name: 'Rocky Mountain Vending',
legalName: 'Rocky Mountain Vending LLC',
url: 'https://rockymountainvending.com',
logo: {
'@type': 'ImageObject',
url: 'https://rockymountainvending.com/rmv-logo.png',
width: 180,
height: 45,
},
};
structuredData.mainEntityOfPage = {
'@type': 'WebPage',
'@id': url,
};
}
return structuredData;
}