This commit is contained in:
2025-09-30 23:10:13 +02:00
parent a646542d8f
commit 9938c1f9e2
27 changed files with 2158 additions and 498 deletions

187
src/utils/structuredData.ts Normal file
View File

@@ -0,0 +1,187 @@
/**
* Utility functions for generating structured data (JSON-LD) schemas
* Enhances SEO and AEO by providing rich metadata to search engines
*/
export interface ArticleSchemaProps {
headline: string;
description: string;
author: string;
datePublished: string;
dateModified?: string;
image?: string;
url: string;
keywords?: string[];
articleBody?: string;
}
export interface ServiceSchemaProps {
name: string;
description: string;
provider: string;
areaServed: string[];
url: string;
image?: string;
offers?: {
price?: string;
priceCurrency?: string;
availability?: string;
};
}
export interface ReviewSchemaProps {
author: string;
datePublished: string;
reviewBody: string;
ratingValue: number;
bestRating?: number;
worstRating?: number;
}
/**
* Generate Article/BlogPosting schema for blog posts
*/
export const generateArticleSchema = (props: ArticleSchemaProps) => {
const schema = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: props.headline,
description: props.description,
author: {
"@type": "Organization",
name: props.author,
url: "https://bayarea-cc.com"
},
publisher: {
"@type": "Organization",
name: "Bay Area Affiliates",
logo: {
"@type": "ImageObject",
url: "https://bayarea-cc.com/logo_bayarea.svg"
}
},
datePublished: props.datePublished,
dateModified: props.dateModified || props.datePublished,
mainEntityOfPage: {
"@type": "WebPage",
"@id": props.url
},
...(props.image && {
image: {
"@type": "ImageObject",
url: props.image
}
}),
...(props.keywords && { keywords: props.keywords.join(", ") }),
...(props.articleBody && { articleBody: props.articleBody })
};
return schema;
};
/**
* Generate Service schema for service pages
*/
export const generateServiceSchema = (props: ServiceSchemaProps) => {
const schema = {
"@context": "https://schema.org",
"@type": "Service",
name: props.name,
description: props.description,
provider: {
"@type": "Organization",
name: props.provider,
url: "https://bayarea-cc.com"
},
areaServed: props.areaServed.map(area => ({
"@type": "City",
name: area
})),
url: props.url,
...(props.image && {
image: {
"@type": "ImageObject",
url: props.image
}
}),
...(props.offers && {
offers: {
"@type": "Offer",
...props.offers,
seller: {
"@type": "Organization",
name: props.provider
}
}
})
};
return schema;
};
/**
* Generate Review schema
*/
export const generateReviewSchema = (props: ReviewSchemaProps) => {
const schema = {
"@context": "https://schema.org",
"@type": "Review",
author: {
"@type": "Person",
name: props.author
},
datePublished: props.datePublished,
reviewBody: props.reviewBody,
reviewRating: {
"@type": "Rating",
ratingValue: props.ratingValue,
bestRating: props.bestRating || 5,
worstRating: props.worstRating || 1
},
itemReviewed: {
"@type": "LocalBusiness",
name: "Bay Area Affiliates",
url: "https://bayarea-cc.com"
}
};
return schema;
};
/**
* Generate FAQ schema
*/
export const generateFAQSchema = (faqs: Array<{ question: string; answer: string }>) => {
const schema = {
"@context": "https://schema.org",
"@type": "FAQPage",
mainEntity: faqs.map(faq => ({
"@type": "Question",
name: faq.question,
acceptedAnswer: {
"@type": "Answer",
text: faq.answer
}
}))
};
return schema;
};
/**
* Inject structured data into the page
*/
export const injectStructuredData = (schema: object, id: string = "structured-data") => {
// Remove existing script if present
const existing = document.getElementById(id);
if (existing) {
existing.remove();
}
// Create and inject new script
const script = document.createElement("script");
script.id = id;
script.type = "application/ld+json";
script.text = JSON.stringify(schema);
document.head.appendChild(script);
};