chore(prompt): record SEO audit and split remaining work

P1-P11 of the live SEO audit are implemented and verified against a local
production build, so the audit file becomes a reference rather than a
task list: it now records what shipped, the root causes that differed
from the original hypotheses, and the deliberate trade-offs.

Remaining work is split into smaller prompts, ordered by dependency:

- seo-post-deploy-verification: the acceptance criteria were "curl on
  production" but were only run against a local build
- blog-city-scoping-activate: blocked on the backend blog city column
- sitemap-simplify-with-city: drops the 35-sweep workaround once the
  doctors list exposes city

Each names its blocking dependency and carries reference numbers so a
regression is visible.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
hamed
2026-07-19 08:09:21 +03:30
co-authored by Claude Opus 4.8
parent c64a4a7d69
commit 21f5f07bc6
4 changed files with 892 additions and 0 deletions
@@ -0,0 +1,148 @@
<div dir="rtl" markdown="1">
# تأیید زندهٔ فیکس‌های SEO بعد از deploy
## پروژه
`nobat724_front`
## زمینه
فیکس‌های ممیزی SEO (P1–P11 از `seo-critical-fixes-live-audit.md`) پیاده و تست شده‌اند، اما تأیید روی **build محلی production** انجام شد، نه روی دامنه‌های واقعی:
```
NEXT_PUBLIC_API_URL=https://clinic-pro.ir DEV_MODE=FALSE npm run build
NODE_TLS_REJECT_UNAUTHORIZED=0 npx next start -p 3111
# سپس curl با هدر Host برای شبیه‌سازی دامنه‌های شهری
```
معیارهای پذیرش اصلی «curl روی production» بودند. این پرامپت همان‌ها را روی دامنه‌های واقعی اجرا می‌کند.
**دو نکتهٔ حیاتی قبل از تفسیر هر خروجی:**
۱. **`DEV_MODE` باید `FALSE` باشد.** با `DEV_MODE=TRUE` کل سایت `noindex, nofollow, nocache` می‌گیرد و همهٔ تست‌های robots بی‌معنی می‌شوند. این مقدار در `next.config.js` از طریق `env` در **زمان build** درون کد جاسازی می‌شود — تغییر آن در runtime بی‌اثر است و نیاز به build مجدد دارد.
۲. **تغییرات هنوز commit نشده‌اند** (وضعیت کاری). قبل از deploy باید commit و push شوند.
## هدف
اجرای کامل معیارهای پذیرش روی production و ثبت خروجی واقعی؛ و در صورت اختلاف با نتایج محلی، ریشه‌یابی.
## وظایف
### ۱. پیش از deploy
- `git status` را بررسی کن؛ تغییرات SEO را commit کن.
- تأیید کن build با `DEV_MODE=FALSE` انجام می‌شود.
- تأیید کن `NEXT_PUBLIC_API_URL` در محیط production درست است (نه `clinic-pro.ddev.site` که مقدار فعلی `.env` محلی است).
### ۲. اجرای معیارهای پذیرش روی production
بعد از deploy، این‌ها را اجرا کن و **خروجی واقعی هر کدام را در گزارش نقل کن**:
```bash
# P3 — noindex سیستمیک لیست‌ها (باید خالی باشد)
for h in yasuj-nobat.ir tehran-nobat.ir yazd-nobat.ir nobat724.com; do
for p in doctors clinics; do
printf "%s/%s: " "$h" "$p"
curl -s "https://$h/$p" | grep -o '<meta name="robots"[^>]*>' | head -1; echo
done
done
# P3 — فیلتر واقعی باید noindex بماند
curl -s "https://yasuj-nobat.ir/doctors?name=x" | grep -o 'content="noindex[^"]*"'
curl -s "https://yasuj-nobat.ir/doctors?specialty=پوست%20و%20مو&gender=woman" | grep -o 'content="noindex[^"]*"'
# P4 — C1-a self-canonical
for h in yazd-nobat.ir nobat724.com yasuj-nobat.ir; do
for p in "" doctors clinics specialties blogs; do
printf "%s/%s -> " "$h" "$p"
curl -s "https://$h/$p" | grep -o 'rel="canonical" href="[^"]*"'; echo
done
done
# P4 — C1-c استاتیک مشترک → دامنهٔ اصلی
curl -s https://yasuj-nobat.ir/about-us | grep -o 'rel="canonical" href="[^"]*"'
curl -s https://yasuj-nobat.ir/contact-us | grep -o 'rel="canonical" href="[^"]*"'
# P4 — C1-b موجودیت → دامنهٔ شهر خودش
D=beaca548-816f-4613-937d-360db01bd7c8 # شهر: یاسوج
C=9c163d69-0051-4745-a423-c830135b1c01 # شهر: یاسوج
curl -s "https://nobat724.com/doctor/$D" | grep -o 'rel="canonical" href="[^"]*"' # → yasuj-nobat.ir
curl -s "https://yazd-nobat.ir/doctor/$D" | grep -o 'rel="canonical" href="[^"]*"' # → yasuj-nobat.ir
curl -s "https://nobat724.com/clinic/$C" | grep -o 'rel="canonical" href="[^"]*"' # → yasuj-nobat.ir
# P4 — هماهنگی JSON-LD با canonical (تناقض زندهٔ قبلی)
curl -s "https://nobat724.com/clinic/$C" | grep -o '"url":"[^"]*"'
# M3-B — pagination
curl -s "https://yasuj-nobat.ir/doctors?page=2" | grep -o 'rel="canonical" href="[^"]*"' # → ?page=2
curl -s "https://yasuj-nobat.ir/doctors?page=1" | grep -o 'rel="canonical" href="[^"]*"' # → بدون page
# P6 — Soft-404 (هر چهار باید 404 بدهند)
for p in doctor/invalid-xxx clinic/invalid-xxx blog/invalid-xxx specialties/invalid-slug; do
printf "%s -> " "$p"; curl -o /dev/null -s -w "%{http_code}\n" "https://nobat724.com/$p"
done
# و موجودیت معتبر باید 200 بماند
curl -o /dev/null -s -w "valid doctor: %{http_code}\n" "https://nobat724.com/doctor/$D"
# P7 — دقیقاً یک h1 و شهرمحور
for p in doctors clinics specialties; do
printf "%s h1count=" "$p"; curl -s "https://yasuj-nobat.ir/$p" | grep -c "<h1"
curl -s "https://yasuj-nobat.ir/$p" | grep -o "<h1[^>]*>[^<]*"
done
curl -s "https://yasuj-nobat.ir/clinic/$C" | grep -c "<h1" # → 1 (قبلاً 4)
curl -s "https://nobat724.com/doctors" | grep -o "<title>[^<]*" # → بدون «نوبت 724» به‌جای شهر
# P8 — breadcrumb
curl -s https://yasuj-nobat.ir/clinics | grep -c 'aria-label="breadcrumb"'
curl -s https://yasuj-nobat.ir/clinics | grep -c 'BreadcrumbList'
curl -s "https://yasuj-nobat.ir/clinic/$C" | grep -c 'aria-label="breadcrumb"'
# P9 — صفحات فرود تخصص
curl -o /dev/null -s -w "%{http_code}\n" https://tehran-nobat.ir/specialties/dermatology # → 200
curl -o /dev/null -s -w "%{http_code}\n" https://tehran-nobat.ir/specialties/invalid-slug # → 404
curl -s "https://tehran-nobat.ir/doctors?specialty=پوست%20و%20مو" | grep -o 'rel="canonical" href="[^"]*"'
curl -s https://yasuj-nobat.ir/specialties | grep -c "doctors?specialty=" # → 0
# P5 — sitemap
curl -s https://nobat724.com/sitemap.xml | grep -c "<loc>"
curl -s https://yasuj-nobat.ir/sitemap.xml | grep -c "<loc>"
curl -s https://yasuj-nobat.ir/sitemap.xml | grep -c "$D" # → 1
curl -s https://nobat724.com/sitemap.xml | grep -c "$D" # → 0
curl -s https://yasuj-nobat.ir/sitemap.xml | grep -c "9c163d69" # → 0 (کلینیک بی‌محتوا)
# P11 — FAQPage بدون شکستن اسکیماهای موجود
curl -s https://tehran-nobat.ir/specialties/dermatology | grep -c FAQPage
curl -s "https://yasuj-nobat.ir/doctor/$D" | grep -c FAQPage
curl -s "https://yasuj-nobat.ir/doctor/$D" | grep -c Physician # باید سالم بماند
```
### ۳. اعتبارسنجی ساختاریافته
- **Rich Results Test گوگل** روی یک صفحهٔ پزشک و یک صفحهٔ تخصص: تأیید کن `Physician` / `MedicalClinic` / `Review` / `BreadcrumbList` قبلی سالم‌اند و `FAQPage` معتبر است.
- تأیید کن همهٔ بلوک‌های JSON-LD پارس می‌شوند (در تست محلی: ۰ خطای پارس، انواع `Organization, WebSite, Physician, FAQPage, BreadcrumbList`).
### ۴. بازبینی بصری
صفحات تغییریافته را با نسخهٔ قبل مقایسه کن. تغییر تگ‌ها باید بدون اثر بصری باشد:
| صفحه | تغییر | انتظار |
|------|-------|--------|
| `/` | خط برند `h1``p`؛ شعار `h2``h1` | ظاهر یکسان |
| `/doctors` | متن دکمهٔ موقعیت `h1``span` + h1 جدید و پاراگراف مقدمه | دکمه بدون تغییر؛ عنوان و متن جدید بالای لیست |
| `/clinics` | عنوان پویا + پاراگراف مقدمه + breadcrumb با «خانه» | «خانه >» به breadcrumb اضافه شده |
| `/clinic/[id]` | ۳ عنوان بخش `h1``h2`؛ breadcrumb بصری جدید | تایپوگرافی یکسان |
### ۵. Search Console
برای هر دامنهٔ درگیر: URL Inspection روی صفحات کلیدی (`/`، `/doctors`، `/clinics`، یک صفحهٔ پزشک، یک صفحهٔ تخصص) و ثبت sitemap. re-index چند هفته طول می‌کشد.
## نکات مهم
- **رگرسیون احتمالی که باید مشخصاً چک شود:** حذف `loading.js` از سه مسیر `doctor/[slug]`، `clinic/[slug]`، `blog/[slug]`. این کار برای رفع Soft-404 لازم بود (Suspense باعث می‌شد پاسخ با ۲۰۰ استریم شود و `notFound()` دیگر نتواند status را عوض کند). هزینه‌اش نبودِ اسکلت لودینگ هنگام ناوبری کلاینتی است — تجربهٔ کاربری این سه مسیر را عملاً بررسی کن.
- اگر `robots` روی همهٔ صفحات `noindex, nofollow, nocache` بود، تقریباً قطعاً `DEV_MODE=TRUE` در build بوده — کد را دنبال نکن، اول build را بررسی کن.
- ArvanCloud ممکن است روی دامنهٔ اصلی bot-challenge داشته باشد؛ اگر curl خروجی غیرمنتظره داد، قبل از دیباگ کد این را رد کن. (خارج از دامنهٔ اپ — DevOps)
- sitemap دامنهٔ اصلی ~۱۳ ثانیه طول می‌کشد (۳۵ sweep برای تفکیک شهر). اگر timeout خورد، این انتظار است نه باگ — رفعش در `sitemap-simplify-with-city.md`.
</div>