feat(blog): enhance metadata generation and blog fetching by city context
This commit is contained in:
+11
-2
@@ -31,12 +31,21 @@ export async function generateMetadata({ params }) {
|
|||||||
if (!blog) notFound();
|
if (!blog) notFound();
|
||||||
|
|
||||||
try {
|
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 || "")
|
const rawText = (blog.summary || blog.body || "")
|
||||||
.replace(/<[^>]*>/g, "")
|
.replace(/<[^>]*>/g, "")
|
||||||
.trim();
|
.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;
|
const image = blog.image_url ? imageUrl(blog.image_url) : FALLBACK_IMG;
|
||||||
|
|
||||||
// C1-b — پست شهریافته به دامنهٔ همان شهر canonical میشود؛
|
// C1-b — پست شهریافته به دامنهٔ همان شهر canonical میشود؛
|
||||||
|
|||||||
+6
-2
@@ -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 (
|
return (
|
||||||
<Layout name="/blogs">
|
<Layout name="/blogs">
|
||||||
<BlogsPage />
|
<BlogsPage cityId={isRoot ? null : (matchedCity?.id ?? null)} />
|
||||||
</Layout>
|
</Layout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-6
@@ -175,14 +175,18 @@ function getSpecialtyUrls(baseUrl) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// هر پست فقط در sitemap دامنهٔ canonical خودش: پست شهریافته → دامنهٔ آن شهر،
|
// هر پست فقط در sitemap دامنهٔ canonical خودش: پست شهریافته → دامنهٔ آن شهر،
|
||||||
// پست سراسری (بدون شهر) → دامنهٔ اصلی.
|
// پست سراسری (یا شهرِ بدون دامنهٔ اختصاصی) → دامنهٔ اصلی.
|
||||||
async function getBlogUrls(baseUrl, scope, currentDomain) {
|
//
|
||||||
|
// تطبیق با شناسهٔ شهرِ scope انجام میشود نه با رشتهٔ دامنه: scope خودش از subdomain
|
||||||
|
// استخراج شده، پس با پورت و TLD متفاوت (محیط محلی) هم درست کار میکند.
|
||||||
|
async function getBlogUrls(baseUrl, scope) {
|
||||||
const blogs = await fetchAllPages('/api/v1/blogs');
|
const blogs = await fetchAllPages('/api/v1/blogs');
|
||||||
return blogs
|
return blogs
|
||||||
.filter((b) => b?.slug || b?.uuid)
|
.filter((b) => b?.slug || b?.uuid)
|
||||||
.filter((b) => {
|
.filter((b) => {
|
||||||
const cityDomain = findDomainByCityId(extractEntityCityId(b));
|
const cityId = extractEntityCityId(b);
|
||||||
return cityDomain ? cityDomain === currentDomain : scope.isRoot;
|
if (!findDomainByCityId(cityId)) return scope.isRoot;
|
||||||
|
return !scope.isRoot && Number(scope.matchedCity?.id) === Number(cityId);
|
||||||
})
|
})
|
||||||
.map((b) =>
|
.map((b) =>
|
||||||
withLastModified(
|
withLastModified(
|
||||||
@@ -201,12 +205,11 @@ export default async function sitemap() {
|
|||||||
const domain = await getCurrentDomain();
|
const domain = await getCurrentDomain();
|
||||||
const baseUrl = getBaseUrl(domain);
|
const baseUrl = getBaseUrl(domain);
|
||||||
const scope = getCityScope(domain);
|
const scope = getCityScope(domain);
|
||||||
const normalizedDomain = domain.toLowerCase().replace(/^www\./, '').split(':')[0];
|
|
||||||
|
|
||||||
const [doctorUrls, clinicUrls, blogUrls] = await Promise.all([
|
const [doctorUrls, clinicUrls, blogUrls] = await Promise.all([
|
||||||
getDoctorUrls(baseUrl, scope),
|
getDoctorUrls(baseUrl, scope),
|
||||||
getClinicUrls(baseUrl, scope),
|
getClinicUrls(baseUrl, scope),
|
||||||
getBlogUrls(baseUrl, scope, normalizedDomain),
|
getBlogUrls(baseUrl, scope),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const allUrls = [
|
const allUrls = [
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import LatestArticles from "./latestArticles";
|
|||||||
import { request } from "@/services/response";
|
import { request } from "@/services/response";
|
||||||
import { normalizeBlog } from "@/helper";
|
import { normalizeBlog } from "@/helper";
|
||||||
|
|
||||||
function BlogsPage() {
|
function BlogsPage({ cityId = null }) {
|
||||||
const [blogs, setBlogs] = useState([]);
|
const [blogs, setBlogs] = useState([]);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
const [totalPages, setTotalPages] = useState(1);
|
const [totalPages, setTotalPages] = useState(1);
|
||||||
@@ -25,6 +25,10 @@ function BlogsPage() {
|
|||||||
if (selectedTag) {
|
if (selectedTag) {
|
||||||
params.tag = selectedTag;
|
params.tag = selectedTag;
|
||||||
}
|
}
|
||||||
|
// backend با city_id پستهای آن شهر «و» پستهای سراسری را برمیگرداند
|
||||||
|
if (cityId) {
|
||||||
|
params.city_id = cityId;
|
||||||
|
}
|
||||||
const response = await request.getBlogs(params);
|
const response = await request.getBlogs(params);
|
||||||
setBlogs((response?.data || []).map(normalizeBlog));
|
setBlogs((response?.data || []).map(normalizeBlog));
|
||||||
setTotalPages(response?.meta?.totalPages || 1);
|
setTotalPages(response?.meta?.totalPages || 1);
|
||||||
@@ -37,7 +41,7 @@ function BlogsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchBlogs();
|
fetchBlogs();
|
||||||
}, [page, selectedTag]);
|
}, [page, selectedTag, cityId]);
|
||||||
|
|
||||||
const handlePageChange = (event, value) => {
|
const handlePageChange = (event, value) => {
|
||||||
setPage(value);
|
setPage(value);
|
||||||
|
|||||||
@@ -75,3 +75,17 @@ describe("extractEntityCityId", () => {
|
|||||||
expect(extractEntityCityId(null)).toBeNull();
|
expect(extractEntityCityId(null)).toBeNull();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("شکل پاسخ بلاگ (city آبجکت یا null)", () => {
|
||||||
|
it("پست شهریافته → شناسه و دامنهٔ همان شهر", () => {
|
||||||
|
const post = { slug: "x", city: { id: "123", name: "یاسوج" } };
|
||||||
|
expect(extractEntityCityId(post)).toBe("123");
|
||||||
|
expect(findDomainByCityId(extractEntityCityId(post))).toBe("yasuj-nobat.ir");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("پست سراسری (city: null) → بدون شهر، بدون دامنه", () => {
|
||||||
|
const post = { slug: "x", city: null };
|
||||||
|
expect(extractEntityCityId(post)).toBeNull();
|
||||||
|
expect(findDomainByCityId(extractEntityCityId(post))).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user