This commit is contained in:
payacom
2026-07-12 11:52:53 +03:30
parent d83e91dd2d
commit 94b8ddc589
21 changed files with 429 additions and 169 deletions

18
src/app/(auth)/layout.tsx Normal file
View File

@@ -0,0 +1,18 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: {
default: "ورود و ثبت‌نام | مدستاگرام",
template: "%s | مدستاگرام",
},
description: "ورود یا ثبت‌نام در مدستاگرام",
robots: { index: false, follow: false },
};
export default function AuthLayout({
children,
}: {
children: React.ReactNode;
}) {
return children;
}

View File

@@ -3,6 +3,8 @@ import { cookies } from "next/headers";
import InfinitePosts from "@/components/academy/InfinitePakage";
import { Metadata } from "next";
import AcademyFilter from "@/components/academy/AcademyFilter";
import { pageSeo } from "@/config/pageSeo";
import { generatePageMetadata } from "@/utils/generatePageMetadata";
interface IProjectsProps {
params: Promise<{ slug?: string[] }>;
@@ -22,40 +24,41 @@ export async function generateMetadata({
searchParams,
}: IProjectsProps): Promise<Metadata> {
const filters = await searchParams;
// خواندن مقادیر فیلترها (اگر خالی بود مقدار پیش‌فرض در نظر گرفته میشه)
const isDefaultAcademy =
!filters.category &&
!filters.type &&
!filters.search &&
!filters.minPrice &&
!filters.maxPrice &&
!filters.hasOffer &&
!filters.sortBy;
if (isDefaultAcademy) {
return generatePageMetadata({
title: pageSeo.academy.title,
description: pageSeo.academy.description,
path: pageSeo.academy.path,
keywords: [...pageSeo.academy.keywords],
});
}
const categoryLabel = filters.category || "حوزه زیبایی";
const typeLabel = filters.type ? ` به صورت ${filters.type}` : "";
const descTypeLabel = filters.type ? ` بصورت ${filters.type}` : "";
// مقادیر پیش‌فرض زمانی که هیچ فیلتری انتخاب نشده
let title = "آموزش آنلاین انواع خدمات حوزه زیبایی و مدلینگ - مدستاگرام";
let description = "آموزش آنلاین تخصصی و حرفه‌ای از انواع اساتید حوزه زیبایی در همه زمینه‌ها اعم از آرایشگری، مدلینگ و عکاسی - مدستاگرام";
const title = `آموزش ${categoryLabel}${typeLabel} | آموزشگاه مدستاگرام`;
const description = `آموزش تخصصی و حرفه‌ای ${categoryLabel}${descTypeLabel} در آموزشگاه مدستاگرام. پکیج‌های آموزشی زیبایی، میکاپ، مدلینگ و عکاسی.`;
// اگر کاربر فیلتر "دسته بندی" یا "نوع" رو انتخاب کرده بود، متن‌ها داینامیک میشن
if (filters.category || filters.type) {
title = `آموزش آنلاین انواع خدمات ${categoryLabel}${typeLabel} - مدستاگرام`;
description = `آموزش آنلاین تخصصی و حرفه ای خدمات ${categoryLabel}${descTypeLabel} در مدستاگرام`;
}
// ساخت آدرس URL داینامیک بر اساس فیلترها
let canonicalUrl = "https://modstagram.com/academy";
const queryParams = new URLSearchParams();
if (filters.category) queryParams.append("category", filters.category);
if (filters.type) queryParams.append("type", filters.type);
const queryString = queryParams.toString();
if (queryString) {
canonicalUrl += `?${queryString}`;
}
const path = queryString
? `${pageSeo.academy.path}?${queryString}`
: pageSeo.academy.path;
return {
title,
description,
alternates: {
canonical: canonicalUrl,
},
};
return generatePageMetadata({ title, description, path });
}
export default async function Projects({
@@ -89,6 +92,9 @@ export default async function Projects({
return (
<Container>
<h1 className="sr-only">
{pageSeo.academy.title.replace(" | مدستاگرام", "")}
</h1>
<AcademyFilter />
<InfinitePosts filters={filters} token={token} />
</Container>

View File

@@ -1,16 +1,15 @@
import { Metadata } from "next";
import CourseDetail from "./CourseClient";
import { generatePageMetadata } from "@/utils/generatePageMetadata";
// نوع Props را به‌روز کردیم که params یک Promise باشد
type Props = {
params: Promise<{ id: string; title: string }>;
};
// تابع metadata با await روی params
export async function generateMetadata({ params }: Props): Promise<Metadata> {
try {
const { id } = await params; // اول await می‌کنیم، بعد id می‌گیریم
const { id, title: slugTitle } = await params;
try {
const res = await fetch(
`https://app.modstagram.ir/api/v1/academy/academy/get/curses?page=1&limit=20&status=accept&_id=${id}`,
{ cache: "no-store" }
@@ -32,23 +31,25 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
.replace(/<[^>]*>?/gm, "")
.substring(0, 150);
return {
title: `${courseName} - ${category} - مدستاگرام`,
description: `${category} - ${cleanCaption} - مدرس: ${teacher} - مدستاگرام`,
};
return generatePageMetadata({
title: `${courseName} | ${category} | آموزشگاه مدستاگرام`,
description: `${cleanCaption || courseName}. دسته: ${category}. مدرس: ${teacher}. پکیج آموزشی تخصصی در آموزشگاه مدستاگرام.`,
path: `/academy/${id}/${encodeURIComponent(slugTitle || courseName)}`,
type: "article",
});
}
} catch (error) {
console.error("خطا در دریافت اطلاعات سئو:", error);
}
return {
title: "جزئیات دوره - مدستاگرام",
description: "توضیحات دوره آکادمی - مدستاگرام",
};
return generatePageMetadata({
title: "جزئیات دوره | آموزشگاه مدستاگرام",
description: "مشاهده جزئیات پکیج آموزشی در آموزشگاه مدستاگرام.",
path: `/academy/${id}/${encodeURIComponent(slugTitle || "course")}`,
});
}
// صفحه اصلی را async کردیم تا بتوانیم params را await کنیم
export default async function CoursePage({ params }: Props) {
const { id } = await params; // استخراج id از params
const { id } = await params;
return <CourseDetail courseId={id} />;
}

View File

@@ -1,5 +1,12 @@
import Header from "@/components/main/Header";
import TabNavigation from "@/components/TabNavigation";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "ثبت پروژه جدید | مدستاگرام",
description: "ثبت درخواست همکاری و پروژه جدید در مدستاگرام",
robots: { index: false, follow: false },
};
export default function Layout({ children }: { children: React.ReactNode }) {
return (

View File

@@ -2,6 +2,7 @@ import { Metadata } from "next";
import { Suspense } from "react";
import ProjectDetailClient from "./ProjectDetailClient";
import PageLoader from "@/components/ui/PageLoader";
import { generatePageMetadata } from "@/utils/generatePageMetadata";
type ProjectDetailPageProps = {
params: Promise<{ id: string; title: string }>;
@@ -10,13 +11,14 @@ type ProjectDetailPageProps = {
export async function generateMetadata({
params,
}: ProjectDetailPageProps): Promise<Metadata> {
const { title } = await params;
const { id, title } = await params;
const decodedTitle = decodeURIComponent(title || "پروژه");
return {
title: `${decodedTitle} | پروژه‌ها | مدستاگرام`,
description: `جزئیات پروژه ${decodedTitle} در مدستاگرام`,
};
return generatePageMetadata({
title: `${decodedTitle} | پروژه‌های مدستاگرام`,
description: `جزئیات پروژه «${decodedTitle}» در مدستاگرام. مشاهده شرایط همکاری، بودجه و ثبت پیشنهاد.`,
path: `/projects/${id}/${encodeURIComponent(decodedTitle)}`,
});
}
export default async function ProjectDetailPage({

View File

@@ -4,12 +4,15 @@ import ProjectsFilter from "@/components/projects/ProjectsFilter";
import { fetchProjects } from "@/api/fetchProjects";
import { cookies } from "next/headers";
import { Metadata } from "next";
import { pageSeo } from "@/config/pageSeo";
import { generatePageMetadata } from "@/utils/generatePageMetadata";
export const metadata: Metadata = {
title: "پروژه‌ها | مدستاگرام",
description:
"لیست پروژه‌های مد، زیبایی و عکاسی در مدستاگرام. پروژه مناسب خود را پیدا کنید.",
};
export const metadata: Metadata = generatePageMetadata({
title: pageSeo.projects.title,
description: pageSeo.projects.description,
path: pageSeo.projects.path,
keywords: [...pageSeo.projects.keywords],
});
type ProjectsPageProps = {
searchParams: Promise<{
@@ -38,7 +41,9 @@ export default async function ProjectsPage({ searchParams }: ProjectsPageProps)
return (
<Container className="pb-28">
<h1 className="sr-only">پروژههای مد و زیبایی در مدستاگرام</h1>
<h1 className="sr-only">
{pageSeo.projects.title.replace(" | مدستاگرام", "")}
</h1>
<ProjectsFilter expertise={filters.expertise || ""} />
<InfiniteProjects
initialData={initialData}

View File

@@ -6,6 +6,8 @@ import StoriesBar from "@/components/stories/StoriesBar";
import Header from "@/components/main/Header";
import TabNavigation from "@/components/TabNavigation";
import { Metadata } from "next";
import { pageSeo } from "@/config/pageSeo";
import { generatePageMetadata } from "@/utils/generatePageMetadata";
// درون‌ریزی فایل‌های دیتا برای استخراج نام شهر و استان
import provinces from "@/data/provinces.json";
@@ -27,6 +29,25 @@ interface IModelsProps {
// تابع هوشمند تولید متادیتا
export async function generateMetadata({ params, searchParams }: IModelsProps): Promise<Metadata> {
const [urlParams, filters] = await Promise.all([params, searchParams]);
const isDefaultHome =
(!urlParams.slug || urlParams.slug.length === 0) &&
!filters.expertise &&
!filters.province &&
!filters.city &&
!filters.userLevel &&
!filters.rateFilter &&
!filters.hashtag;
if (isDefaultHome) {
return generatePageMetadata({
title: pageSeo.home.title,
description: pageSeo.home.description,
path: pageSeo.home.path,
keywords: [...pageSeo.home.keywords],
});
}
let locationLabel = "";
let levelLabel = "";
@@ -36,11 +57,13 @@ export async function generateMetadata({ params, searchParams }: IModelsProps):
// اولویت با متن فارسی ساخته شده در URL
if (decodedText.startsWith("استخدام")) {
return {
title: `${decodedText} | مدستاگرام`,
description: `لیست برترین متخصصین مد و زیبایی: ${decodedText}`,
alternates: { canonical: "https://modstagram.com" }
};
const title = `${decodedText} | مدستاگرام`;
const description = `لیست برترین متخصصین مد و زیبایی: ${decodedText}. پیدا کردن مدل، عکاس و آرایشگر حرفه‌ای در مدستاگرام.`;
return generatePageMetadata({
title,
description,
path: `/${urlParams.slug.join("/")}`,
});
}
const pSlug = decodeURIComponent(urlParams.slug[0]);
@@ -71,12 +94,14 @@ export async function generateMetadata({ params, searchParams }: IModelsProps):
const expertiseLabel = filters.expertise || "مدل، عکاس و آرایشگر";
const title = `استخدام ${expertiseLabel}${levelLabel}${locationLabel} | مدستاگرام`;
return {
title,
description: `پلتفرم تخصصی استخدام ${expertiseLabel}. بهترین متخصصین را در مدستاگرام پیدا کنید.`,
alternates: { canonical: "https://modstagram.com" },
};
const description = `پلتفرم تخصصی استخدام ${expertiseLabel}${locationLabel}. بهترین متخصصین حوزه زیبایی و مد را در مدستاگرام پیدا کنید.`;
const path =
urlParams.slug && urlParams.slug.length > 0
? `/${urlParams.slug.join("/")}`
: pageSeo.home.path;
return generatePageMetadata({ title, description, path });
}
export default async function Models({ params, searchParams }: IModelsProps) {
@@ -140,7 +165,15 @@ export default async function Models({ params, searchParams }: IModelsProps) {
<Header />
<Container>
{/* H1 مخفی برای بهبود سئو بر اساس آدرس صفحه */}
<h1 className="sr-only">استخدام {displayExpertise}{levelText} در مدستاگرام</h1>
<h1 className="sr-only">
{(!urlParams.slug || urlParams.slug.length === 0) &&
!filters.expertise &&
!filters.province &&
!filters.city &&
!filters.userLevel
? pageSeo.home.title.replace(" | مدستاگرام", "")
: `استخدام ${displayExpertise}${levelText} در مدستاگرام`}
</h1>
{/* کامپوننت فیلتر با مقدار تخصص فعلی */}
<ModelsFilter expertise={expertise || filters.expertise || ""} />

View File

@@ -1,5 +1,15 @@
import Footer from "@/components/main/Footer";
import Header from "@/components/main/Header";
import type { Metadata } from "next";
import { pageSeo } from "@/config/pageSeo";
import { generatePageMetadata } from "@/utils/generatePageMetadata";
export const metadata: Metadata = generatePageMetadata({
title: pageSeo.about.title,
description: pageSeo.about.description,
path: pageSeo.about.path,
keywords: [...pageSeo.about.keywords],
});
export default function Layout({ children }: { children: React.ReactNode }) {
return (

View File

@@ -4,6 +4,8 @@ import InfiniteBillboard from "@/components/billboards/InfiniteBillboard";
import BillboardsFilter from "@/components/billboards/BillboardPage/BillboardsFilter";
import { fetchBillboards } from "@/api/fetchBillboards";
import { Metadata } from "next";
import { pageSeo } from "@/config/pageSeo";
import { generatePageMetadata } from "@/utils/generatePageMetadata";
// درون‌ریزی دیتاها برای تبدیل متن به ID
import provinces from "@/data/provinces.json";
@@ -72,20 +74,38 @@ const getSEOData = (slugSegments: string[] = [], filters: any) => {
return { provinceId, cityId, category, categoryLabel, locationLabel };
};
// --- اصلاح تایتل و دیسکریپشن ---
// --- متادیتای سئو ---
export async function generateMetadata({ params, searchParams }: IBillboardsProps): Promise<Metadata> {
const [urlParams, filters] = await Promise.all([params, searchParams]);
const isDefaultBillboards =
(!urlParams.slug || urlParams.slug.length === 0) &&
!filters.province &&
!filters.city &&
!filters.category &&
!filters.search &&
!filters.sort;
if (isDefaultBillboards) {
return generatePageMetadata({
title: pageSeo.billboards.title,
description: pageSeo.billboards.description,
path: pageSeo.billboards.path,
keywords: [...pageSeo.billboards.keywords],
});
}
const { locationLabel, categoryLabel } = getSEOData(urlParams.slug, filters);
const title = `تبلیغات و بیلبورد ${categoryLabel}${locationLabel} | مدستاگرام`;
const description = `بهترین ${categoryLabel}${locationLabel}. لیست مراکز معتبر و بیلبوردهای تبلیغاتی در پلتفرم تخصصی مدستاگرام.`;
const title = `تبلیغات ${categoryLabel}${locationLabel} | بیلبورد مدستاگرام`;
const description = `بیلبورد و تبلیغات ${categoryLabel}${locationLabel} در مدستاگرام. فضای ویژه تبلیغات کسب‌وکارهای حوزه زیبایی برای دیده‌شدن بیشتر و جذب مشتری.`;
return {
title,
description,
alternates: { canonical: "https://modstagram.com/billboards" },
openGraph: { title, description, url: "https://modstagram.com/billboards", siteName: "مدستاگرام", locale: "fa_IR", type: "website" }
};
const path =
urlParams.slug && urlParams.slug.length > 0
? `/billboards/${urlParams.slug.join("/")}`
: pageSeo.billboards.path;
return generatePageMetadata({ title, description, path });
}
// --- اصلاح فیلتر و محتوا ---
@@ -115,7 +135,13 @@ export default async function Billboards({ params, searchParams }: IBillboardsPr
return (
<Container>
<h1 className="sr-only">
تبلیغات {categoryLabel}{locationLabel} در مدستاگرام
{(!urlParams.slug || urlParams.slug.length === 0) &&
!filters.province &&
!filters.city &&
!filters.category &&
!filters.search
? pageSeo.billboards.title.replace(" | مدستاگرام", "")
: `تبلیغات ${categoryLabel}${locationLabel} در مدستاگرام`}
</h1>
<BillboardsFilter />

View File

@@ -73,11 +73,11 @@ export async function generateMetadata({
const address = billboard.address || `${province} ${city} ${neighborhood}`;
const dynamicDescription = `${address} - ${billboard.description || ""}`.substring(0, 160);
// کلمه "مدستاگرام" توسط تابع generatePageMetadata به انتهای تایتل اضافه می‌شود
return generatePageMetadata({
title: dynamicTitle,
description: dynamicDescription,
path: `/billboards/${id}/${encodeURIComponent(urlSlug)}`,
appendSiteName: true,
});
}

View File

@@ -0,0 +1,18 @@
import type { Metadata } from "next";
import { pageSeo } from "@/config/pageSeo";
import { generatePageMetadata } from "@/utils/generatePageMetadata";
export const metadata: Metadata = generatePageMetadata({
title: pageSeo.explore.title,
description: pageSeo.explore.description,
path: pageSeo.explore.path,
keywords: [...pageSeo.explore.keywords],
});
export default function ExploreLayout({
children,
}: {
children: React.ReactNode;
}) {
return children;
}

View File

@@ -6,6 +6,7 @@ import ExploreFilterBar from "@/components/explore/ExploreFilterBar";
import ExploreGrid from "@/components/explore/ExploreGrid";
import Header from "@/components/main/Header";
import { ExploreFilterId } from "@/constants/exploreFilters";
import { pageSeo } from "@/config/pageSeo";
import { useCallback, useState } from "react";
import toast from "react-hot-toast";
@@ -54,6 +55,9 @@ export default function ExplorePage() {
<>
<Header />
<Container className="pb-28">
<h1 className="sr-only">
{pageSeo.explore.title.replace(" | مدستاگرام", "")}
</h1>
<ExploreFilterBar
activeFilter={activeFilter}
onFilterChange={handleFilterChange}

View File

@@ -1,6 +1,8 @@
import type { Metadata } from "next";
import localFont from "next/font/local";
import { defaultSEOConfig } from "@/config/seoConfig";
import { pageSeo, siteKeywords } from "@/config/pageSeo";
import SiteJsonLd from "@/components/seo/SiteJsonLd";
import "./globals.css";
import Layout from "@/components/Layout";
@@ -10,24 +12,15 @@ const iranSansFont = localFont({
src: "./../../public/fonts/IRANSansX-Regular.woff",
});
const defaultTitle =
typeof defaultSEOConfig.title === "string"
? defaultSEOConfig.title
: "مدستاگرام";
const defaultTitle = pageSeo.home.title;
export const metadata: Metadata = {
title: {
default: defaultTitle,
},
description: defaultSEOConfig.description,
description: pageSeo.home.description,
metadataBase: new URL("https://modstagram.com"),
keywords: [
"مادستاگرام",
"مدلینگ",
"بیلبورد تبلیغاتی",
"پست مدل",
"آموزشگاه مدلینگ",
],
keywords: siteKeywords,
authors: [{ name: "Modstagram", url: "https://modstagram.com" }],
creator: "Modstagram",
alternates: {
@@ -80,6 +73,7 @@ export default function RootLayout({
return (
<html lang="fa" dir="rtl" suppressHydrationWarning>
<body className={`${iranSansFont.className} antialiased`}>
<SiteJsonLd />
<RegisterSW />
<Layout>{children}</Layout>
</body>

View File

@@ -2,6 +2,13 @@ import Header from "@/components/main/Header";
import TabNavigation from "@/components/TabNavigation";
import { Suspense } from "react";
import PageLoader from "@/components/ui/PageLoader";
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "انتشار پست | مدستاگرام",
description: "انتشار پست، ویدئو یا استوری در مدستاگرام",
robots: { index: false, follow: false },
};
export default function Layout({ children }: { children: React.ReactNode }) {
return (

View File

@@ -1,9 +1,10 @@
import { Metadata } from "next";
import { fetchPostById } from "@/api/fetchPostById";
import PostFeedView from "@/components/posts/PostFeedView";
import { IMAGE_BASE_URL, buildStorageUrl } from "@/components/main/BaseUrl";
import { buildStorageUrl } from "@/components/main/BaseUrl";
import { Suspense } from "react";
import PageLoader from "@/components/ui/PageLoader";
import { generatePageMetadata } from "@/utils/generatePageMetadata";
type Props = { params: Promise<{ id: string }> };
@@ -11,34 +12,23 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { id } = await params;
const post = await fetchPostById(id);
const title = post?.caption
? `${post.caption.slice(0, 60)} | مادستاگرام`
: `پست | مادستاگرام`;
? `${post.caption.slice(0, 60)} | مدستاگرام`
: "پست | مدستاگرام";
const description =
post?.caption?.slice(0, 160) ||
"مشاهده پست در مادستاگرام — پلتفرم مدلینگ و تبلیغات";
"مشاهده پست در مدستاگرام — پلتفرم تخصصی حوزه زیبایی، مدلینگ و عکاسی";
const ogImage = post?.files?.[0]?.path
? buildStorageUrl(post.files[0].path)
: undefined;
return {
return generatePageMetadata({
title,
description,
alternates: { canonical: `https://modstagram.com/posts/${id}` },
openGraph: {
title,
description,
url: `https://modstagram.com/posts/${id}`,
type: "article",
images: ogImage ? [{ url: ogImage, width: 1080, height: 1350 }] : [],
},
twitter: {
card: "summary_large_image",
title,
description,
images: ogImage ? [ogImage] : [],
},
robots: { index: true, follow: true },
};
path: `/posts/${id}`,
type: "article",
imageUrl: ogImage,
imageAlt: post?.caption?.slice(0, 80) || "پست مدستاگرام",
});
}
export default async function PostPage({ params }: Props) {
@@ -55,7 +45,7 @@ export default async function PostPage({ params }: Props) {
__html: JSON.stringify({
"@context": "https://schema.org",
"@type": "SocialMediaPosting",
headline: post.caption?.slice(0, 110) || "پست مادستاگرام",
headline: post.caption?.slice(0, 110) || "پست مدستاگرام",
url: `https://modstagram.com/posts/${id}`,
datePublished: post.createdAt,
author: {

View File

@@ -4,6 +4,8 @@ import SearchFilter from "@/components/search/SearchFilter";
import { fetchSearch } from "@/api/fetchSearch";
import InfiniteSearch from "@/components/search/InfiniteSearch";
import { Metadata } from "next";
import { pageSeo } from "@/config/pageSeo";
import { generatePageMetadata } from "@/utils/generatePageMetadata";
interface ISearchProps {
searchParams: Promise<{
@@ -15,30 +17,24 @@ interface ISearchProps {
// تبدیل متادیتای ثابت به داینامیک
export async function generateMetadata({ searchParams }: ISearchProps): Promise<Metadata> {
const { search } = await searchParams;
const title = search
? `نتایج جستجو برای "${search}" | مدستاگرام`
: "جستجوی بیلبوردها و پروفایل‌ها | مدستاگرام";
const description = search
? `نتایج جستجوی تخصصی برای ${search} در حوزه مد و زیبایی مدستاگرام.`
: "جستجو و فیلتر بیلبورد، فروشگاه، شهر و دسته‌بندی در پلتفرم مدستاگرام.";
if (!search) {
return generatePageMetadata({
title: pageSeo.search.title,
description: pageSeo.search.description,
path: pageSeo.search.path,
keywords: [...pageSeo.search.keywords],
});
}
return {
const title = `نتایج جستجو برای «${search}» | مدستاگرام`;
const description = `نتایج جستجوی تخصصی برای «${search}» در حوزه مد، زیبایی و مدلینگ در مدستاگرام.`;
return generatePageMetadata({
title,
description,
alternates: {
canonical: "https://modstagram.com/search",
},
openGraph: {
title,
description,
url: "https://modstagram.com/search",
siteName: "مدستاگرام",
locale: "fa_IR",
type: "website",
}
};
path: `${pageSeo.search.path}?search=${encodeURIComponent(search)}`,
});
}
export default async function Search({ searchParams }: ISearchProps) {

View File

@@ -6,6 +6,8 @@ export const metadata: Metadata = {
default: "تنظیمات",
template: "%s | مدستاگرام",
},
description: "تنظیمات حساب کاربری در مدستاگرام",
robots: { index: false, follow: false },
};
export default function Layout({ children }: { children: React.ReactNode }) {

View File

@@ -0,0 +1,43 @@
import { pageSeo, SITE_NAME, SITE_URL } from "@/config/pageSeo";
/** JSON-LD برای موتورهای جستجو و سیستم‌های هوش مصنوعی */
export default function SiteJsonLd() {
const schema = {
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": `${SITE_URL}/#organization`,
name: SITE_NAME,
url: SITE_URL,
logo: `${SITE_URL}/images/icons/256x256.png`,
description: pageSeo.home.description,
inLanguage: "fa-IR",
},
{
"@type": "WebSite",
"@id": `${SITE_URL}/#website`,
name: SITE_NAME,
url: SITE_URL,
description: pageSeo.home.description,
inLanguage: "fa-IR",
publisher: { "@id": `${SITE_URL}/#organization` },
potentialAction: {
"@type": "SearchAction",
target: {
"@type": "EntryPoint",
urlTemplate: `${SITE_URL}/search?search={search_term_string}`,
},
"query-input": "required name=search_term_string",
},
},
],
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
);
}

104
src/config/pageSeo.ts Normal file
View File

@@ -0,0 +1,104 @@
export const SITE_URL = "https://modstagram.com";
export const SITE_NAME = "مدستاگرام";
export type PageSeoEntry = {
title: string;
description: string;
path: string;
keywords?: string[];
};
/** متادیتای صفحات اصلی — منبع واحد برای title / description */
export const pageSeo = {
home: {
title: "مدستاگرام | پلتفرم تخصصی پیدا کردن مدل، عکاس و آرایشگر",
description:
"مدستاگرام، شبکه اجتماعی تخصصی حوزه زیبایی برای پیدا کردن مدل، عکاس و آرایشگر حرفه‌ای، مشاهده پست‌ها، ارتباط مستقیم و همکاری در پروژه‌های زیبایی و مد.",
path: "/",
keywords: [
"مدستاگرام",
"مدل",
"عکاس",
"آرایشگر",
"مدلینگ",
"شبکه اجتماعی زیبایی",
"استخدام مدل",
],
},
academy: {
title: "آموزشگاه مدستاگرام | پکیج‌های آموزشی زیبایی، میکاپ و عکاسی",
description:
"در آموزشگاه مدستاگرام، پکیج‌های آموزشی تخصصی حوزه زیبایی، میکاپ، مدلینگ و عکاسی را ببینید، آموزش بگذارید و مهارت‌های خود را حرفه‌ای‌تر کنید.",
path: "/academy",
keywords: [
"آموزشگاه مدستاگرام",
"آموزش میکاپ",
"آموزش مدلینگ",
"آموزش عکاسی",
"پکیج آموزشی زیبایی",
],
},
projects: {
title: "پروژه‌های مدستاگرام | ثبت و انتخاب همکاری‌های زیبایی و مدلینگ",
description:
"در بخش پروژه‌های مدستاگرام، درخواست همکاری ثبت کنید یا از بین پروژه‌های زیبایی، مدلینگ، عکاسی و آرایشگری بهترین فرصت را برای همکاری انتخاب کنید.",
path: "/projects",
keywords: [
"پروژه مدلینگ",
"پروژه زیبایی",
"همکاری مدل",
"درخواست همکاری",
"پروژه عکاسی",
],
},
billboards: {
title: "بیلبورد مدستاگرام | تبلیغات هدفمند کسب‌وکارهای حوزه زیبایی",
description:
"بیلبورد مدستاگرام، فضای ویژه تبلیغات کسب‌وکارهای حوزه زیبایی، سالن‌های آرایش، برندهای آرایشی و خدمات تخصصی برای دیده‌شدن بیشتر و جذب مشتری.",
path: "/billboards",
keywords: [
"بیلبورد مدستاگرام",
"تبلیغات زیبایی",
"تبلیغ سالن آرایش",
"تبلیغات مد و زیبایی",
],
},
explore: {
title: "اکسپلور مدستاگرام | کشف پست‌ها و محتوای حوزه زیبایی",
description:
"در اکسپلور مدستاگرام، جدیدترین پست‌ها، ریل‌ها و محتوای تخصصی حوزه مد، زیبایی، میکاپ و عکاسی را کشف کنید.",
path: "/explore",
keywords: ["اکسپلور", "پست زیبایی", "ریل مد", "محتوای مدلینگ"],
},
search: {
title: "جستجو در مدستاگرام | پیدا کردن مدل، عکاس، آرایشگر و بیلبورد",
description:
"جستجوی تخصصی در مدستاگرام برای یافتن مدل، عکاس، آرایشگر، بیلبورد و کسب‌وکارهای حوزه زیبایی.",
path: "/search",
keywords: ["جستجوی مدل", "جستجوی آرایشگر", "جستجوی بیلبورد"],
},
about: {
title: "درباره مدستاگرام | پلتفرم تخصصی حوزه زیبایی و مد",
description:
"درباره مدستاگرام، پلتفرم تخصصی شبکه اجتماعی و همکاری در حوزه زیبایی، مدلینگ، عکاسی و آرایشگری.",
path: "/about-us",
keywords: ["درباره مدستاگرام", "تماس مدستاگرام"],
},
} as const satisfies Record<string, PageSeoEntry>;
export const siteKeywords = [
"مدستاگرام",
"مدل",
"عکاس",
"آرایشگر",
"مدلینگ",
"زیبایی",
"میکاپ",
"عکاسی",
"پروژه مد",
"بیلبورد زیبایی",
"آموزش میکاپ",
"شبکه اجتماعی زیبایی",
"استخدام مدل",
"همکاری مدلینگ",
];

View File

@@ -1,23 +1,20 @@
import { NextSeoProps } from "next-seo";
const BASE_URL = "https://modstagram.com";
import { pageSeo, SITE_URL } from "@/config/pageSeo";
export const defaultSEOConfig: NextSeoProps = {
title: "مدستاگرام - استخدام مدل و عکاس و آرایشگر در محل",
description:
"پلتفرم استخدام مدلینگ، آرایشگر در محل و عکاس حرفه ای مدل مزون، مدل عروس، مدل عکاسی، مدل آرایشی اپلیکیشنی اختصاصی استخدام و تبلیغات حوزه زیبایی",
canonical: BASE_URL,
title: pageSeo.home.title,
description: pageSeo.home.description,
canonical: SITE_URL,
openGraph: {
type: "website",
locale: "fa_IR",
url: BASE_URL,
url: SITE_URL,
site_name: "مدستاگرام",
title: "مدستاگرام - استخدام مدل و عکاس و آرایشگر در محل",
description:
"پلتفرم استخدام مدلینگ، آرایشگر در محل و عکاس حرفه ای مدل مزون، مدل عروس، مدل عکاسی، مدل آرایشی اپلیکیشنی اختصاصی استخدام و تبلیغات حوزه زیبایی",
title: pageSeo.home.title,
description: pageSeo.home.description,
images: [
{
url: `${BASE_URL}/images/icons/256x256.png`,
url: `${SITE_URL}/images/icons/256x256.png`,
width: 1200,
height: 630,
alt: "مدستاگرام",

View File

@@ -1,69 +1,74 @@
import { defaultSEOConfig } from "@/config/seoConfig";
import { SITE_URL } from "@/config/pageSeo";
import { Metadata } from "next";
interface PageMetadataOptions {
title: string;
description: string;
path: string; // این باید مسیر نسبی صفحه باشد، مثلا '/users/123'
type?: "website" | "article" | "profile"; // انواع Open Graph را اضافه کنید
path: string;
type?: "website" | "article" | "profile";
publishedTime?: string;
modifiedTime?: string;
imageUrl?: string; // آدرس تصویر خاص برای این صفحه
imageAlt?: string; // متن جایگزین تصویر
imageUrl?: string;
imageAlt?: string;
keywords?: string[];
/** اگر true باشد « - مدستاگرام» به انتهای عنوان اضافه می‌شود */
appendSiteName?: boolean;
}
export function generatePageMetadata(options: PageMetadataOptions): Metadata {
const baseUrl = "https://modstagram.com"; // مطمئن شوید BASE_URL یکسان باشد
const fullUrl = `${baseUrl}${options.path}`;
const defaultOgImage = defaultSEOConfig.openGraph?.images?.[0];
const finalTitle = options.title
? `${options.title} - مدستاگرام`
: defaultSEOConfig.title; // عنوان صفحه + نام سایت
const finalDescription = options.description || defaultSEOConfig.description;
export function generatePageMetadata(options: PageMetadataOptions): Metadata {
const fullUrl = `${SITE_URL}${options.path.startsWith("/") ? options.path : `/${options.path}`}`;
const finalTitle = options.appendSiteName
? `${options.title} | مدستاگرام`
: options.title;
const finalDescription = options.description || defaultSEOConfig.description || "";
const images = options.imageUrl
? [
{
url: options.imageUrl,
width: 1200, // توصیه می‌شود این ابعاد را رعایت کنید
width: 1200,
height: 630,
alt: options.imageAlt || options.title,
},
]
: defaultSEOConfig.openGraph?.images || []; // اگر تصویر خاصی نیست، از تصاویر پیش‌فرض استفاده کند
: defaultOgImage
? [defaultOgImage]
: [];
return {
title: {
absolute: finalTitle,
},
description: finalDescription,
keywords: options.keywords,
alternates: {
canonical: fullUrl,
},
robots: {
index: true,
follow: true,
},
openGraph: {
title: finalTitle,
description: finalDescription,
url: fullUrl,
siteName: defaultSEOConfig.openGraph?.site_name,
siteName: defaultSEOConfig.openGraph?.site_name || "مدستاگرام",
locale: "fa_IR",
type: options.type || "website",
// images: images,
images,
},
twitter: {
card: "summary_large_image",
site: defaultSEOConfig.twitter?.site,
creator: defaultSEOConfig.twitter?.handle,
title: finalTitle,
description: finalDescription,
images: images.map((img) => img.url), // تصاویر توییتر
images: images.map((img) => img.url),
},
// اگر تگ های meta اضافی از defaultSEOConfig نیاز دارید
// ...defaultSEOConfig.additionalMetaTags.reduce((acc: any, tag: any) => {
// if (tag.name === 'google-site-verification') acc.google = tag.content;
// if (tag.name === 'samandehi') acc.samandehi = tag.content;
// return acc;
// }, {}),
// برای meta tags های خاص مقاله/پروفایل
...(options.type === "article" &&
options.publishedTime &&
options.modifiedTime
@@ -74,13 +79,5 @@ export function generatePageMetadata(options: PageMetadataOptions): Metadata {
},
}
: {}),
...(options.type === "profile"
? {
// اینجا می توانید متاتگ های schema.org برای پروفایل را اضافه کنید اگر از ساختار json-ld استفاده می کنید
// @context: 'https://schema.org',
// @type: 'ProfilePage',
// ...
}
: {}),
};
}