SEO polish: facility structured data + trim homepage description
CI/CD / CI · dotnet build (push) Successful in 1m26s
CI/CD / Deploy · hamkadr (push) Successful in 1m57s

- Add SeoJsonLd.MedicalOrganization (Hospital/MedicalClinic schema with address, geo coords, and
  aggregateRating) and emit it on facility detail pages — only for real named facilities (not the
  «نامشخص» placeholder) — so Google can show a rich place result. Facility pages previously had no
  JSON-LD at all.
- Trim the homepage meta description from 193 to ~135 chars so Google doesn''t truncate it.

(Shift-detail canonical was a non-issue: the layout correctly omits canonical only on noindex pages,
which is what the audited past shift was.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-22 16:03:28 +03:30
parent 410fc86c60
commit 219207ad68
3 changed files with 30 additions and 1 deletions
@@ -149,3 +149,12 @@
{ {
<partial name="_NeshanMap" model="Model.MapKey" /> <partial name="_NeshanMap" model="Model.MapKey" />
} }
@* Place/clinic structured data — only for a real named facility (not the «نامشخص» placeholder). *@
@if (JobsMedical.Web.Services.SeoJsonLd.HasRealEmployer(f))
{
var bu = $"{ViewContext.HttpContext.Request.Scheme}://{ViewContext.HttpContext.Request.Host}";
@Html.Raw("<script type=\"application/ld+json\">"
+ JobsMedical.Web.Services.SeoJsonLd.MedicalOrganization(f, bu, Model.AvgRating, Model.RatingCount)
+ "</script>")
}
+1 -1
View File
@@ -2,7 +2,7 @@
@model IndexModel @model IndexModel
@{ @{
ViewData["Title"] = null; // use default site title for the home page (best for SEO) ViewData["Title"] = null; // use default site title for the home page (best for SEO)
ViewData["Description"] = "همکادر؛ سریع‌ترین راه برای کادر درمان (پزشک، پرستار، ماما، تکنسین) جهت یافتن شیفت و موقعیت استخدامی در بیمارستان‌ها و کلینیک‌های تهران. به‌جای گشتن در کانال‌های تلگرام و بله، همه فرصت‌ها یک‌جا."; ViewData["Description"] = "یافتن شیفت و موقعیت استخدامی کادر درمان (پزشک، پرستار، ماما، تکنسین) در بیمارستان‌ها و کلینیک‌های تهران همهٔ فرصت‌ها یک‌جا در همکادر.";
} }
<section class="hero"> <section class="hero">
+20
View File
@@ -100,6 +100,26 @@ public static class SeoJsonLd
return Fix(JsonSerializer.Serialize(obj, Opts)); return Fix(JsonSerializer.Serialize(obj, Opts));
} }
/// <summary>schema.org structured data for a facility page — a Hospital/MedicalClinic with its
/// address, map coordinates, and aggregate review rating, so Google can show a rich place result.</summary>
public static string MedicalOrganization(Facility f, string baseUrl, double avgRating = 0, int ratingCount = 0)
{
var schemaType = f.Type == FacilityType.Hospital ? "Hospital" : "MedicalClinic";
var obj = new Dictionary<string, object?>
{
["@context"] = "https://schema.org",
["@type"] = schemaType,
["name"] = f.Name,
["url"] = $"{baseUrl}/Facilities/Details/{f.Id}",
["address"] = new { type = "PostalAddress", addressLocality = f.City?.Name, addressCountry = "IR", streetAddress = f.Address },
};
if (f.Lat is double la && f.Lng is double lo)
obj["geo"] = new { type = "GeoCoordinates", latitude = la, longitude = lo };
if (ratingCount > 0)
obj["aggregateRating"] = new { type = "AggregateRating", ratingValue = Math.Round(avgRating, 1), reviewCount = ratingCount };
return Fix(JsonSerializer.Serialize(obj, Opts));
}
public static string Organization(string baseUrl) => Fix(JsonSerializer.Serialize(new Dictionary<string, object?> public static string Organization(string baseUrl) => Fix(JsonSerializer.Serialize(new Dictionary<string, object?>
{ {
["@context"] = "https://schema.org", ["@context"] = "https://schema.org",