264 lines
8.0 KiB
TypeScript
264 lines
8.0 KiB
TypeScript
import React from "react";
|
|
import { notFound } from 'next/navigation';
|
|
import type { Metadata } from 'next';
|
|
import BlogPostClient from "../../../components/BlogPostClient";
|
|
import { getSeoConfig, formatPageTitle } from "../../../lib/seo";
|
|
import { safeJsonLd } from "../../../lib/schema";
|
|
|
|
function safeIso(val: any, fallback?: string): string | undefined {
|
|
if (!val) return fallback;
|
|
try {
|
|
const d = new Date(val);
|
|
return isNaN(d.getTime()) ? fallback : d.toISOString();
|
|
} catch {
|
|
return fallback;
|
|
}
|
|
}
|
|
|
|
function safeLocalDate(val: any): string {
|
|
if (!val) return '';
|
|
try {
|
|
const d = new Date(val);
|
|
return isNaN(d.getTime()) ? '' : d.toLocaleDateString('fa-IR');
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
async function getBlog(rawSlug: string) {
|
|
if (!rawSlug) return null;
|
|
try {
|
|
const slug = decodeURIComponent(rawSlug);
|
|
const apiUrl = process.env.INTERNAL_API_URL || process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4001/api';
|
|
const apiBase = apiUrl.endsWith('/api') ? apiUrl : `${apiUrl.replace(/\/$/, '')}/api`;
|
|
const res = await fetch(`${apiBase}/blogs/${encodeURIComponent(slug)}`, {
|
|
next: {
|
|
tags: ['blogs', `blog-${slug}`],
|
|
revalidate: 3600,
|
|
},
|
|
});
|
|
if (!res.ok) {
|
|
console.warn(`[Blog] Failed to fetch blog ${slug}: ${res.status}`);
|
|
return null;
|
|
}
|
|
const b = await res.json();
|
|
return b;
|
|
} catch (error) {
|
|
console.error('[Blog] Error fetching blog detail:', error);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export const revalidate = 3600;
|
|
|
|
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }): Promise<Metadata> {
|
|
const { slug } = await params;
|
|
const blog = await getBlog(slug);
|
|
const config = await getSeoConfig();
|
|
|
|
if (!blog) {
|
|
return {
|
|
title: formatPageTitle('مقاله یافت نشد', config.brandNameFa, config.titleSeparator, config.titlePosition),
|
|
};
|
|
}
|
|
|
|
const rawSiteUrl = process.env.NEXT_PUBLIC_SITE_URL || config.canonicalBaseUrl || 'https://canina.ir';
|
|
const siteUrl = rawSiteUrl.replace(/\/+$/, '');
|
|
|
|
const metaTitle = blog.metaTitle || blog.title || 'مقاله آموزشی و تخصصی کنینا';
|
|
const title = formatPageTitle(metaTitle, config.brandNameFa, config.titleSeparator, config.titlePosition, false);
|
|
const description =
|
|
blog.metaDescription ||
|
|
blog.excerpt ||
|
|
(typeof blog.content === 'string' ? blog.content.substring(0, 155).replace(/<[^>]+>/g, '') : '') ||
|
|
config.defaultMetaDescription;
|
|
const canonicalUrl = blog.canonicalUrl ? blog.canonicalUrl.replace(/\/+$/, '') : `${siteUrl}/blog/${slug}`;
|
|
const ogImageUrl = blog.ogImage || blog.imageUrl || config.ogImageUrl;
|
|
const ogTitle = blog.ogTitle || title;
|
|
const ogDesc = blog.ogDescription || description;
|
|
|
|
const authorName = blog.author
|
|
? `${blog.author.firstName || ''} ${blog.author.lastName || ''}`.trim() || 'نویسنده کنینا'
|
|
: 'نویسنده و پژوهشگر کنینا';
|
|
|
|
const publishedTime = safeIso(blog.publishedAt) || safeIso(blog.createdAt);
|
|
const modifiedTime = safeIso(blog.updatedAt) || publishedTime;
|
|
|
|
const keywordsList = blog.focusKeywords
|
|
? blog.focusKeywords.split(',').map((k: string) => k.trim())
|
|
: blog.keywords
|
|
? blog.keywords.split(',').map((k: string) => k.trim())
|
|
: undefined;
|
|
|
|
return {
|
|
title,
|
|
description,
|
|
keywords: keywordsList,
|
|
alternates: {
|
|
canonical: canonicalUrl,
|
|
},
|
|
robots: {
|
|
index: !blog.noIndex,
|
|
follow: !blog.noFollow,
|
|
googleBot: {
|
|
index: !blog.noIndex,
|
|
follow: !blog.noFollow,
|
|
'max-image-preview': 'large',
|
|
'max-snippet': -1,
|
|
'max-video-preview': -1,
|
|
},
|
|
},
|
|
openGraph: {
|
|
title: ogTitle,
|
|
description: ogDesc,
|
|
images: [
|
|
{
|
|
url: ogImageUrl,
|
|
width: 1200,
|
|
height: 630,
|
|
alt: blog.imageAlt || blog.title || title,
|
|
},
|
|
],
|
|
url: canonicalUrl,
|
|
siteName: config.brandNameFa,
|
|
type: "article",
|
|
publishedTime,
|
|
modifiedTime,
|
|
authors: [authorName],
|
|
tags: blog.tags?.map((t: any) => typeof t === 'string' ? t : t?.name).filter(Boolean) || [],
|
|
},
|
|
twitter: {
|
|
card: "summary_large_image",
|
|
title: ogTitle,
|
|
description: ogDesc,
|
|
images: [ogImageUrl],
|
|
},
|
|
};
|
|
}
|
|
|
|
export default async function BlogPostPage({ params }: { params: Promise<{ slug: string }> }) {
|
|
const { slug } = await params;
|
|
const blog = await getBlog(slug);
|
|
const config = await getSeoConfig();
|
|
|
|
if (!blog) {
|
|
notFound();
|
|
}
|
|
|
|
const rawSiteUrl = process.env.NEXT_PUBLIC_SITE_URL || config.canonicalBaseUrl || 'https://canina.ir';
|
|
const siteUrl = rawSiteUrl.replace(/\/+$/, '');
|
|
|
|
const authorName = blog.author
|
|
? `${blog.author.firstName || ''} ${blog.author.lastName || ''}`.trim() || 'نویسنده کنینا'
|
|
: 'نویسنده و پژوهشگر کنینا';
|
|
const publishedDate = safeLocalDate(blog.publishedAt) || safeLocalDate(blog.createdAt);
|
|
|
|
const canonicalUrl = blog.canonicalUrl
|
|
? blog.canonicalUrl.replace(/\/+$/, '')
|
|
: `${siteUrl}/blog/${slug}`;
|
|
|
|
const publishedIso = safeIso(blog.publishedAt) || safeIso(blog.createdAt) || new Date().toISOString();
|
|
const modifiedIso = safeIso(blog.updatedAt) || publishedIso;
|
|
|
|
const schemaType = blog.schemaType || 'BlogPosting';
|
|
|
|
const authorSchema = blog.author && (blog.author.firstName || blog.author.lastName)
|
|
? {
|
|
'@type': 'Person',
|
|
name: authorName,
|
|
url: `${siteUrl}/about`,
|
|
}
|
|
: {
|
|
'@type': 'Organization',
|
|
name: config.brandNameFa || 'کنینا ایران',
|
|
url: siteUrl,
|
|
};
|
|
|
|
const articleJsonLd = {
|
|
'@context': 'https://schema.org',
|
|
'@type': schemaType,
|
|
mainEntityOfPage: {
|
|
'@type': 'WebPage',
|
|
'@id': canonicalUrl,
|
|
},
|
|
headline: blog.title,
|
|
description: blog.excerpt || blog.metaDescription || blog.title,
|
|
image: blog.imageUrl
|
|
? [blog.imageUrl.startsWith('http') ? blog.imageUrl : `${siteUrl}${blog.imageUrl.startsWith('/') ? '' : '/'}${blog.imageUrl}`]
|
|
: [config.ogImageUrl.startsWith('http') ? config.ogImageUrl : `${siteUrl}${config.ogImageUrl.startsWith('/') ? '' : '/'}${config.ogImageUrl}`],
|
|
datePublished: publishedIso,
|
|
dateModified: modifiedIso,
|
|
author: authorSchema,
|
|
publisher: {
|
|
'@type': 'Organization',
|
|
name: config.brandNameFa || 'کنینا ایران',
|
|
logo: {
|
|
'@type': 'ImageObject',
|
|
url: `${siteUrl}/assets/logo.png`,
|
|
},
|
|
},
|
|
articleSection: blog.category?.name || 'دانشنامه سلامت حیوانات',
|
|
keywords: blog.focusKeywords || (Array.isArray(blog.tags) ? blog.tags.map((t: any) => typeof t === 'string' ? t : t?.name).filter(Boolean).join(', ') : undefined),
|
|
};
|
|
|
|
const breadcrumbJsonLd = {
|
|
'@context': 'https://schema.org',
|
|
'@type': 'BreadcrumbList',
|
|
itemListElement: [
|
|
{
|
|
'@type': 'ListItem',
|
|
position: 1,
|
|
name: 'خانه',
|
|
item: siteUrl,
|
|
},
|
|
{
|
|
'@type': 'ListItem',
|
|
position: 2,
|
|
name: 'مجله سلامت',
|
|
item: `${siteUrl}/blog`,
|
|
},
|
|
...(blog.category
|
|
? [
|
|
{
|
|
'@type': 'ListItem',
|
|
position: 3,
|
|
name: blog.category.name,
|
|
item: `${siteUrl}/blog?categoryId=${blog.category.id}`,
|
|
},
|
|
{
|
|
'@type': 'ListItem',
|
|
position: 4,
|
|
name: blog.title,
|
|
item: canonicalUrl,
|
|
},
|
|
]
|
|
: [
|
|
{
|
|
'@type': 'ListItem',
|
|
position: 3,
|
|
name: blog.title,
|
|
item: canonicalUrl,
|
|
},
|
|
]),
|
|
],
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: safeJsonLd(articleJsonLd) }}
|
|
/>
|
|
<script
|
|
type="application/ld+json"
|
|
dangerouslySetInnerHTML={{ __html: safeJsonLd(breadcrumbJsonLd) }}
|
|
/>
|
|
<BlogPostClient
|
|
blog={blog}
|
|
publishedDate={publishedDate}
|
|
authorName={authorName}
|
|
/>
|
|
</>
|
|
);
|
|
}
|