feat: enhance SEO for listing and specialty pages, fix search autocomplete issue
This commit is contained in:
+1
-1
@@ -22,7 +22,7 @@ export async function generateMetadata({ searchParams }) {
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
...listingRobots(awaitedParams, { matchedCity, matchedState }),
|
||||
...listingRobots(awaitedParams),
|
||||
openGraph: { title, description, images: [image] },
|
||||
twitter: { card: "summary_large_image", title, description, images: [image] },
|
||||
};
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ export async function generateMetadata({ searchParams }) {
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
...listingRobots(awaitedParams, { matchedCity, matchedState }),
|
||||
...listingRobots(awaitedParams),
|
||||
openGraph: { title, description, images: [image] },
|
||||
twitter: { card: "summary_large_image", title, description, images: [image] },
|
||||
};
|
||||
|
||||
+4
-1
@@ -6,6 +6,7 @@ import { isRootCity } from '@/lib/rootCity';
|
||||
import { extractEntityCityId, findDomainByCityId } from '@/lib/domainHelpers';
|
||||
import { isThinClinic, isThinDoctor } from '@/lib/entityQuality';
|
||||
import specialtiesData from '@/data/specialties.json';
|
||||
import { isIndexableSpecialty } from '@/lib/specialtyIndexing';
|
||||
|
||||
// sitemap باید در زمان اجرا (per-request) روی سرور production تولید شود، نه در build.
|
||||
// در build کانتینر به API دسترسی شبکه ندارد → fetch failed. force-dynamic این را قطعی میکند.
|
||||
@@ -165,9 +166,11 @@ async function getClinicUrls(baseUrl, scope) {
|
||||
// نمیشمارد)، و اگر sitemap از یک منبع و خودِ صفحه از منبع دیگر تصمیم بگیرد، صفحهٔ
|
||||
// ایندکسپذیرِ اعلامنشده یا برعکس میسازیم. اینطور هیچ فراخوانی اضافهای هم ندارد.
|
||||
function getSpecialtyUrls(baseUrl, doctors) {
|
||||
// والدها noindex هستند (lib/specialtyIndexing)؛ اعلامشان در sitemap سیگنال
|
||||
// متناقض به گوگل میدهد، پس اصلاً منتشر نمیشوند.
|
||||
const slugById = new Map(
|
||||
specialtiesData
|
||||
.filter((s) => s?.slug && s.status === 1)
|
||||
.filter((s) => s?.slug && s.status === 1 && isIndexableSpecialty(s))
|
||||
.map((s) => [String(s.id), s.slug])
|
||||
);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { getStateInfo } from "@/lib/getStateInfo";
|
||||
import { getRequestOrigin } from "@/lib/getCanonicalUrl";
|
||||
import { resolveCityDisplayName } from "@/lib/domainHelpers";
|
||||
import { buildSpecialtyFaq, buildSpecialtyIntro } from "@/lib/specialtyContent";
|
||||
import { isCategorySpecialty } from "@/lib/specialtyIndexing";
|
||||
import { safeJsonLd } from "@/lib/sanitize";
|
||||
import { fetchReq } from "@/lib/req";
|
||||
import { isNextRedirectError } from "@/lib/maintenance";
|
||||
@@ -58,10 +59,14 @@ export async function generateMetadata({ params }) {
|
||||
// داخلیاش دنبال شوند. بهمحض افزودهشدن اولین پزشک، خودکار ایندکسپذیر میشود.
|
||||
const { total } = await getDoctors(specialty.id, isRoot ? null : matchedCity?.id);
|
||||
|
||||
// دستهٔ سطحبالا (دارای زیرشاخهٔ فعال) محتوای مستقلی ندارد و با فرزندانش روی همان
|
||||
// کوئری رقابت میکند؛ صفحهٔ قابلایندکس، همان زیرشاخه است.
|
||||
const noindex = isCategorySpecialty(specialty) || total === 0;
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
...(total === 0 && { robots: { index: false, follow: true } }),
|
||||
...(noindex && { 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] },
|
||||
@@ -83,7 +88,7 @@ async function SpecialtyDetail({ params }) {
|
||||
);
|
||||
|
||||
const intro = buildSpecialtyIntro(specialty.name, cityName, total);
|
||||
const faq = buildSpecialtyFaq(specialty.name, cityName, total);
|
||||
const faq = buildSpecialtyFaq(specialty.name, cityName, total, { isRoot });
|
||||
const pageUrl = `${origin}/specialties/${specialty.slug}`;
|
||||
|
||||
const pageJsonLd = {
|
||||
|
||||
Reference in New Issue
Block a user