From 8eb55679b2bbc4b4e81451250ed6b7ed9d1a3e21 Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Sun, 19 Jul 2026 09:10:04 +0330 Subject: [PATCH] feat(sitemap): optimize doctor URL fetching and add sitemap index threshold warning --- app/sitemap.js | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/app/sitemap.js b/app/sitemap.js index fb1edbe..bd4cc03 100644 --- a/app/sitemap.js +++ b/app/sitemap.js @@ -20,6 +20,9 @@ const API_URL = process.env.NEXT_PUBLIC_API_URL; const PAGE_LIMIT = 50; const MAX_PAGES = 200; +// سقف استاندارد هر فایل sitemap ۵۰٬۰۰۰ URL است؛ کمی زودتر هشدار می‌دهیم. +const SITEMAP_INDEX_THRESHOLD = 45000; + function toSafeDate(value) { if (!value) return null; const d = new Date(typeof value === 'number' ? value * 1000 : value); @@ -106,33 +109,17 @@ function cityFilterParams(scope) { return params; } -// شهرهایی که دامنهٔ اختصاصی دارند — موجودیت‌هایشان canonical روی همان دامنه دارند و -// نباید در sitemap دامنهٔ اصلی تکرار شوند. -const CITY_IDS_WITH_DOMAIN = citiesData - .filter((c) => !isRootCity(c) && c.domain) - .map((c) => c.id); - -/** - * پاسخ لیست پزشکان شهر ندارد؛ پس تفکیک با فیلتر سمت API انجام می‌شود: - * روی دامنهٔ شهری با city_id، و روی دامنهٔ اصلی «همه منهای پزشکانِ شهرهای دامنه‌دار». - */ -async function getDoctorsForScope(scope) { - if (!scope.isRoot) return fetchAllPages('/api/v1/doctors', cityFilterParams(scope)); - - const [all, ...perCity] = await Promise.all([ - fetchAllPages('/api/v1/doctors'), - ...CITY_IDS_WITH_DOMAIN.map((cityId) => - fetchAllPages('/api/v1/doctors', { city_id: String(cityId) }) - ), - ]); - const ownedByCityDomain = new Set(perCity.flat().map((d) => d?.uuid).filter(Boolean)); - return all.filter((d) => !ownedByCityDomain.has(d?.uuid)); -} - +// پاسخ لیست پزشکان از GET /api/v1/doctors حالا خودش city دارد، پس تفکیک دامنه با همان +// فیلتری انجام می‌شود که کلینیک‌ها استفاده می‌کنند. پیش از این، نبودِ city مجبورمان +// می‌کرد روی دامنهٔ اصلی کل لیست را ۳۵ بار (یک‌بار به‌ازای هر شهرِ دامنه‌دار) بگیریم و +// تفاضل بگیریم — همان کاری که ~۱۳ ثانیه طول می‌کشید. async function getDoctorUrls(baseUrl, scope) { - const doctors = await getDoctorsForScope(scope); + const doctors = await fetchAllPages('/api/v1/doctors', cityFilterParams(scope)); return doctors .filter((d) => !isThinDoctor(d)) + // روی دامنهٔ اصلی فقط پزشکانی که شهرشان دامنهٔ اختصاصی ندارد؛ روی دامنهٔ شهری + // فیلتر city_id سمت API قبلاً کار را کرده است. + .filter((d) => !scope.isRoot || !findDomainByCityId(extractEntityCityId(d))) .map((d) => withLastModified( { @@ -221,11 +208,24 @@ export default async function sitemap() { ]; const seen = new Set(); - return allUrls.filter((item) => { + const uniqueUrls = allUrls.filter((item) => { if (seen.has(item.url)) return false; seen.add(item.url); return true; }); + + // sitemap-index عمداً پیاده نشده: سقف استاندارد هر فایل ۵۰٬۰۰۰ URL است و + // بزرگ‌ترین دامنه اکنون حدود ۲٬۵۰۰ URL دارد — یعنی هنوز یک مرتبهٔ بزرگی فاصله. + // پیاده‌سازی‌اش با generateSitemaps ساختار URL را به /sitemap/[id].xml می‌برد و + // ارجاع robots.txt هم باید همراهش عوض شود؛ پس تا نزدیک‌شدن به آستانه نمی‌ارزد. + // این هشدار هست تا وقتی واقعاً لازم شد، در لاگ دیده شود نه در Search Console. + if (uniqueUrls.length > SITEMAP_INDEX_THRESHOLD) { + console.warn( + `[sitemap] ${domain}: ${uniqueUrls.length} URLs — نزدیک سقف ۵۰k، وقت پیاده‌سازی sitemap-index است` + ); + } + + return uniqueUrls; } catch (error) { console.error('Error generating sitemap:', error); return [];