feat: add canonical URL handling and entity quality checks
- Implemented canonical URL strategies for city-specific domains and entities. - Added helper functions for domain and city resolution. - Created tests for canonical URL generation and domain resolution. - Introduced entity quality checks for doctors and clinics to ensure meaningful content. - Developed unique introductory texts for listing pages to avoid duplicate content. - Established robots.txt policies for listing pages to manage indexing based on user filters. - Enhanced specialty content with dynamic introductions and FAQs to improve SEO.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
// هلپرهای مشترک نگاشت «شهر ↔ دامنه» و نام نمایشی شهر.
|
||||
// این ماژول pure است (بدون next/headers) تا در client، server، sitemap و تست قابل import باشد.
|
||||
import citiesData from "@/data/city.json";
|
||||
import { isRootCity } from "@/lib/rootCity";
|
||||
|
||||
const ROOT_DISPLAY_NAME = "ایران";
|
||||
|
||||
/**
|
||||
* دامنهٔ شهریِ متناظر با یک city_id.
|
||||
* رکورد ریشه (id:600 → nobat724.com) هرگز بهعنوان «شهر» برنمیگردد.
|
||||
* @returns {string|null} مثلا "yasuj-nobat.ir" یا null
|
||||
*/
|
||||
export function findDomainByCityId(cityId) {
|
||||
if (cityId == null || cityId === "") return null;
|
||||
const id = Number(cityId);
|
||||
if (!Number.isFinite(id)) return null;
|
||||
|
||||
const city = citiesData.find((c) => Number(c.id) === id);
|
||||
if (!city || isRootCity(city) || !city.domain) return null;
|
||||
return city.domain;
|
||||
}
|
||||
|
||||
/** رکورد شهر بر اساس id — رکورد ریشه شهر نیست. */
|
||||
export function findCityById(cityId) {
|
||||
if (cityId == null || cityId === "") return null;
|
||||
const city = citiesData.find((c) => Number(c.id) === Number(cityId));
|
||||
return city && !isRootCity(city) ? city : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* نام قابلنمایش شهر برای H1/Title.
|
||||
* روی دامنهٔ ریشه نام رکورد («نوبت 724») یک برند است نه شهر — بهجای آن «ایران».
|
||||
*/
|
||||
export function resolveCityDisplayName(cityInfo) {
|
||||
if (!cityInfo || isRootCity(cityInfo)) return ROOT_DISPLAY_NAME;
|
||||
return cityInfo.name || ROOT_DISPLAY_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* city_id یک موجودیت را از شکلهای مختلف پاسخ API بیرون میکشد:
|
||||
* - کلینیک: city: [{ id }]
|
||||
* - پزشک: city خالی است و شهر واقعی داخل address[].city.id مینشیند
|
||||
* - بلاگ: city_id مسطح
|
||||
* اولین شهرِ دارای دامنهٔ اختصاصی برنده است (پزشک چند-شهری → یک شهر اصلی).
|
||||
*/
|
||||
export function extractEntityCityId(entity) {
|
||||
if (!entity) return null;
|
||||
|
||||
const candidates = [
|
||||
entity.city_id,
|
||||
...(Array.isArray(entity.city) ? entity.city.map((c) => c?.id) : [entity.city?.id]),
|
||||
...(Array.isArray(entity.address) ? entity.address.map((a) => a?.city?.id) : []),
|
||||
].filter((id) => id != null);
|
||||
|
||||
const withDomain = candidates.find((id) => findDomainByCityId(id));
|
||||
return withDomain ?? candidates[0] ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user