feat(breadcrumb): implement BreadcrumbList structure and validation for SEO compliance
This commit is contained in:
@@ -189,7 +189,9 @@ async function Blog({ params }) {
|
||||
itemListElement: [
|
||||
{ "@type": "ListItem", position: 1, name: "خانه", item: origin },
|
||||
{ "@type": "ListItem", position: 2, name: "مقالات", item: `${origin}/blogs` },
|
||||
{ "@type": "ListItem", position: 3, name: blog.title },
|
||||
// آیتم آخر بدون `item` کل BreadcrumbList را نامعتبر میکند
|
||||
{ "@type": "ListItem", position: 3, name: blog.title,
|
||||
item: `${origin}/blog/${slug}` },
|
||||
],
|
||||
}
|
||||
: null;
|
||||
|
||||
@@ -5,6 +5,7 @@ import Layout from "@/components/layout/StLayout";
|
||||
import { getStateInfo } from "@/lib/getStateInfo";
|
||||
import { buildClinicParams } from "@/helper";
|
||||
import { listingRobots } from "@/lib/listingRobots";
|
||||
import { getRequestOrigin } from "@/lib/getCanonicalUrl";
|
||||
|
||||
export async function generateMetadata({ searchParams }) {
|
||||
const awaitedParams = await searchParams;
|
||||
@@ -68,12 +69,16 @@ export default async function Clinics({ searchParams }) {
|
||||
if (isNextRedirectError(err)) throw err;
|
||||
}
|
||||
|
||||
// origin برای BreadcrumbList لازم است: گوگل در itemListElement آدرس مطلق میخواهد.
|
||||
const origin = await getRequestOrigin();
|
||||
|
||||
return (
|
||||
<Layout name="/clinics">
|
||||
<ClinicsPage
|
||||
clinics={clinics}
|
||||
matchedCity={matchedCity}
|
||||
matchedState={matchedState}
|
||||
origin={origin}
|
||||
/>
|
||||
</Layout>
|
||||
);
|
||||
|
||||
+12
-22
@@ -1,41 +1,31 @@
|
||||
import Link from "next/link";
|
||||
import { safeJsonLd } from "@/lib/sanitize";
|
||||
import { buildBreadcrumbJsonLd, normalizeBreadcrumb } from "@/lib/breadcrumb";
|
||||
|
||||
// Breadcrumb واحد سایت: ظاهر دقیقاً همان نسخهٔ قبلی (همان کلاسها، همان جداکنندهٔ «>»)
|
||||
// ولی ساختار سمانتیک درست (nav/ol/a) و BreadcrumbList از همان یک منبع داده تولید میشود
|
||||
// تا نسخهٔ بصری و schema هرگز واگرا نشوند.
|
||||
//
|
||||
// list: Array<string | { name, href }> — آیتم آخر همیشه صفحهٔ جاری است (بدون لینک).
|
||||
// list: Array<string | { name, href }> — به آیتم آخر (صفحهٔ جاری) هم href بده؛
|
||||
// ظاهرش عوض نمیشود (هرگز لینک نمیشود) ولی schema بدون `item` نامعتبر است.
|
||||
// `origin` هم لازم است، چون گوگل URL مطلق میخواهد؛ بدون آن JSON-LD منتشر نمیشود.
|
||||
|
||||
const ITEM_CLASS = "text-[16px] font-normal";
|
||||
|
||||
function normalize(list) {
|
||||
return list
|
||||
.map((item) => (typeof item === "string" ? { name: item } : item))
|
||||
.filter((item) => item?.name);
|
||||
}
|
||||
|
||||
function Pageguide({ list = [], origin }) {
|
||||
const items = normalize(list);
|
||||
const items = normalizeBreadcrumb(list);
|
||||
if (items.length === 0) return null;
|
||||
|
||||
const jsonLd = {
|
||||
"@context": "https://schema.org",
|
||||
"@type": "BreadcrumbList",
|
||||
itemListElement: items.map((item, idx) => ({
|
||||
"@type": "ListItem",
|
||||
position: idx + 1,
|
||||
name: item.name,
|
||||
...(item.href && origin && { item: `${origin}${item.href}` }),
|
||||
})),
|
||||
};
|
||||
const jsonLd = buildBreadcrumbJsonLd(items, origin);
|
||||
|
||||
return (
|
||||
<>
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: safeJsonLd(jsonLd) }}
|
||||
/>
|
||||
{jsonLd && (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{ __html: safeJsonLd(jsonLd) }}
|
||||
/>
|
||||
)}
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol className="flex items-center justify-start gap-1">
|
||||
{items.map((item, idx) => {
|
||||
|
||||
@@ -262,7 +262,10 @@ async function Doctor({ params }) {
|
||||
itemListElement: [
|
||||
{ "@type": "ListItem", position: 1, name: "خانه", item: origin },
|
||||
{ "@type": "ListItem", position: 2, name: specialtyLabel, item: specialtyItem },
|
||||
{ "@type": "ListItem", position: 3, name: doctorTitle(doctor.name) },
|
||||
// آیتم آخر هم باید `item` داشته باشد (URL خودِ همین صفحه)؛ بدون آن
|
||||
// سرچکنسول خطای بحرانی Missing field "item" میدهد.
|
||||
{ "@type": "ListItem", position: 3, name: doctorTitle(doctor.name),
|
||||
item: `${origin}/doctor/${slug}` },
|
||||
],
|
||||
}
|
||||
: null;
|
||||
|
||||
@@ -141,6 +141,7 @@ async function SpecialtyDetail({ params }) {
|
||||
intro={intro}
|
||||
faq={faq}
|
||||
origin={origin}
|
||||
slug={slug}
|
||||
/>
|
||||
</Layout>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user