feat(blog): implement domain-specific blog fetching and canonical URL handling

This commit is contained in:
hamed
2026-07-24 10:01:35 +03:30
parent f46ba03422
commit 9053681375
4 changed files with 123 additions and 11 deletions
+31 -11
View File
@@ -4,30 +4,45 @@ import Layout from "@/components/layout/StLayout";
import { notFound } from "next/navigation";
import { fetchReq } from "@/lib/req";
import { getStateInfo } from "@/lib/getStateInfo";
import { getEntityOrigin } from "@/lib/getCanonicalUrl";
import { extractEntityCityId, findCityById } from "@/lib/domainHelpers";
import { getBlogCanonicalOrigin } from "@/lib/getCanonicalUrl";
import {
domainScopeCityId,
extractEntityCityId,
findCityById,
} from "@/lib/domainHelpers";
import { safeJsonLd } from "@/lib/sanitize";
import { normalizeBlog, imageUrl } from "@/helper";
const FALLBACK_IMG = "/assets/images/og-image.png";
const API_URL = process.env.NEXT_PUBLIC_API_URL;
const getBlog = cache(async (slug) => {
const res = await fetch(`${API_URL}/api/v1/blog/${slug}`, {
// scope دامنه در کلید cache می‌نشیند: یک پست روی دامنهٔ شهریِ دیگر ۴۰۴ است، پس
// پاسخِ دامنهٔ ریشه نباید به دامنهٔ شهری نشت کند.
const getBlog = cache(async (slug, cityId) => {
const query = cityId != null ? `?city_id=${cityId}` : "";
const res = await fetch(`${API_URL}/api/v1/blog/${slug}${query}`, {
next: { revalidate: 3600, tags: [`blog-${slug}`] },
});
// ۴۰۴ یعنی «روی این دامنه وجود ندارد» — پستِ شهرِ دیگر (یا پستِ دامنهٔ اصلی)
// نباید روی دامنهٔ شهری باز شود؛ بک‌اند همان فیلتر لیست را اینجا هم اعمال می‌کند.
if (res.status === 404 || res.status === 400) return null;
if (!res.ok) throw new Error(`Failed to fetch blog: ${res.status}`);
const json = await res.json();
return json?.data?.data ?? null;
});
/** پست را در scope دامنهٔ جاری می‌گیرد (دامنهٔ ریشه scope ندارد). */
async function getScopedBlog(slug) {
const { matchedCity, isRoot } = await getStateInfo();
return getBlog(slug, domainScopeCityId({ matchedCity, isRoot }));
}
export async function generateMetadata({ params }) {
const { slug } = await params;
const { matchedCity } = await getStateInfo();
const siteName = matchedCity?.site_name || "نوبت 724";
const blog = await getBlog(slug);
const blog = await getScopedBlog(slug);
if (!blog) notFound();
try {
@@ -63,10 +78,11 @@ export async function generateMetadata({ params }) {
? imageUrl(blog.image_url)
: FALLBACK_IMG;
// C1-b — پست شهریافته به دامنهٔ همان شهر canonical می‌شود؛
// پست سراسری (بدون شهر) روی دامنهٔ جاری self-canonical می‌ماند.
// C1-b — پست شهریافته به دامنهٔ همان شهر canonical می‌شود؛ پست سراسری (یا پستِ
// رکورد ریشهٔ nobat724) به دامنهٔ اصلی، نه دامنهٔ جاری — وگرنه یک محتوا روی چند
// دامنه سرو می‌شد و هرکدام خودش را canonical اعلام می‌کرد.
// فیلد canonical_url اختصاصی (مثلاً نسخهٔ شهریِ پایپ‌لاین) مقدم است.
const origin = await getEntityOrigin(extractEntityCityId(blog));
const origin = getBlogCanonicalOrigin(extractEntityCityId(blog));
const canonical = blog.canonical_url || `${origin}/blog/${slug}`;
return {
@@ -102,16 +118,20 @@ export async function generateMetadata({ params }) {
async function Blog({ params }) {
const { slug } = await params;
const blog = normalizeBlog(await getBlog(slug));
const blog = normalizeBlog(await getScopedBlog(slug));
if (!blog) notFound();
const blogCityId = extractEntityCityId(blog);
const blogCity = findCityById(blogCityId);
const origin = await getEntityOrigin(blogCityId);
const origin = getBlogCanonicalOrigin(blogCityId);
// «مقالات مرتبط» هم باید scope دامنه را رعایت کند، وگرنه روی دامنهٔ شهری لینکِ
// پستِ شهر دیگر نشان داده می‌شود که حالا ۴۰۴ می‌گیرد.
const { matchedCity, isRoot } = await getStateInfo();
const scopeCityId = domainScopeCityId({ matchedCity, isRoot });
let relatedBlogs = [];
const relatedResponse = await fetchReq(
`${API_URL}/api/v1/blogs?page=1&limit=6`
`${API_URL}/api/v1/blogs?page=1&limit=6${scopeCityId != null ? `&city_id=${scopeCityId}` : ""}`
);
relatedBlogs = (relatedResponse?.data || [])
.filter((b) => b.uuid !== blog?.uuid)