- Add metadata to login and login-verify pages to prevent indexing. - Update robots.txt to disallow additional sensitive paths. - Enhance sitemap generation to filter by city and include accurate last modified dates. - Refactor canonical URL generation to support multi-domain architecture, ensuring self-canonicalization for city domains. - Remove deprecated CanonicalHandler component and streamline canonical URL handling. - Introduce safe JSON-LD output to prevent XSS vulnerabilities. - Add payment layout with appropriate metadata to prevent indexing. - Conduct a comprehensive technical SEO audit and implement necessary fixes across the application.
132 lines
3.7 KiB
JavaScript
132 lines
3.7 KiB
JavaScript
import { cache } from "react";
|
|
import BlogPage from "@/components/blog";
|
|
import Layout from "@/components/layout/StLayout";
|
|
import { notFound } from "next/navigation";
|
|
import { fetchReq } from "@/lib/req";
|
|
import { getStateInfo } from "@/lib/getStateInfo";
|
|
import { getRequestOrigin } from "@/lib/getCanonicalUrl";
|
|
import { safeJsonLd } from "@/lib/sanitize";
|
|
import { normalizeBlog, imageUrl } from "@/helper";
|
|
|
|
const FALLBACK_IMG = "https://nobat724.com/assets/images/logo.png";
|
|
const API_URL = process.env.NEXT_PUBLIC_API_URL;
|
|
|
|
const getBlog = cache(async (slug) => {
|
|
try {
|
|
const res = await fetch(`${API_URL}/api/v1/blog/${slug}`, {
|
|
next: { revalidate: 3600, tags: [`blog-${slug}`] },
|
|
});
|
|
if (!res.ok) return null;
|
|
const json = await res.json();
|
|
return json?.data?.data ?? null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
});
|
|
|
|
export async function generateMetadata({ params }) {
|
|
const { slug } = await params;
|
|
const { matchedCity } = await getStateInfo();
|
|
const siteName = matchedCity?.site_name || "نوبت 724";
|
|
|
|
try {
|
|
const blog = await getBlog(slug);
|
|
if (!blog) return {};
|
|
|
|
const title = `${blog.title} | ${siteName}`;
|
|
const rawText = (blog.summary || blog.body || "")
|
|
.replace(/<[^>]*>/g, "")
|
|
.trim();
|
|
const description = rawText.slice(0, 160) || blog.title;
|
|
const image = blog.image_url ? imageUrl(blog.image_url) : FALLBACK_IMG;
|
|
|
|
return {
|
|
title,
|
|
description,
|
|
// محتوای بلاگ روی همهی دامنههای شهری یکسان است — تجمیع اعتبار روی دامنهی اصلی
|
|
alternates: { canonical: `https://nobat724.com/blog/${slug}` },
|
|
openGraph: {
|
|
title,
|
|
description,
|
|
type: "article",
|
|
...(blog.created_at && {
|
|
publishedTime: new Date(blog.created_at * 1000).toISOString(),
|
|
}),
|
|
images: [image],
|
|
},
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title,
|
|
description,
|
|
images: [image],
|
|
},
|
|
};
|
|
} catch {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
async function Blog({ params }) {
|
|
const { slug } = await params;
|
|
const origin = await getRequestOrigin();
|
|
|
|
const blog = normalizeBlog(await getBlog(slug));
|
|
if (!blog) notFound();
|
|
|
|
let relatedBlogs = [];
|
|
const relatedResponse = await fetchReq(
|
|
`${API_URL}/api/v1/blogs?page=1&limit=6`
|
|
);
|
|
relatedBlogs = (relatedResponse?.data || [])
|
|
.filter((b) => b.uuid !== blog?.uuid)
|
|
.slice(0, 4)
|
|
.map(normalizeBlog);
|
|
|
|
const jsonLd = blog
|
|
? {
|
|
"@context": "https://schema.org",
|
|
"@type": "Article",
|
|
headline: blog.title,
|
|
...(blog.images?.[0]?.url && { image: imageUrl(blog.images[0].url) }),
|
|
...(blog.created && {
|
|
datePublished: new Date(blog.created * 1000).toISOString(),
|
|
}),
|
|
...(blog.author && { author: { "@type": "Person", name: blog.author } }),
|
|
}
|
|
: null;
|
|
|
|
const breadcrumbJsonLd = blog
|
|
? {
|
|
"@context": "https://schema.org",
|
|
"@type": "BreadcrumbList",
|
|
itemListElement: [
|
|
{ "@type": "ListItem", position: 1, name: "خانه", item: origin },
|
|
{ "@type": "ListItem", position: 2, name: "مقالات", item: `${origin}/blogs` },
|
|
{ "@type": "ListItem", position: 3, name: blog.title },
|
|
],
|
|
}
|
|
: null;
|
|
|
|
return (
|
|
<>
|
|
{jsonLd && (
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: safeJsonLd(jsonLd) }}
|
|
/>
|
|
)}
|
|
{breadcrumbJsonLd && (
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: safeJsonLd(breadcrumbJsonLd) }}
|
|
/>
|
|
)}
|
|
<Layout>
|
|
<BlogPage blog={blog} blogs={relatedBlogs} />
|
|
</Layout>
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default Blog;
|