feat(seo): ایندکسپذیری کامل، محتوای سمت سرور برای خزندههای AI و رفع باگ ایندکس
سئوی سمت سرور (backend/app/seo_pages.py) - متا/canonical/hreflang/JSON-LD برای همهٔ صفحات عمومی (شش صفحهٔ استاتیک فارسی که قبلاً پوستهٔ خالی بودند) - canonical سمت سرور (قبلاً فقط با JS ست میشد)، ۴۰۴ واقعی برای مسیرهای ناموجود - اسکیما: Organization با لوگوی ImageObject، FAQPage، LocalBusiness، BreadcrumbList، CreativeWork پروژه با همهٔ تصاویر بهصورت ImageObject (نام/توضیح + موقعیت پروژه) - hreflang فقط وقتی ترجمهٔ انگلیسی واقعاً وجود دارد؛ sitemap با URLهای درصدگذاریشده - llms.txt و robots.txt تازهساز از دیتابیس؛ سیاست صریح خزندههای AI + Allow: /api/site - _site_content_html: محتوای واقعی سایت بهصورت HTML خوانا در <noscript> — خزندههای بدون جاوااسکریپت (GPTBot/OAI-SearchBot/ClaudeBot/PerplexityBot) از ۲۴۰ نویسه به ۸٬۵۰۰+ نویسه محتوای معنایی میرسند - Cache-Control: no-store روی HTML و llms.txt (جلوگیری از کش کهنهٔ CDN)، max-age روی sitemap فرانتاند - api.tsx: خواندن دادهٔ تزریقشدهٔ سرور (window.__SITE__) بهعنوان حالت اولیه — رندر بدون XHR - App.tsx: اسکلت بارگذاری بهجای DOM خالی (رفع Soft 404 گوگل) - index.html: فاوآیکون واقعی برند (قبلاً به vite.svg ناموجود اشاره میکرد) + preload فونت زیرساخت - Dockerfile: آینهٔ PyPI (Tsinghua/Aliyun) — PyPI برای IP ایران بلاک است - Caddyfile: هر مسیر پسونددار بدون فایل واقعی ۴۰۴ میگیرد (قبلاً HTML اسپا برمیگرداند) - docker-compose.prod.yml: مسیرهای seo برای صفحات استاتیک + llms.txt + کلیدهای IndexNow داده - seed_data*.py: حذف دفاتر و معرفیهای دمو (که در هر استقرار برمیگشتند)؛ تنها دفتر واقعی: اصفهان، ملاصدرا شمالی داراییها - فاوآیکون/آیکونهای برند از لوگوی رسمی + کلیدهای IndexNow
This commit is contained in:
@@ -149,7 +149,14 @@ function AppShell() {
|
||||
<ScrollToTop />
|
||||
<AppChrome />
|
||||
</>
|
||||
) : null}
|
||||
) : (
|
||||
// اسکلت اولیه تا رسیدن دادههای API — صفحه هرگز خالی رندر نمیشود.
|
||||
// (خزندههای رندرکننده — مثل گوگل — صفحهٔ خالی را Soft 404 میبینند)
|
||||
<div className="flex min-h-screen flex-col items-center justify-center px-6 text-center">
|
||||
<h1 className="text-2xl font-bold text-ivory">{t('brandFallback')}</h1>
|
||||
<p className="mt-4 text-sm font-light text-ivory/70">{t('loading')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
+15
-3
@@ -228,6 +228,14 @@ interface SiteContextValue {
|
||||
updateSite: (mutate: (data: SiteData) => void) => void
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
/** دادهٔ سایت که بکاند داخل HTML تزریق میکند (app/seo_pages.py) —
|
||||
* رندر بدون انتظار برای درخواست /api/site؛ خزندههای رندرکننده صفحهٔ کامل میبینند */
|
||||
__SITE__?: SiteData
|
||||
}
|
||||
}
|
||||
|
||||
const SiteContext = createContext<SiteContextValue>({
|
||||
data: null,
|
||||
error: false,
|
||||
@@ -242,7 +250,10 @@ const SiteContext = createContext<SiteContextValue>({
|
||||
const SITE_TIMEOUT_MS = 15_000
|
||||
|
||||
export function SiteProvider({ children }: { children: ReactNode }) {
|
||||
const [data, setData] = useState<SiteData | null>(null)
|
||||
// دادهٔ تزریقشدهٔ سرور (window.__SITE__) بهعنوان حالت اولیه — اولین رندر کامل است
|
||||
// و به درخواست /api/site وابسته نیست؛ درخواست همچنان در پسزمینه تازهسازی میکند
|
||||
const embedded = typeof window !== 'undefined' ? window.__SITE__ : undefined
|
||||
const [data, setData] = useState<SiteData | null>(embedded ?? null)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const load = useCallback(() => {
|
||||
@@ -284,11 +295,12 @@ export function SiteProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
if (!cancelled) {
|
||||
if (json.brand && json.hero && json.footer && json.nav) setData(json)
|
||||
else setError(true)
|
||||
// با دادهٔ تزریقشده، پاسخ نامعتبر فقط یعنی «تازهسازی نشد» — خطا نشان نده
|
||||
else if (!embedded) setError(true)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setError(true)
|
||||
if (!cancelled && !embedded) setError(true)
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
|
||||
Reference in New Issue
Block a user