Fix community IDs and update sitemap functionality
- Updated community IDs for various components in graph.json to reflect correct associations. - Added new function `getPublishedDoctors()` in app/sitemap.js and established relationships with existing functions. - Adjusted source locations for several functions in app/sitemap.js to ensure accurate mapping. - Enhanced canonical URL handling in lib/getCanonicalUrl.js to prevent incorrect canonicalization for paginated specialty pages. - Updated manifest.json with new modification times and AST hashes for affected files.
This commit is contained in:
+50
-23
@@ -113,23 +113,28 @@ function cityFilterParams(scope) {
|
||||
// فیلتری انجام میشود که کلینیکها استفاده میکنند. پیش از این، نبودِ city مجبورمان
|
||||
// میکرد روی دامنهٔ اصلی کل لیست را ۳۵ بار (یکبار بهازای هر شهرِ دامنهدار) بگیریم و
|
||||
// تفاضل بگیریم — همان کاری که ~۱۳ ثانیه طول میکشید.
|
||||
async function getDoctorUrls(baseUrl, scope) {
|
||||
// پزشکانی که این دامنه منتشرشان میکند. هم URLهای پزشک و هم صفحات تخصص از همین
|
||||
// مجموعه ساخته میشوند تا نتوانند واگرا شوند.
|
||||
async function getPublishedDoctors(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(
|
||||
{
|
||||
url: `${baseUrl}/doctor/${d.uuid}`,
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.8,
|
||||
},
|
||||
d.updated || d.created
|
||||
)
|
||||
);
|
||||
.filter((d) => !scope.isRoot || !findDomainByCityId(extractEntityCityId(d)));
|
||||
}
|
||||
|
||||
function getDoctorUrls(baseUrl, doctors) {
|
||||
return doctors.map((d) =>
|
||||
withLastModified(
|
||||
{
|
||||
url: `${baseUrl}/doctor/${d.uuid}`,
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.8,
|
||||
},
|
||||
d.updated || d.created
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async function getClinicUrls(baseUrl, scope) {
|
||||
@@ -151,14 +156,34 @@ async function getClinicUrls(baseUrl, scope) {
|
||||
}
|
||||
|
||||
// صفحات فرود تخصص per-domain — روی هر دامنه «تخصص + آن شهر» است، پس همهجا self.
|
||||
function getSpecialtyUrls(baseUrl) {
|
||||
return specialtiesData
|
||||
.filter((s) => s?.slug && s.status === 1)
|
||||
.map((s) => ({
|
||||
url: `${baseUrl}/specialties/${s.slug}`,
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.8,
|
||||
}));
|
||||
//
|
||||
// فقط تخصصهایی که در همین دامنه واقعاً پزشک دارند اعلام میشوند؛ تولید بیقید هر ۹۳
|
||||
// تخصص برای هر ۳۵ دامنه هزاران صفحهٔ خالی به گوگل معرفی میکرد (thin content انبوه).
|
||||
//
|
||||
// منبع، همان پزشکانی است که این sitemap منتشرشان میکند — نه endpoint شمارش. آن
|
||||
// endpoint با فیلتر لیست پزشکان اختلاف دارد (مثلاً پزشکِ یافتشده از آدرس کلینیک را
|
||||
// نمیشمارد)، و اگر sitemap از یک منبع و خودِ صفحه از منبع دیگر تصمیم بگیرد، صفحهٔ
|
||||
// ایندکسپذیرِ اعلامنشده یا برعکس میسازیم. اینطور هیچ فراخوانی اضافهای هم ندارد.
|
||||
function getSpecialtyUrls(baseUrl, doctors) {
|
||||
const slugById = new Map(
|
||||
specialtiesData
|
||||
.filter((s) => s?.slug && s.status === 1)
|
||||
.map((s) => [String(s.id), s.slug])
|
||||
);
|
||||
|
||||
const slugs = new Set();
|
||||
for (const doctor of doctors) {
|
||||
for (const specialty of doctor?.specialties ?? []) {
|
||||
const slug = slugById.get(String(specialty?.id));
|
||||
if (slug) slugs.add(slug);
|
||||
}
|
||||
}
|
||||
|
||||
return [...slugs].map((slug) => ({
|
||||
url: `${baseUrl}/specialties/${slug}`,
|
||||
changeFrequency: 'weekly',
|
||||
priority: 0.8,
|
||||
}));
|
||||
}
|
||||
|
||||
// هر پست فقط در sitemap دامنهٔ canonical خودش: پست شهریافته → دامنهٔ آن شهر،
|
||||
@@ -193,15 +218,17 @@ export default async function sitemap() {
|
||||
const baseUrl = getBaseUrl(domain);
|
||||
const scope = getCityScope(domain);
|
||||
|
||||
const [doctorUrls, clinicUrls, blogUrls] = await Promise.all([
|
||||
getDoctorUrls(baseUrl, scope),
|
||||
const [doctors, clinicUrls, blogUrls] = await Promise.all([
|
||||
getPublishedDoctors(scope),
|
||||
getClinicUrls(baseUrl, scope),
|
||||
getBlogUrls(baseUrl, scope),
|
||||
]);
|
||||
|
||||
const doctorUrls = getDoctorUrls(baseUrl, doctors);
|
||||
|
||||
const allUrls = [
|
||||
...getStaticPages(baseUrl),
|
||||
...getSpecialtyUrls(baseUrl),
|
||||
...getSpecialtyUrls(baseUrl, doctors),
|
||||
...doctorUrls,
|
||||
...clinicUrls,
|
||||
...blogUrls,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { cache } from "react";
|
||||
import { notFound } from "next/navigation";
|
||||
import Layout from "@/components/layout/StLayout";
|
||||
import SpecialtyDetailPage from "@/components/specialties/detail";
|
||||
@@ -16,7 +17,8 @@ const findSpecialty = (slug) =>
|
||||
specialtiesData.find((item) => item.slug === slug && item.status === 1) ?? null;
|
||||
|
||||
// روی هر دامنهٔ شهری این صفحه خودکار «تخصص + همان شهر» است — شهر از host میآید.
|
||||
async function getDoctors(specialtyId, cityId) {
|
||||
// cache تا generateMetadata و بدنهٔ صفحه یک بار fetch کنند، نه دو بار.
|
||||
const getDoctors = cache(async (specialtyId, cityId) => {
|
||||
try {
|
||||
const res = await fetchReq(`${API_URL}/api/v1/doctors`, {
|
||||
params: {
|
||||
@@ -33,7 +35,7 @@ async function getDoctors(specialtyId, cityId) {
|
||||
} catch {
|
||||
return { items: [], total: 0 };
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export async function generateMetadata({ params }) {
|
||||
const { slug } = await params;
|
||||
@@ -48,9 +50,15 @@ export async function generateMetadata({ params }) {
|
||||
const description = `لیست بهترین پزشکان ${specialty.name} در ${cityName} همراه با نشانی مطب، ساعات کاری و امتیاز بیماران. رزرو نوبت آنلاین متخصص ${specialty.name}.`;
|
||||
const image = "/assets/images/og-image.png";
|
||||
|
||||
// تخصصی که در این شهر پزشکی ندارد فقط یک عنوان و متن قالبی است — thin content.
|
||||
// ایندکس نمیشود (و در sitemap هم نمیآید)، ولی follow میماند تا لینکهای
|
||||
// داخلیاش دنبال شوند. بهمحض افزودهشدن اولین پزشک، خودکار ایندکسپذیر میشود.
|
||||
const { total } = await getDoctors(specialty.id, isRoot ? null : matchedCity?.id);
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
...(total === 0 && { robots: { index: false, follow: true } }),
|
||||
// C1-a — صفحهٔ فرود تخصص per-domain است؛ canonical لایهٔ layout (self) درست است.
|
||||
openGraph: { title, description, images: [image] },
|
||||
twitter: { card: "summary_large_image", title, description, images: [image] },
|
||||
|
||||
Reference in New Issue
Block a user