feat(sitemap): optimize doctor URL fetching and add sitemap index threshold warning

This commit is contained in:
hamed
2026-07-19 09:10:04 +03:30
parent 488e5eb0a4
commit 8eb55679b2
+25 -25
View File
@@ -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 [];