feat: رفع لینک /doctor/undefined و متمایزسازی صفحهٔ اصلی شهرها

- ایجاد پرامپت برای رفع خطای 404 ناشی از لینک `/doctor/undefined` در سایت‌های بهبهان و یاسوج.
- افزودن کامپوننت `CityHighlights` برای نمایش محتوای یکتا و آمار پزشکان و تخصص‌های پرمراجعه در صفحهٔ اصلی هر شهر.
- به‌روزرسانی تست‌های مربوط به کامپوننت‌های `ItemDoctor` و `CityHighlights` برای اطمینان از عدم وجود لینک‌های نامعتبر و نمایش صحیح اطلاعات.
- ایجاد سند جدید برای مستندسازی خطاهای Search Console که باگ نیستند و نیاز به رفع ندارند.
- افزودن تست‌های واحد برای توابع `buildCityIntro` و دیگر توابع مرتبط با تولید متن یکتا برای صفحات لیست.
This commit is contained in:
hamed
2026-08-08 19:14:50 +03:30
parent 79747c443c
commit b494473e40
13 changed files with 1047 additions and 28 deletions
+154
View File
@@ -0,0 +1,154 @@
# خطاهای Search Console که باگ نیستند
هر دامنهٔ شهری در Google Search Console چند دستهٔ «مشکل» نشان می‌دهد که خروجیِ **تصمیم‌های
عمدی** سیستم‌اند، نه نقص. این سند مرجع است تا هر بار کسی داشبورد را باز کرد، دوباره دنبال
رفع چیزی نگردد که نباید رفع شود.
سنجش‌های این سند روی `behbahan-nobat.ir` و `yasuj-nobat.ir` در ۵ اوت ۲۰۲۶ گرفته شده‌اند.
> آنچه **واقعاً** باگ بود و جدا رفع می‌شود، اینجا نیست:
> `/doctor/undefined` و یکسان‌بودن صفحهٔ اصلی شهرها.
> ← `.claude/prompt/gsc-undefined-link-and-home-uniqueness.md`
---
## ۱. Excluded by 'noindex' tag
**نمونه:** ۸۰ صفحه در یاسوج، ۱۲ صفحه در بهبهان — عمدتاً `/doctor/<uuid>`.
**چرا عمدی است**
پروفایل پزشکی که از نظام پزشکی وارد شده و خودِ پزشک آن را claim نکرده، محتوای قابل
اتکا نیست. `lib/entityQuality.js` همین را اعمال می‌کند:
```js
export function isUnclaimedDoctor(doctor) {
return doctor?.owner_status !== "claimed";
}
export function isNoindexDoctor(doctor) {
return isThinDoctor(doctor) || isUnclaimedDoctor(doctor);
}
```
همین تابع هم `robots` صفحهٔ پزشک را می‌سازد و هم ورودی `app/sitemap.js` را فیلتر می‌کند، تا
چیزی که noindex است هرگز در sitemap اعلام نشود.
پزشک «بی‌محتوا» هم noindex می‌شود: بدون نام واقعی (شمارهٔ تلفن یا «تست» به‌جای نام)، یا
بدون هیچ تخصص و مکانی.
**چه چیزی این را برمی‌دارد**
فقط claim شدن پروفایل توسط خودِ پزشک، یا کامل شدن نام و تخصص و مکان. **هیچ تغییر کدی
لازم نیست و هیچ تغییر کدی هم نباید انجام شود.**
**چرا `VALIDATE FIX` شکست می‌خورد**
در یاسوج این اعتبارسنجی روی ۲۸ ژوئیه شروع و ۵ اوت `Validation failed` شد. طبیعی است:
اعتبارسنجی یعنی «به گوگل بگو دیگر noindex نیست» در حالی که صفحات عمداً noindex مانده‌اند.
**این دکمه را برای این دسته نزنید.** هر بار شکست می‌خورد و فقط تاریخچهٔ داشبورد را شلوغ می‌کند.
---
## ۲. Alternate page with proper canonical tag
**نمونه در بهبهان:** `/about-us`، `/contact-us`، و دو `/clinic/<uuid>`.
**چرا عمدی است**
استراتژی canonical سه‌دسته است و در بالای `lib/getCanonicalUrl.js` مستند شده:
| دسته | مسیرها | canonical |
|---|---|---|
| C1-a | `/`، `/doctors`، `/clinics`، `/specialties`، `/blogs` | self روی همان دامنهٔ شهری |
| C1-b | `/doctor/[uuid]`، `/clinic/[uuid]`، `/blog/[slug]` | دامنهٔ شهرِ خودِ موجودیت |
| C1-c | `/about-us`، `/contact-us`، `/terms`، `/privacy` | دامنهٔ اصلی `nobat724.com` |
`/about-us` و `/contact-us` روی همهٔ دامنه‌ها محتوای یکسان دارند، پس اعتبارشان عمداً روی
دامنهٔ اصلی تجمیع می‌شود. کلینیکی هم که شهرش بهبهان نیست، canonical‌اش به دامنهٔ شهر خودش
می‌رود تا یک موجودیت روی دو دامنه دو صفحهٔ رقیب نسازد.
`Alternate page with proper canonical tag` در Search Console **گزارش اطلاعاتی** است، نه خطا.
عبارت `proper` در عنوانش یعنی گوگل canonical ما را پذیرفته. این دقیقاً نتیجهٔ مطلوب است.
---
## ۳. Page with redirect
**نمونه:** `http://behbahan-nobat.ir/` — یک صفحه.
ریدایرکت `http` به `https` است. رفتار درست و لازم. کاری ندارد.
---
## ۴. آدرس‌های قدیمی بلاگ که ۴۰۴ می‌دهند
**نمونه در بهبهان:** چهار آدرس با slug انگلیسی، مثل
`/blog/warning-signs-sleep-elderly` و `/blog/alamat-tahooe-mokarrar-dar-bimaran-sartani`.
**چه اتفاقی افتاده**
فرمت slug مقاله‌ها عوض شده. slug امروز فارسی است با پسوند هگز هشت‌کاراکتری — نمونهٔ
واقعی از API:
```
آلرژی-دارویی-تشخیص-علائم-خطرناک-و-ضرورت-مراجعه-به-پزشک-58d2e880
```
slugهای انگلیسیِ قبلی دیگر وجود ندارند و **نگاشت قدیم‌به‌جدید هیچ‌جا ذخیره نشده**.
**تصمیم: ۴۰۴ پذیرفته می‌شود**
۴۰۴ پاسخ صحیح برای محتوایی است که دیگر با آن نشانی وجود ندارد. گوگل خودش این آدرس‌ها را
از ایندکس حذف می‌کند. ساختن redirect نیازمند پیدا کردن دستیِ معادل هر کدام است و فقط
چهار آدرس در میان است.
بررسی شد که خودِ رفتار ۴۰۴ سالم است — سه نشانیِ ناموجود، هر سه `404`:
```
/blog/warning-signs-sleep-elderly → 404
/blog/definitely-does-not-exist-xyz-123 → 404
/blog/alamat-tahooe-mokarrar-dar-bimaran-sartani → 404
```
**اگر روزی slug دوباره عوض شد**, این‌بار قبلش نگاشت را نگه دارید. تکرار همین وضعیت با
تعداد بیشتر، دیگر با «۴۰۴ بپذیر» حل نمی‌شود.
---
## ۵. Discovered — currently not indexed
**نمونه:** ۷۳ آدرس در یاسوج — صفحات تخصص، کلینیک، پزشک و بلاگ.
**معنایش**
گوگل این آدرس‌ها را پیدا کرده اما هنوز crawl نکرده. تقریباً همیشه یعنی سایت در نظر گوگل
اولویت crawl پایینی دارد، نه اینکه صفحه خراب باشد.
**چرا اینجا رخ می‌دهد**
دو عامل، و هر دو در حال رفع‌اند یا عمدی‌اند:
۱. سهم بزرگی از صفحات دامنه noindex است (بند ۱). سایتی که بیشتر صفحاتش noindex است،
سیگنال ضعیف‌تری برای صرف بودجهٔ crawl می‌دهد.
۲. صفحهٔ اصلی شهرها تا پیش از این ۹۹٪ شبیه هم بود. این مورد **باگ است** و جدا رفع می‌شود
(پرامپت بالای این سند).
**کاری که نباید کرد**
آدرس‌ها را دستی و دسته‌ای برای ایندکس submit نکنید. مشکل کمبودِ کشف نیست؛ اعلام دوبارهٔ
همان آدرس‌ها چیزی را جلو نمی‌اندازد.
---
## خلاصهٔ تصمیم
| دستهٔ Search Console | وضعیت | اقدام |
|---|---|---|
| Excluded by 'noindex' tag | عمدی | هیچ. `Validate fix` نزنید |
| Alternate page with proper canonical | عمدی و مطلوب | هیچ |
| Page with redirect | طبیعی | هیچ |
| Not found (404) — بلاگ‌های قدیمی | پذیرفته‌شده | هیچ |
| Not found (404) — `/doctor/undefined` | **باگ** | پرامپت جداگانه |
| Duplicate, Google chose different canonical | **باگ محتوایی** | پرامپت جداگانه |
| Discovered — currently not indexed | پیامد دو مورد بالا | بعد از رفع آن‌ها دوباره سنجیده شود |