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
+50 -2
View File
@@ -1,10 +1,58 @@
@page "/blog"
@model DrSousan.Api.Pages.Blog.BlogIndexModel
@{
var blogBase = Environment.GetEnvironmentVariable("SITE_BASE_URL")?.TrimEnd('/') ?? (Request.Scheme + "://" + Request.Host);
var catQs = string.IsNullOrEmpty(Model.ActiveCat) ? "" : "&category=" + Model.ActiveCat;
string PageUrl(int p) => p <= 1
? blogBase + "/blog" + (string.IsNullOrEmpty(Model.ActiveCat) ? "" : "?category=" + Model.ActiveCat)
: blogBase + "/blog?pg=" + p + catQs;
var blogDesc = "مقالات تخصصی دکتر سوسن آل‌طه درباره زیبایی پوست، بوتاکس، فیلر، لیزر و مراقبت از پوست.";
}
@section Head {
<title>@ViewData["Title"]</title>
<meta name="description" content="مقالات تخصصی دکتر سوسن آل‌طه درباره زیبایی پوست، بوتاکس، فیلر، لیزر و مراقبت از پوست." />
<link rel="canonical" href="@((Environment.GetEnvironmentVariable("SITE_BASE_URL")?.TrimEnd('/') ?? (Request.Scheme + "://" + Request.Host)) + "/blog")" />
<meta name="description" content="@blogDesc" />
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1" />
<meta name="author" content="@ViewData["SiteName"]" />
<meta name="theme-color" content="#B8955A" />
<link rel="canonical" href="@PageUrl(Model.CurrentPage)" />
@if (Model.CurrentPage > 1) { <link rel="prev" href="@PageUrl(Model.CurrentPage - 1)" /> }
@if (Model.CurrentPage < Model.TotalPages) { <link rel="next" href="@PageUrl(Model.CurrentPage + 1)" /> }
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:site_name" content="@ViewData["SiteName"]" />
<meta property="og:title" content="@ViewData["Title"]" />
<meta property="og:description" content="@blogDesc" />
<meta property="og:url" content="@PageUrl(Model.CurrentPage)" />
<meta property="og:locale" content="fa_IR" />
<!-- Twitter -->
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="@ViewData["Title"]" />
<meta name="twitter:description" content="@blogDesc" />
<!-- Structured data: Blog + breadcrumb -->
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@type": "Blog",
"name": "@ViewData["Title"]",
"description": "@blogDesc",
"url": "@(blogBase)/blog",
"inLanguage": "fa-IR",
"publisher": { "@@type": "Organization", "name": "@ViewData["SiteName"]", "url": "@blogBase" }
}
</script>
<script type="application/ld+json">
{
"@@context": "https://schema.org",
"@@type": "BreadcrumbList",
"itemListElement": [
{ "@@type": "ListItem", "position": 1, "name": "خانه", "item": "@blogBase/" },
{ "@@type": "ListItem", "position": 2, "name": "وبلاگ", "item": "@blogBase/blog" }
]
}
</script>
<style>
/* ─── Blog Hero ─────────────────────────────────────────────── */
.blog-hero{background:linear-gradient(135deg,var(--gold-pale) 0%,#EDE0CA 100%);padding:6rem 2rem 3rem;text-align:center}