feat: Enhance metadata generation for About Us and Contact Us pages, implement legacy slug resolution for blog posts, and add city-specific intros

This commit is contained in:
hamed
2026-08-09 07:14:15 +03:30
parent 9cfbd2e002
commit 002f388ac8
12 changed files with 334 additions and 28 deletions
+12 -3
View File
@@ -3,10 +3,19 @@ import Layout from "@/components/layout/StLayout";
import { getStateInfo } from "@/lib/getStateInfo";
export async function generateMetadata() {
const { matchedCity } = await getStateInfo();
const { matchedCity, matchedState, isRoot } = await getStateInfo();
const siteName = matchedCity?.site_name || "نوبت 724";
const title = `درباره ما | ${siteName}`;
const description = `آشنایی با ${siteName}، سیستم آنلاین نوبت‌دهی پزشکی. هدف ما ارائه خدمات سریع و کارآمد رزرو نوبت پزشکی برای همه مردم است.`;
const cityName = isRoot ? null : matchedCity?.name;
const provinceName = isRoot ? null : matchedState?.name;
// صفحه self-canonical است؛ عنوان و توضیح باید شهری باشند وگرنه ۳۵ دامنه یک
// متادیتا اعلام می‌کنند و همان duplicate با شکل تازه برمی‌گردد.
const title = cityName
? `درباره ما | نوبت‌دهی آنلاین پزشکان ${cityName} | ${siteName}`
: `درباره ما | ${siteName}`;
const description = cityName
? `آشنایی با ${siteName}، سامانه رزرو آنلاین نوبت پزشکان ${cityName}${provinceName ? ` و ${provinceName}` : ""}. جست‌وجوی پزشک بر اساس تخصص و ثبت نوبت بدون تماس تلفنی.`
: `آشنایی با ${siteName}، سیستم آنلاین نوبت‌دهی پزشکی. هدف ما ارائه خدمات سریع و کارآمد رزرو نوبت پزشکی برای همه مردم است.`;
const image = "/assets/images/og-image.png";
return {
title,
+28 -3
View File
@@ -1,7 +1,7 @@
import { cache } from "react";
import BlogPage from "@/components/blog";
import Layout from "@/components/layout/StLayout";
import { notFound } from "next/navigation";
import { notFound, permanentRedirect } from "next/navigation";
import { fetchReq } from "@/lib/req";
import { getStateInfo } from "@/lib/getStateInfo";
import { getBlogCanonicalOrigin } from "@/lib/getCanonicalUrl";
@@ -13,6 +13,7 @@ import {
import { safeJsonLd } from "@/lib/sanitize";
import { decodeRouteParam } from "@/lib/routeParams";
import { normalizeBlog, blogCover } from "@/helper";
import legacySlugs from "@/data/blogLegacySlugs.json";
const API_URL = process.env.NEXT_PUBLIC_API_URL;
@@ -39,13 +40,31 @@ async function getScopedBlog(slug) {
return getBlog(slug, domainScopeCityId({ matchedCity, isRoot }));
}
/**
* پست را با هر شناسه‌ای که روزی به آن اشاره داشته پیدا می‌کند.
*
* بک‌اند خودش slug و uuid و topic_slug و پسوند ۸ کاراکتریِ uuid را resolve می‌کند.
* چیزی که آنجا قابل resolve نیست، اسلاگ‌های لاتینِ نسل قبل‌اند: هیچ ردی در دیتابیس
* ندارند چون هنگام تغییر عنوان بازنویسی شدند. Search Console همان‌ها را روی
* `behbahan-nobat.ir` ۴۰۴ گزارش کرد، پس نگاشتشان در data/blogLegacySlugs.json
* دستی نگه داشته می‌شود — مقدارِ نگاشت پیشوند uuid است، نه اسلاگ مقصد، تا فیلتر
* شهریِ همان درخواست دوباره اعمال شود و پستِ شهر دیگر ریدایرکتِ ۴۰۴ نسازد.
*/
async function resolveScopedBlog(slug) {
const blog = await getScopedBlog(slug);
if (blog) return blog;
const legacyUuidPrefix = legacySlugs[slug];
return legacyUuidPrefix ? getScopedBlog(legacyUuidPrefix) : null;
}
export async function generateMetadata({ params }) {
const { slug: rawSlug } = await params;
const slug = decodeRouteParam(rawSlug);
const { matchedCity } = await getStateInfo();
const siteName = matchedCity?.site_name || "نوبت 724";
const blog = await getScopedBlog(slug);
const blog = await resolveScopedBlog(slug);
if (!blog) notFound();
try {
@@ -123,9 +142,15 @@ async function Blog({ params }) {
const slug = decodeRouteParam(rawSlug);
const slugPath = encodeURIComponent(slug);
const blog = normalizeBlog(await getScopedBlog(slug));
const blog = normalizeBlog(await resolveScopedBlog(slug));
if (!blog) notFound();
// اسلاگ کهنه (عنوان عوض شده یا نسل لاتین) به آدرس قطعی منتقل می‌شود؛ دو URL برای
// یک محتوا باقی نمی‌ماند و لینک‌های قدیمیِ ایندکس‌شده اعتبارشان را می‌برند.
if (blog.slug && blog.slug !== slug) {
permanentRedirect(`/blog/${encodeURIComponent(blog.slug)}`);
}
const blogCityId = extractEntityCityId(blog);
const blogCity = findCityById(blogCityId);
const origin = getBlogCanonicalOrigin(blogCityId);
+25
View File
@@ -1,5 +1,30 @@
import ContactUsPage from "@/components/contactUs";
import Layout from "@/components/layout/StLayout";
import { getStateInfo } from "@/lib/getStateInfo";
export async function generateMetadata() {
const { matchedCity, matchedState, isRoot } = await getStateInfo();
const siteName = matchedCity?.site_name || "نوبت 724";
const cityName = isRoot ? null : matchedCity?.name;
const provinceName = isRoot ? null : matchedState?.name;
// صفحه self-canonical است؛ عنوان و توضیح هم باید شهری باشند وگرنه ۳۵ دامنه
// یک متادیتا اعلام می‌کنند.
const title = cityName
? `تماس با ما | نوبت‌دهی آنلاین ${cityName} | ${siteName}`
: `تماس با ما | ${siteName}`;
const description = cityName
? `راه‌های ارتباط با ${siteName}: تلفن، واتساپ و ایمیل. پشتیبانی رزرو و لغو نوبت پزشکان ${cityName}${provinceName ? ` و ${provinceName}` : ""}.`
: `راه‌های ارتباط با ${siteName}: تلفن، واتساپ و ایمیل. پشتیبانی رزرو و لغو نوبت آنلاین پزشکان.`;
const image = "/assets/images/og-image.png";
return {
title,
description,
openGraph: { title, description, images: [image] },
twitter: { card: "summary_large_image", title, description, images: [image] },
};
}
function ContactUs() {
return (