feat: update specialty URL generation logic and improve indexing strategy for specialties

This commit is contained in:
hamed
2026-07-20 11:54:03 +03:30
parent 30c3fe8637
commit 984511653b
2 changed files with 22 additions and 37 deletions
+15 -29
View File
@@ -158,35 +158,21 @@ async function getClinicUrls(baseUrl, scope) {
// صفحات فرود تخصص per-domain — روی هر دامنه «تخصص + آن شهر» است، پس همه‌جا self. // صفحات فرود تخصص per-domain — روی هر دامنه «تخصص + آن شهر» است، پس همه‌جا self.
// //
// فقط تخصص‌هایی که در همین دامنه واقعاً پزشک دارند اعلام می‌شوند؛ تولید بی‌قید هر ۹۳ // هر تخصص ایندکس‌پذیر روی هر دامنه اعلام می‌شود، مستقل از اینکه امروز در آن شهر پزشک
// تخصص برای هر ۳۵ دامنه هزاران صفحهٔ خالی به گوگل معرفی می‌کرد (thin content انبوه). // دارد یا نه — همان سیاستی که صفحه اعمال می‌کند. اگر sitemap به تعداد پزشکان گره
// می‌خورد، صفحهٔ ایندکس‌پذیرِ اعلام‌نشده می‌ساختیم و با هر افت و خیز داده، URLها از
// sitemap در می‌آمدند و برمی‌گشتند.
// //
// منبع، همان پزشکانی است که این sitemap منتشرشان می‌کند — نه endpoint شمارش. آن // دسته‌های سطح‌بالا (lib/specialtyIndexing) noindex هستند؛ اعلامشان سیگنال متناقض
// endpoint با فیلتر لیست پزشکان اختلاف دارد (مثلاً پزشکِ یافت‌شده از آدرس کلینیک را // به گوگل می‌دهد، پس منتشر نمی‌شوند.
// نمی‌شمارد)، و اگر sitemap از یک منبع و خودِ صفحه از منبع دیگر تصمیم بگیرد، صفحهٔ function getSpecialtyUrls(baseUrl) {
// ایندکس‌پذیرِ اعلام‌نشده یا برعکس می‌سازیم. این‌طور هیچ فراخوانی اضافه‌ای هم ندارد. return specialtiesData
function getSpecialtyUrls(baseUrl, doctors) { .filter((s) => s?.slug && s.status === 1 && isIndexableSpecialty(s))
// والدها noindex هستند (lib/specialtyIndexing)؛ اعلامشان در sitemap سیگنال .map((s) => ({
// متناقض به گوگل می‌دهد، پس اصلاً منتشر نمی‌شوند. url: `${baseUrl}/specialties/${s.slug}`,
const slugById = new Map( changeFrequency: 'weekly',
specialtiesData priority: 0.8,
.filter((s) => s?.slug && s.status === 1 && isIndexableSpecialty(s)) }));
.map((s) => [String(s.id), s.slug])
);
const slugs = new Set();
for (const doctor of doctors) {
for (const specialty of doctor?.specialties ?? []) {
const slug = slugById.get(String(specialty?.id));
if (slug) slugs.add(slug);
}
}
return [...slugs].map((slug) => ({
url: `${baseUrl}/specialties/${slug}`,
changeFrequency: 'weekly',
priority: 0.8,
}));
} }
// هر پست فقط در sitemap دامنهٔ canonical خودش: پست شهریافته → دامنهٔ آن شهر، // هر پست فقط در sitemap دامنهٔ canonical خودش: پست شهریافته → دامنهٔ آن شهر،
@@ -231,7 +217,7 @@ export default async function sitemap() {
const allUrls = [ const allUrls = [
...getStaticPages(baseUrl), ...getStaticPages(baseUrl),
...getSpecialtyUrls(baseUrl, doctors), ...getSpecialtyUrls(baseUrl),
...doctorUrls, ...doctorUrls,
...clinicUrls, ...clinicUrls,
...blogUrls, ...blogUrls,
+7 -8
View File
@@ -54,14 +54,13 @@ export async function generateMetadata({ params }) {
const description = `لیست بهترین پزشکان ${specialty.name} در ${cityName} همراه با نشانی مطب، ساعات کاری و امتیاز بیماران. رزرو نوبت آنلاین متخصص ${specialty.name}.`; const description = `لیست بهترین پزشکان ${specialty.name} در ${cityName} همراه با نشانی مطب، ساعات کاری و امتیاز بیماران. رزرو نوبت آنلاین متخصص ${specialty.name}.`;
const image = "/assets/images/og-image.png"; const image = "/assets/images/og-image.png";
// تخصصی که در این شهر پزشکی ندارد فقط یک عنوان و متن قالبی است — thin content. // ایندکس به تعداد پزشکان شهر گره نمی‌خورد: صفحه روی هر دامنه متن مقدمه و FAQ یکتای
// ایندکس نمی‌شود (و در sitemap هم نمی‌آید)، ولی follow می‌ماند تا لینک‌های // خودش را دارد (buildSpecialtyIntro با هشِ تخصص+شهر واگرا می‌شود) و شهری که امروز
// داخلی‌اش دنبال شوند. به‌محض افزوده‌شدن اولین پزشک، خودکار ایندکس‌پذیر می‌شود. // پزشک ندارد فردا دارد — نوسان ایندکس بدتر از صفحهٔ کم‌پزشک است.
const { total } = await getDoctors(specialty.id, isRoot ? null : matchedCity?.id); //
// تنها استثنا دستهٔ سطح‌بالاست (دارای زیرشاخهٔ فعال): محتوای مستقلی ندارد و با
// دستهٔ سطح‌بالا (دارای زیرشاخهٔ فعال) محتوای مستقلی ندارد و با فرزندانش روی همان // فرزندانش روی همان کوئری رقابت می‌کند؛ صفحهٔ قابل‌ایندکس، همان زیرشاخه است.
// کوئری رقابت می‌کند؛ صفحهٔ قابل‌ایندکس، همان زیرشاخه است. const noindex = isCategorySpecialty(specialty);
const noindex = isCategorySpecialty(specialty) || total === 0;
return { return {
title, title,