feat(seo): complete OG/Twitter/structured-data coverage + clean encoding
CI/CD / CI · dotnet build (push) Successful in 2m1s
CI/CD / Deploy · drsousan (push) Successful in 35s

Blog list (/blog): add robots, full Open Graph + Twitter, Blog +
BreadcrumbList JSON-LD, per-page self-canonical, and rel=prev/next for
paginated pages.

Blog post: add robots, og:site_name, article:published_time /
modified_time / author / section, twitter:image, og:image:alt, and a
BreadcrumbList JSON-LD (Home → Blog → Category → Post).

Gallery (/gallery): add robots, full OG + Twitter (with first image as
og:image), ImageGallery + BreadcrumbList JSON-LD.

Encoding: register HtmlEncoder.Create(UnicodeRanges.All) so Persian text
in meta tags and JSON-LD renders literally instead of &#xXXXX; entities
(smaller, cleaner output; friendlier to SEO validators).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-25 13:44:37 +03:30
parent 5a1f1a8ccb
commit 00a138fe46
4 changed files with 140 additions and 5 deletions
+30 -1
View File
@@ -18,17 +18,28 @@
@if (!string.IsNullOrEmpty(ViewData["Keywords"]?.ToString())) {
<meta name="keywords" content="@ViewData["Keywords"]" />
}
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1" />
<meta name="author" content="@post.Author" />
<meta property="og:type" content="article" />
<meta property="og:site_name" content="@ViewData["SiteName"]" />
<meta property="og:title" content="@ViewData["Title"]" />
<meta property="og:description" content="@ViewData["MetaDesc"]" />
<meta property="og:url" content="@canonicalUrl" />
<meta property="og:locale" content="fa_IR" />
@if (!string.IsNullOrEmpty(pubDate)) { <meta property="article:published_time" content="@pubDate" /> }
<meta property="article:modified_time" content="@updDate" />
<meta property="article:author" content="@post.Author" />
@if (post.Category != null) { <meta property="article:section" content="@post.Category.Name" /> }
@if (!string.IsNullOrEmpty(ogImage)) {
<meta property="og:image" content="@(ogImage.StartsWith("http") ? ogImage : baseUrl + ogImage)" />
<meta property="og:image" content="@(ogImage.StartsWith("http") ? ogImage : baseUrl + ogImage)" />
<meta property="og:image:alt" content="@post.Title" />
}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="@ViewData["Title"]" />
<meta name="twitter:description" content="@ViewData["MetaDesc"]" />
@if (!string.IsNullOrEmpty(ogImage)) {
<meta name="twitter:image" content="@(ogImage.StartsWith("http") ? ogImage : baseUrl + ogImage)" />
}
<link rel="canonical" href="@canonicalUrl" />
<script type="application/ld+json">
@@ -52,6 +63,24 @@
}
</script>
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@type": "BreadcrumbList",
"itemListElement": [
{ "@@type": "ListItem", "position": 1, "name": "خانه", "item": "@baseUrl/" },
{ "@@type": "ListItem", "position": 2, "name": "وبلاگ", "item": "@baseUrl/blog" }@(post.Category != null ? "," : "")
@if (post.Category != null) {
@:{ "@@type": "ListItem", "position": 3, "name": "@J(post.Category.Name)", "item": "@baseUrl/blog?category=@post.Category.Slug" },
@:{ "@@type": "ListItem", "position": 4, "name": "@J(post.Title)", "item": "@canonicalUrl" }
}
else {
@:{ "@@type": "ListItem", "position": 3, "name": "@J(post.Title)", "item": "@canonicalUrl" }
}
]
}
</script>
<style>
/* ─── Post Layout ──────────────────────────────────────────────── */
.post-layout{max-width:1100px;margin:0 auto;padding:5rem 2rem 3rem;display:grid;grid-template-columns:1fr 320px;gap:3rem;align-items:start}