Add structured data for search engines
This commit is contained in:
+130
-2
@@ -55,6 +55,14 @@ export async function generateMetadata({ params }) {
|
|||||||
type: "article",
|
type: "article",
|
||||||
publishedTime: post.frontmatter.date,
|
publishedTime: post.frontmatter.date,
|
||||||
authors: [post.frontmatter.author],
|
authors: [post.frontmatter.author],
|
||||||
|
url: `https://communityrule.com/blog/${slug}`,
|
||||||
|
siteName: "CommunityRule",
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary_large_image",
|
||||||
|
title: post.frontmatter.title,
|
||||||
|
description: post.frontmatter.description,
|
||||||
|
creator: "@communityrule",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -79,11 +87,130 @@ export default async function BlogPostPage({ params }) {
|
|||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get related articles (for now, just get other posts)
|
// Get related articles with improved algorithm
|
||||||
const allPosts = getAllPosts();
|
const allPosts = getAllPosts();
|
||||||
const relatedArticles = allPosts; // Pass all posts to RelatedArticles component for filtering
|
|
||||||
|
// Simple related articles algorithm based on content similarity
|
||||||
|
const getRelatedArticles = (currentPost, allPosts, limit = 3) => {
|
||||||
|
const otherPosts = allPosts.filter((p) => p.slug !== currentPost.slug);
|
||||||
|
|
||||||
|
// Score posts based on content similarity
|
||||||
|
const scoredPosts = otherPosts.map((post) => {
|
||||||
|
let score = 0;
|
||||||
|
|
||||||
|
// Check for similar keywords in title and description
|
||||||
|
const currentTitle = currentPost.frontmatter.title.toLowerCase();
|
||||||
|
const currentDesc = currentPost.frontmatter.description.toLowerCase();
|
||||||
|
const postTitle = post.frontmatter.title.toLowerCase();
|
||||||
|
const postDesc = post.frontmatter.description.toLowerCase();
|
||||||
|
|
||||||
|
// Common keywords that indicate similarity
|
||||||
|
const keywords = [
|
||||||
|
"community",
|
||||||
|
"conflict",
|
||||||
|
"decision",
|
||||||
|
"governance",
|
||||||
|
"security",
|
||||||
|
"trust",
|
||||||
|
"collaboration",
|
||||||
|
"organization",
|
||||||
|
];
|
||||||
|
|
||||||
|
keywords.forEach((keyword) => {
|
||||||
|
if (currentTitle.includes(keyword) && postTitle.includes(keyword))
|
||||||
|
score += 3;
|
||||||
|
if (currentDesc.includes(keyword) && postDesc.includes(keyword))
|
||||||
|
score += 2;
|
||||||
|
if (currentTitle.includes(keyword) && postDesc.includes(keyword))
|
||||||
|
score += 1;
|
||||||
|
if (currentDesc.includes(keyword) && postTitle.includes(keyword))
|
||||||
|
score += 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
return { ...post, score };
|
||||||
|
});
|
||||||
|
|
||||||
|
// Sort by score and return top posts
|
||||||
|
return scoredPosts
|
||||||
|
.sort((a, b) => b.score - a.score)
|
||||||
|
.slice(0, limit)
|
||||||
|
.map(({ score, ...post }) => post); // Remove score from final result
|
||||||
|
};
|
||||||
|
|
||||||
|
const relatedArticles = getRelatedArticles(post, allPosts);
|
||||||
|
|
||||||
|
// Generate structured data for search engines
|
||||||
|
const structuredData = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "Article",
|
||||||
|
headline: post.frontmatter.title,
|
||||||
|
description: post.frontmatter.description,
|
||||||
|
author: {
|
||||||
|
"@type": "Person",
|
||||||
|
name: post.frontmatter.author,
|
||||||
|
},
|
||||||
|
publisher: {
|
||||||
|
"@type": "Organization",
|
||||||
|
name: "CommunityRule",
|
||||||
|
url: "https://communityrule.com",
|
||||||
|
logo: {
|
||||||
|
"@type": "ImageObject",
|
||||||
|
url: "https://communityrule.com/assets/Logo.svg",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
datePublished: post.frontmatter.date,
|
||||||
|
dateModified: post.frontmatter.date,
|
||||||
|
mainEntityOfPage: {
|
||||||
|
"@type": "WebPage",
|
||||||
|
"@id": `https://communityrule.com/blog/${post.slug}`,
|
||||||
|
},
|
||||||
|
url: `https://communityrule.com/blog/${post.slug}`,
|
||||||
|
articleSection: "Community Building",
|
||||||
|
keywords: ["community", "governance", "decision making", "collaboration"],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Breadcrumb structured data
|
||||||
|
const breadcrumbData = {
|
||||||
|
"@context": "https://schema.org",
|
||||||
|
"@type": "BreadcrumbList",
|
||||||
|
itemListElement: [
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
position: 1,
|
||||||
|
name: "Home",
|
||||||
|
item: "https://communityrule.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
position: 2,
|
||||||
|
name: "Blog",
|
||||||
|
item: "https://communityrule.com/blog",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@type": "ListItem",
|
||||||
|
position: 3,
|
||||||
|
name: post.frontmatter.title,
|
||||||
|
item: `https://communityrule.com/blog/${post.slug}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
{/* Structured Data */}
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify(structuredData),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<script
|
||||||
|
type="application/ld+json"
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: JSON.stringify(breadcrumbData),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
<div className="min-h-screen bg-[#F4F3F1] relative overflow-hidden">
|
<div className="min-h-screen bg-[#F4F3F1] relative overflow-hidden">
|
||||||
{/* Content Banner */}
|
{/* Content Banner */}
|
||||||
<ContentBanner post={post} />
|
<ContentBanner post={post} />
|
||||||
@@ -140,5 +267,6 @@ export default async function BlogPostPage({ params }) {
|
|||||||
{/* Ask Organizer Section */}
|
{/* Ask Organizer Section */}
|
||||||
<AskOrganizer {...askOrganizerData} variant="inverse" />
|
<AskOrganizer {...askOrganizerData} variant="inverse" />
|
||||||
</div>
|
</div>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user