سئو: بررسی سئو در صفحه نوشتن + عنوان و توضیح متای هر صفحه

- پنل «بررسی سئو» در صفحه نوشتن مقاله — سبز/نارنجی/قرمز سازگار با فارسی:
  طول عنوان، کلمه کلیدی هدف (در عنوان/پاراگراف اول/تیتر H2)، طول چکیده،
  تعداد واژه، تیتربندی، متن جایگزین تصاویر، پیوندها و slug.
  کلمه کلیدی در localStorage نگه داشته می‌شود (راهنمای ویرایشگر است، نه محتوا)
- جدول seo_meta: عنوان و توضیح متای هر صفحه (page_key برای هر ۸ صفحه،
  seed خودکار، CRUD در /api/admin/seo-meta، خواندن در GET /api/site)
- تزریق عنوان/توضیح متا در فرانت‌اند: صفحه‌های فهرست از دیتابیس؛ صفحه
  مقاله و پروژه از محتوای خودشان (بدون تداخل با اثر سطح والد)
- دکمه سئو روی هیرو هر صفحه و کنار هیرو صفحه اصلی (فقط حالت ویرایش)
This commit is contained in:
MMD
2026-09-07 03:12:12 +03:30
parent 17345daa81
commit a29deb8bb1
19 changed files with 380 additions and 15 deletions
+14 -4
View File
@@ -16,6 +16,7 @@ import { ArticleDetail } from './pages/ArticleDetail';
import { FaqPage } from './pages/FaqPage';
import { ContactPage } from './pages/ContactPage';
import { SiteProvider, useSiteData } from './api';
import { seoKeyForPath, setMetaDescription } from './seo';
import { AdminProvider, useAdmin } from './admin/AdminContext';
import { AdminBar } from './admin/AdminBar';
import { AdminLoginPage } from './admin/AdminLoginPage';
@@ -64,8 +65,21 @@ function AnimatedRoutes() {
function AppChrome() {
const { pathname } = useLocation();
const { isAuthenticated } = useAdmin();
const { data } = useSiteData();
const isAdminPage = pathname.startsWith('/admin');
// عنوان و توضیح متای هر صفحه از دیتابیس (سئو) — جز صفحه‌های جزئیات که خودشان ست می‌کنند
useEffect(() => {
if (isAdminPage || !data) return;
const key = seoKeyForPath(pathname);
if (!key) return;
const meta = data.seo_meta?.find((s) => s.page_key === key);
const brand = data.brand.name;
const navLabel = data.nav.find((n) => n.href === `/${key}`)?.label;
document.title = meta?.title || (key === 'home' ? brand : navLabel ? `${navLabel} | ${brand}` : brand);
setMetaDescription(meta?.description ?? '');
}, [pathname, data, isAdminPage]);
return (
<>
{!isAdminPage && <Header />}
@@ -80,10 +94,6 @@ function AppChrome() {
function AppShell() {
const { data, error, reload } = useSiteData();
useEffect(() => {
if (data?.brand.name) document.title = data.brand.name;
}, [data?.brand.name]);
return (
<div dir="rtl" lang="fa" className="min-h-screen w-full overflow-x-hidden bg-ink font-pinar text-ivory">
<Preloader />