Breadcrumbs: visible trail + BreadcrumbList JSON-LD
CI/CD / CI · dotnet build (push) Successful in 2m8s
CI/CD / Deploy · hamkadr (push) Has been cancelled

Add SeoJsonLd.Breadcrumb + Crumb record + _Breadcrumbs partial, and wire a trail
into the Jobs/Shifts list (landing) and detail pages: خانه › استخدام/شیفت › {نقش}
› {شهر|عنوان}. The role crumb links to the role landing page (more internal
links), and Google can show the breadcrumb path in results. Detail pages emit it
alongside the existing JobPosting JSON-LD.

Improvement 5 of the backlog (SEO).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-20 19:12:38 +03:30
parent 142136ebc9
commit 3edd21d2b6
9 changed files with 89 additions and 2 deletions
+23
View File
@@ -125,9 +125,32 @@ public static class SeoJsonLd
},
}, Opts));
/// <summary>BreadcrumbList JSON-LD from an ordered crumb trail (relative URLs are made absolute).
/// Google can then show the breadcrumb path in search results.</summary>
public static string Breadcrumb(IReadOnlyList<Crumb> items, string baseUrl)
{
var els = new List<object>();
for (var i = 0; i < items.Count; i++)
{
var el = new Dictionary<string, object?> { ["type"] = "ListItem", ["position"] = i + 1, ["name"] = items[i].Name };
if (!string.IsNullOrEmpty(items[i].Url))
el["item"] = items[i].Url!.StartsWith("http") ? items[i].Url : baseUrl + items[i].Url;
els.Add(el);
}
return Fix(JsonSerializer.Serialize(new Dictionary<string, object?>
{
["@context"] = "https://schema.org",
["@type"] = "BreadcrumbList",
["itemListElement"] = els,
}, Opts));
}
// Nested anonymous objects use "type"/"queryyy" placeholders for @type / query-input;
// restore the @-prefixed schema.org keys here.
private static string Fix(string json) => json
.Replace("\"type\":", "\"@type\":")
.Replace("\"queryyy\":", "\"query-input\":");
}
/// <summary>One step in a breadcrumb trail. <see cref="Url"/> is null for the current (last) page.</summary>
public record Crumb(string Name, string? Url = null);