381 lines
10 KiB
TypeScript
381 lines
10 KiB
TypeScript
import type { BlogPost, AuthorProfile, PillarMeta, Testimonial, AggregateRating } from "./types";
|
|
|
|
const SITE_URL = "https://www.qrmaster.net";
|
|
|
|
export function websiteSchema() {
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'WebSite',
|
|
'@id': `${SITE_URL}/#website`,
|
|
name: 'QR Master',
|
|
url: SITE_URL,
|
|
inLanguage: 'en',
|
|
mainEntityOfPage: SITE_URL,
|
|
publisher: {
|
|
'@id': `${SITE_URL}/#organization`,
|
|
},
|
|
potentialAction: {
|
|
'@type': 'SearchAction',
|
|
target: {
|
|
'@type': 'EntryPoint',
|
|
urlTemplate: `${SITE_URL}/blog?q={search_term_string}`,
|
|
},
|
|
'query-input': 'required name=search_term_string',
|
|
},
|
|
};
|
|
}
|
|
|
|
export function organizationSchema() {
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'Organization',
|
|
'@id': `${SITE_URL}/#organization`,
|
|
name: 'QR Master',
|
|
alternateName: 'QRMaster',
|
|
url: SITE_URL,
|
|
logo: {
|
|
'@type': 'ImageObject',
|
|
url: `${SITE_URL}/static/og-image.png`,
|
|
width: 1200,
|
|
height: 630,
|
|
},
|
|
sameAs: [
|
|
'https://www.wikidata.org/wiki/Q137918857',
|
|
'https://x.com/TIMO_QRMASTER',
|
|
'https://www.linkedin.com/in/qr-master-44b6863a2/',
|
|
'https://www.instagram.com/qrmaster_net/',
|
|
'https://www.pr.com/press-release/959199',
|
|
],
|
|
contactPoint: [{
|
|
'@type': 'ContactPoint',
|
|
contactType: 'Customer Support',
|
|
email: 'support@qrmaster.net',
|
|
availableLanguage: ['en', 'de'],
|
|
}],
|
|
};
|
|
}
|
|
|
|
|
|
export function blogPostingSchema(post: BlogPost, author?: AuthorProfile) {
|
|
const url = `${SITE_URL}/blog/${post.slug}`;
|
|
|
|
// Use post.authorName if available (from AEO/GEO optimization), otherwise fall back to author profile
|
|
const authorSchema = post.authorName
|
|
? {
|
|
"@type": "Person",
|
|
name: post.authorName,
|
|
jobTitle: post.authorTitle,
|
|
url: `${SITE_URL}/#organization`,
|
|
}
|
|
: author
|
|
? {
|
|
"@type": "Person",
|
|
name: author.name,
|
|
url: `${SITE_URL}/authors/${author.slug}`,
|
|
sameAs: author.sameAs ?? undefined,
|
|
knowsAbout: author.knowsAbout ?? undefined
|
|
}
|
|
: {
|
|
"@type": "Organization",
|
|
name: "QR Master"
|
|
};
|
|
|
|
return {
|
|
"@context": "https://schema.org",
|
|
"@type": "BlogPosting",
|
|
headline: post.title,
|
|
description: post.description,
|
|
url,
|
|
datePublished: post.datePublished,
|
|
dateModified: post.dateModified || post.datePublished,
|
|
image: post.heroImage ? `${SITE_URL}${post.heroImage}` : undefined,
|
|
author: authorSchema,
|
|
publisher: {
|
|
"@type": "Organization",
|
|
name: "QR Master",
|
|
url: SITE_URL,
|
|
logo: {
|
|
'@type': 'ImageObject',
|
|
url: `${SITE_URL}/static/og-image.png`,
|
|
}
|
|
},
|
|
isPartOf: {
|
|
'@type': 'Blog',
|
|
'@id': `${SITE_URL}/blog#blog`,
|
|
name: 'QR Master Blog',
|
|
url: `${SITE_URL}/blog`,
|
|
},
|
|
};
|
|
}
|
|
|
|
export function howToSchema(post: BlogPost, author?: AuthorProfile) {
|
|
const url = `${SITE_URL}/blog/${post.slug}`;
|
|
const steps = (post.keySteps ?? []).map((text, idx) => ({
|
|
"@type": "HowToStep",
|
|
position: idx + 1,
|
|
name: `Step ${idx + 1}`,
|
|
text
|
|
}));
|
|
|
|
// Use post.authorName if available, otherwise fall back to author profile
|
|
const authorSchema = post.authorName
|
|
? { "@type": "Person", name: post.authorName, jobTitle: post.authorTitle }
|
|
: author
|
|
? { "@type": "Person", name: author.name, url: `${SITE_URL}/authors/${author.slug}` }
|
|
: undefined;
|
|
|
|
return {
|
|
"@context": "https://schema.org",
|
|
"@type": "HowTo",
|
|
name: post.title,
|
|
description: post.description,
|
|
url: `${url}#howto`,
|
|
step: steps,
|
|
author: authorSchema
|
|
};
|
|
}
|
|
|
|
export function pillarPageSchema(meta: PillarMeta, posts: BlogPost[]) {
|
|
const url = `${SITE_URL}/learn/${meta.key}`;
|
|
return {
|
|
"@context": "https://schema.org",
|
|
"@type": "WebPage",
|
|
name: meta.title,
|
|
description: meta.description,
|
|
url,
|
|
mainEntity: {
|
|
"@type": "ItemList",
|
|
itemListElement: posts.map((p, i) => ({
|
|
"@type": "ListItem",
|
|
position: i + 1,
|
|
url: `${SITE_URL}/blog/${p.slug}`,
|
|
name: p.title
|
|
}))
|
|
}
|
|
};
|
|
}
|
|
|
|
export function faqPageSchema(faqs: { question: string, answer: string }[]) {
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'FAQPage',
|
|
mainEntity: faqs.map((faq) => ({
|
|
'@type': 'Question',
|
|
name: faq.question,
|
|
acceptedAnswer: {
|
|
'@type': 'Answer',
|
|
text: faq.answer,
|
|
},
|
|
})),
|
|
};
|
|
}
|
|
|
|
export function breadcrumbSchema(items: { name: string; url: string }[]) {
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'BreadcrumbList',
|
|
itemListElement: items.map((item, index) => ({
|
|
'@type': 'ListItem',
|
|
position: index + 1,
|
|
name: item.name,
|
|
item: item.url.startsWith('http') ? item.url : `https://www.qrmaster.net${item.url}`,
|
|
})),
|
|
};
|
|
}
|
|
|
|
export function softwareApplicationSchema(aggregateRating?: AggregateRating) {
|
|
const schema: any = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'SoftwareApplication',
|
|
name: 'QR Master',
|
|
applicationCategory: 'BusinessApplication',
|
|
operatingSystem: 'Web Browser',
|
|
offers: {
|
|
'@type': 'Offer',
|
|
price: '0',
|
|
priceCurrency: 'EUR',
|
|
availability: 'https://schema.org/InStock',
|
|
priceValidUntil: '2026-12-31'
|
|
},
|
|
publisher: {
|
|
'@id': `${SITE_URL}/#organization`,
|
|
}
|
|
};
|
|
|
|
if (aggregateRating) {
|
|
schema.aggregateRating = {
|
|
'@type': 'AggregateRating',
|
|
ratingValue: aggregateRating.ratingValue,
|
|
reviewCount: aggregateRating.reviewCount,
|
|
bestRating: aggregateRating.bestRating,
|
|
worstRating: aggregateRating.worstRating
|
|
};
|
|
}
|
|
|
|
return schema;
|
|
}
|
|
|
|
export function authorPageSchema(author: AuthorProfile, posts?: BlogPost[]) {
|
|
const url = `${SITE_URL}/authors/${author.slug}`;
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'ProfilePage',
|
|
mainEntity: {
|
|
'@type': 'Person',
|
|
'@id': url,
|
|
name: author.name,
|
|
jobTitle: author.role,
|
|
description: author.bio,
|
|
image: author.image ? `${SITE_URL}${author.image}` : undefined,
|
|
sameAs: author.sameAs?.length ? author.sameAs : undefined,
|
|
knowsAbout: author.knowsAbout?.length ? author.knowsAbout : undefined,
|
|
url,
|
|
},
|
|
about: posts?.length
|
|
? {
|
|
'@type': 'ItemList',
|
|
itemListElement: posts.map((p, i) => ({
|
|
'@type': 'ListItem',
|
|
position: i + 1,
|
|
url: `${SITE_URL}/blog/${p.slug}`,
|
|
name: p.title,
|
|
})),
|
|
}
|
|
: undefined,
|
|
};
|
|
}
|
|
|
|
export function articleSchema(params: {
|
|
headline: string;
|
|
description: string;
|
|
image?: string;
|
|
datePublished: string;
|
|
dateModified?: string;
|
|
author: string;
|
|
url?: string;
|
|
}) {
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'Article',
|
|
headline: params.headline,
|
|
description: params.description,
|
|
image: params.image,
|
|
datePublished: params.datePublished,
|
|
dateModified: params.dateModified || params.datePublished,
|
|
author: {
|
|
'@type': 'Organization',
|
|
name: params.author,
|
|
},
|
|
publisher: {
|
|
'@type': 'Organization',
|
|
name: 'QR Master',
|
|
url: SITE_URL,
|
|
logo: {
|
|
'@type': 'ImageObject',
|
|
url: `${SITE_URL}/static/og-image.png`,
|
|
},
|
|
},
|
|
url: params.url,
|
|
};
|
|
}
|
|
|
|
export function reviewSchema(testimonial: Testimonial) {
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'Review',
|
|
itemReviewed: {
|
|
'@type': 'SoftwareApplication',
|
|
name: 'QR Master',
|
|
description: 'Professional QR code generator with dynamic QR codes, analytics, and customization.',
|
|
image: `${SITE_URL}/static/og-image.png`,
|
|
applicationCategory: 'BusinessApplication',
|
|
operatingSystem: 'Web Browser',
|
|
offers: {
|
|
'@type': 'Offer',
|
|
price: '0',
|
|
priceCurrency: 'EUR',
|
|
availability: 'https://schema.org/InStock',
|
|
priceValidUntil: '2026-12-31'
|
|
}
|
|
},
|
|
reviewRating: {
|
|
'@type': 'Rating',
|
|
ratingValue: testimonial.rating,
|
|
bestRating: 5,
|
|
worstRating: 1
|
|
},
|
|
author: {
|
|
'@type': 'Person',
|
|
name: testimonial.author.name
|
|
},
|
|
datePublished: testimonial.datePublished,
|
|
reviewBody: testimonial.content,
|
|
headline: testimonial.title
|
|
};
|
|
}
|
|
|
|
export function aggregateRatingSchema(aggregateRating: AggregateRating) {
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'SoftwareApplication',
|
|
name: 'QR Master',
|
|
description: 'Professional QR code generator with dynamic QR codes, analytics, and customization.',
|
|
image: `${SITE_URL}/static/og-image.png`,
|
|
applicationCategory: 'BusinessApplication',
|
|
operatingSystem: 'Web Browser',
|
|
url: SITE_URL,
|
|
offers: {
|
|
'@type': 'Offer',
|
|
price: '0',
|
|
priceCurrency: 'EUR',
|
|
availability: 'https://schema.org/InStock',
|
|
priceValidUntil: '2026-12-31'
|
|
},
|
|
aggregateRating: {
|
|
'@type': 'AggregateRating',
|
|
ratingValue: aggregateRating.ratingValue,
|
|
reviewCount: aggregateRating.reviewCount,
|
|
bestRating: aggregateRating.bestRating,
|
|
worstRating: aggregateRating.worstRating
|
|
}
|
|
};
|
|
}
|
|
|
|
export interface NewsArticleParams {
|
|
headline: string;
|
|
datePublished: string;
|
|
description: string;
|
|
url?: string;
|
|
}
|
|
|
|
export function newsArticleSchema(params: NewsArticleParams) {
|
|
return {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'NewsArticle',
|
|
headline: params.headline,
|
|
datePublished: params.datePublished,
|
|
description: params.description,
|
|
url: params.url || SITE_URL,
|
|
author: {
|
|
'@type': 'Organization',
|
|
name: 'QR Master',
|
|
'@id': `${SITE_URL}/#organization`,
|
|
},
|
|
publisher: {
|
|
'@type': 'Organization',
|
|
name: 'QR Master',
|
|
'@id': `${SITE_URL}/#organization`,
|
|
logo: {
|
|
'@type': 'ImageObject',
|
|
url: `${SITE_URL}/static/og-image.png`,
|
|
width: 1200,
|
|
height: 630,
|
|
},
|
|
},
|
|
mainEntityOfPage: {
|
|
'@type': 'WebPage',
|
|
'@id': params.url || SITE_URL,
|
|
},
|
|
};
|
|
}
|