feat(metadata): enhance SEO by adding Open Graph and Twitter metadata across various pages

feat(blog): implement loading and error handling components for blog pages

feat(clinic): add loading and error handling components for clinic pages

feat(doctor): create loading and error handling components for doctor pages

feat(clinics): add loading component for clinics page

feat(specialties): improve metadata for specialties page with Open Graph and Twitter images

feat(layout): add structured data for Organization and WebSite in layout

fix(middleware): restrict middleware execution to specific routes to improve performance

chore(audit): add comprehensive SEO and performance audit documentation
This commit is contained in:
hamed
2026-06-21 11:38:39 +03:30
parent 6ec2f5a069
commit 06a877a725
19 changed files with 566 additions and 37 deletions
+14
View File
@@ -0,0 +1,14 @@
import { Skeleton } from "@mui/material";
export default function Loading() {
return (
<div className="p-4 flex flex-col gap-6" dir="rtl">
<Skeleton variant="rounded" height={180} className="!rounded-lg" />
<div className="flex flex-col gap-3">
<Skeleton variant="rounded" width={200} height={24} />
<Skeleton variant="rounded" width={160} height={20} />
</div>
<Skeleton variant="rounded" height={300} className="!rounded-lg" />
</div>
);
}
+36 -6
View File
@@ -1,18 +1,32 @@
import { cache } from "react";
import ClinicPage from "@/components/clinic";
import Layout from "@/components/layout/StLayout";
import { fetchReq } from "@/lib/req";
import { getStateInfo } from "@/lib/getStateInfo";
import { imageUrl } from "@/helper";
const API_URL = process.env.NEXT_PUBLIC_API_URL;
const getClinic = cache(async (slug) => {
try {
const res = await fetch(`${API_URL}/api/v1/clinic/${slug}`, {
next: { revalidate: 3600, tags: [`clinic-${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 API_URL = process.env.NEXT_PUBLIC_API_URL;
const { matchedCity } = await getStateInfo();
const siteName = matchedCity?.site_name || "نوبت 724";
try {
const res = await fetchReq(`${API_URL}/api/v1/clinic/${slug}`);
const clinic = res?.data?.data;
const clinic = await getClinic(slug);
if (!clinic) return {};
const title = `${clinic.title} | ${siteName}`;
@@ -37,16 +51,14 @@ export async function generateMetadata({ params }) {
async function Clinic({ params, searchParams }) {
const { slug } = await params;
const sp = await searchParams;
const API_URL = process.env.NEXT_PUBLIC_API_URL;
const page = Number(sp?.page) || 1;
const limit = 50;
const reqClinics = await fetchReq(`${API_URL}/api/v1/clinic/${slug}`);
const clinic = await getClinic(slug);
const reqDoctors = await fetchReq(
`${API_URL}/api/v1/clinic/doctor-list/${slug}?page=${page}&limit=${limit}`
);
const clinic = reqClinics?.data?.data || null;
const doctors = reqDoctors?.data?.data || [];
const meta = reqDoctors?.data?.meta;
const pagedoctors = meta
@@ -64,6 +76,18 @@ async function Clinic({ params, searchParams }) {
}
: null;
const breadcrumbJsonLd = clinic
? {
"@context": "https://schema.org",
"@type": "BreadcrumbList",
itemListElement: [
{ "@type": "ListItem", position: 1, name: "خانه", item: "https://www.nobat724.com" },
{ "@type": "ListItem", position: 2, name: "کلینیک‌ها", item: "https://www.nobat724.com/clinics" },
{ "@type": "ListItem", position: 3, name: clinic.title },
],
}
: null;
return (
<>
{jsonLd && (
@@ -72,6 +96,12 @@ async function Clinic({ params, searchParams }) {
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
)}
{breadcrumbJsonLd && (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }}
/>
)}
<Layout>
<ClinicPage data={clinic} doctors={doctors} slug={slug} pages={pagedoctors} />
</Layout>