feat(blog): enhance metadata generation and blog fetching by city context

This commit is contained in:
hamed
2026-07-19 09:02:14 +03:30
parent 88782e70c2
commit 4d7b87ee1a
5 changed files with 46 additions and 12 deletions
+11 -2
View File
@@ -31,12 +31,21 @@ export async function generateMetadata({ params }) {
if (!blog) notFound();
try {
// برند از شهرِ خودِ پست می‌آید، نه از دامنه‌ای که اتفاقاً آن را سرو می‌کند —
// وگرنه پست یاسوجی که روی دامنهٔ یزد باز شود عنوانش «یزد نوبت» می‌شد در حالی
// که canonical آن به یاسوج اشاره می‌کند. پست سراسری برند دامنهٔ جاری را می‌گیرد.
const blogCity = findCityById(extractEntityCityId(blog));
const brand = blogCity?.site_name || siteName;
const title = `${blog.title} | ${siteName}`;
const title = `${blog.title} | ${brand}`;
const rawText = (blog.summary || blog.body || "")
.replace(/<[^>]*>/g, "")
.trim();
const description = rawText.slice(0, 160) || blog.title;
const baseDescription = rawText.slice(0, 160) || blog.title;
// نام شهر فقط برای پست شهریافته؛ پست سراسری هیچ شهری نمی‌گیرد.
const description = blogCity
? `${baseDescription}`.slice(0, 150) + ` | ${blogCity.name}`
: baseDescription;
const image = blog.image_url ? imageUrl(blog.image_url) : FALLBACK_IMG;
// C1-b — پست شهریافته به دامنهٔ همان شهر canonical می‌شود؛
+6 -2
View File
@@ -18,10 +18,14 @@ export async function generateMetadata() {
};
}
export default function Blogs() {
export default async function Blogs() {
const { matchedCity, isRoot } = await getStateInfo();
// روی دامنهٔ شهری: پست‌های همان شهر + سراسری. روی دامنهٔ ریشه: همهٔ پست‌ها.
// تشخیص شهر سمت سرور انجام می‌شود (همان الگوی app/specialties/page.js).
return (
<Layout name="/blogs">
<BlogsPage />
<BlogsPage cityId={isRoot ? null : (matchedCity?.id ?? null)} />
</Layout>
);
}
+9 -6
View File
@@ -175,14 +175,18 @@ function getSpecialtyUrls(baseUrl) {
}
// هر پست فقط در sitemap دامنهٔ canonical خودش: پست شهریافته → دامنهٔ آن شهر،
// پست سراسری (بدون شهر) → دامنهٔ اصلی.
async function getBlogUrls(baseUrl, scope, currentDomain) {
// پست سراسری (یا شهرِ بدون دامنهٔ اختصاصی) → دامنهٔ اصلی.
//
// تطبیق با شناسهٔ شهرِ scope انجام می‌شود نه با رشتهٔ دامنه: scope خودش از subdomain
// استخراج شده، پس با پورت و TLD متفاوت (محیط محلی) هم درست کار می‌کند.
async function getBlogUrls(baseUrl, scope) {
const blogs = await fetchAllPages('/api/v1/blogs');
return blogs
.filter((b) => b?.slug || b?.uuid)
.filter((b) => {
const cityDomain = findDomainByCityId(extractEntityCityId(b));
return cityDomain ? cityDomain === currentDomain : scope.isRoot;
const cityId = extractEntityCityId(b);
if (!findDomainByCityId(cityId)) return scope.isRoot;
return !scope.isRoot && Number(scope.matchedCity?.id) === Number(cityId);
})
.map((b) =>
withLastModified(
@@ -201,12 +205,11 @@ export default async function sitemap() {
const domain = await getCurrentDomain();
const baseUrl = getBaseUrl(domain);
const scope = getCityScope(domain);
const normalizedDomain = domain.toLowerCase().replace(/^www\./, '').split(':')[0];
const [doctorUrls, clinicUrls, blogUrls] = await Promise.all([
getDoctorUrls(baseUrl, scope),
getClinicUrls(baseUrl, scope),
getBlogUrls(baseUrl, scope, normalizedDomain),
getBlogUrls(baseUrl, scope),
]);
const allUrls = [