# بهینه‌سازی SEO تک‌تک شهرها در city.json (سطح حرفه‌ای) ## پروژه `nobat724_front` (سایت عمومی نوبت‌دهی، چند-دامنه‌ای — هر شهر یک دامنه) ## نقش تو یک **متخصص حرفه‌ای SEO فارسی/محلی (Local SEO)** هستی. خروجی باید در سطح یک آژانس سئوی حرفه‌ای باشد: یکتا، کلیدواژه‌محور بدون stuffing، با رعایت طول بهینه، CTR-optimized. ## زمینه `data/city.json` منبع واحد SEO هر دامنه‌ی شهری است. `app/layout.js` → `generateMetadata()` مقادیر این فایل را مستقیم به تگ‌های متا تبدیل می‌کند. یعنی کیفیت SEO هر شهر = کیفیت فیلدهای متنی همان رکورد در این فایل. ۳۴ رکورد وجود دارد (۳۳ شهر + رکورد اصلی `id:600` دامنه `nobat724.com`). محتوای فعلی با یک **قالب یکسان کلون** شده — جملات تقریباً تکراری بین شهرها (خطر duplicate content) و ضعیف. ## مشکل / هدف دو مشکل بحرانی: 1. **فیلد `title` در هیچ‌کدام از ۳۴ رکورد وجود ندارد.** `layout.js` اول `matchedCity?.title` را می‌خواند؛ چون نیست، همه شهرها به یک قالب یکسان fallback می‌کنند → تایتل‌های تقریباً تکراری. **باید به هر رکورد فیلد `title` یکتا و بهینه اضافه شود.** 2. `description` / `keywords` / `slogan` / `footer_description` قالبی و تکراری‌اند → باید هرکدام برای هر شهر **بازنویسی یکتا و بهینه** شوند. هدف: هر ۳۴ رکورد، ۵ فیلد متنی SEO در بالاترین کیفیت، **بدون تکرار جمله بین شهرها**. ## فایل‌های مرتبط | فایل | نقش | |------|-----| | `data/city.json` | تنها فایلی که ویرایش می‌شود — رکورد هر شهر | | `app/layout.js` | مصرف‌کننده فیلدها (فقط برای درک، ویرایش نشود مگر task ۶) | ## وضعیت فعلی مصرف فیلدها در `app/layout.js`: ```js const title = matchedCity?.title || // ← این فیلد وجود ندارد! (matchedCity?.name ? `نوبت‌دهی آنلاین پزشکان ${matchedCity.name} | نوبت 724` : "نوبت 724 | سیستم نوبت‌دهی آنلاین پزشکی"); const description = matchedCity?.description || "..."; // keywords: matchedCity.keywords // openGraph.siteName: matchedCity.site_name ``` نمونه رکورد فعلی (تکراری/قالبی): ```json { "id": 113, "name": "اهواز", "site_name": "اهواز نوبت", "province_name": "خوزستان", "description": "اهواز نوبت، سامانه نوبت‌دهی آنلاین در اهواز و استان خوزستان. امکان جستجو و رزرو نوبت در مراکز درمانی. اهواز نوبت بهترین راه دسترسی به پزشکان متخصص است.", "slogan": "سامانه هوشمند نوبت‌دهی پزشکان اهواز", "keywords": "رزرو نوبت اهواز, پزشکان خوزستان, کلینیک‌های اهواز, بیمارستان‌های اهواز, متخصصین پزشکی اهواز", "footer_description": "اهواز نوبت، سامانه جامع نوبت‌دهی پزشکی در استان خوزستان. رزرو نوبت از پزشکان و مراکز درمانی اهواز بدون نیاز به مراجعه حضوری." } ``` ## فهرست ۳۴ رکورد (همه باید پردازش شوند) | id | شهر | استان | دامنه | |----|-----|-------|-------| | 129 | اراک | مرکزی | arak-nobat.ir | | 103 | اردبیل | اردبیل | ardabil-nobat.ir | | 102 | ارومیه | آذربایجان غربی | urmia-nobat.ir | | 104 | اصفهان | اصفهان | esf-nobat.ir | | 113 | اهواز | خوزستان | ahvaz-nobat.ir | | 106 | ایلام | ایلام | ilam-nobat.ir | | 112 | بجنورد | خراسان شمالی | bojnord-nobat.ir | | 130 | بندرعباس | هرمزگان | bandar-nobat.ir | | 107 | بوشهر | بوشهر | bushehr-nobat.ir | | 110 | بیرجند | خراسان جنوبی | birjand-nobat.ir | | 101 | تبریز | آذربایجان شرقی | tabriz-nobat.ir | | 108 | تهران | تهران | tehran-nobat.ir | | 127 | خرم‌آباد | لرستان | lorestan-nobat.ir | | 126 | رشت | گیلان | rasht-nobat.ir | | 116 | زاهدان | سیستان و بلوچستان | zahedan-nobat.ir | | 114 | زنجان | زنجان | zanjan-nobat.ir | | 128 | ساری | مازندران | sari-nobat.ir | | 115 | سمنان | سمنان | semnan-nobat.ir | | 120 | سنندج | کردستان | sanandaj-nobat.ir | | 109 | شهرکرد | چهارمحال و بختیاری | shkord-nobat.ir | | 117 | شیراز | فارس | shiraz-nobat.ir | | 118 | قزوین | قزوین | qazvin-nobat.ir | | 119 | قم | قم | qom-nobat.ir | | 105 | کرج | البرز | karaj-nobat.ir | | 121 | کرمان | کرمان | kerman-nobat.ir | | 122 | کرمانشاه | کرمانشاه | kermanshah-nobat.ir | | 125 | گرگان | گلستان | golestan-nobat.ir | | 111 | مشهد | خراسان رضوی | mashhad-nobat.ir | | 131 | همدان | همدان | hamadan-nobat.ir | | 123 | یاسوج | کهگیلویه و بویراحمد | yasuj-nobat.ir | | 132 | یزد | یزد | yazd-nobat.ir | | 133 | دهدشت | کهگیلویه و بویراحمد | dehdasht-nobat.ir | | 134 | بهبهان | خوزستان | behbahan-nobat.ir | | 600 | نوبت 724 (برند اصلی) | — | nobat724.com | ## وظایف ### ۱. افزودن فیلد `title` یکتا به هر رکورد (مهم‌ترین) بعد از `site_name` در هر رکورد، فیلد `title` اضافه کن. **قواعد `title`:** - طول: **۵۰–۶۰ کاراکتر** (حداکثر ~۶۰ برای عدم truncate در گوگل). - کلیدواژه اصلی **اول** جمله: «نوبت‌دهی آنلاین پزشکان {شهر}» یا واریانت. - برند در انتها با جداکننده `|`: `... | نوبت ۷۲۴`. - **یکتا بین شهرها** — فقط جای‌گذاری اسم شهر کافی نیست؛ ساختار/کلمات را بین گروه‌ها متنوع کن (مثلاً برخی «رزرو نوبت پزشک {شهر}»، برخی «نوبت اینترنتی دکتر {شهر}»). - بدون stuffing، خوانا، طبیعی. مثال طلایی: ```json "title": "نوبت‌دهی آنلاین پزشکان اهواز | رزرو نوبت دکتر | نوبت ۷۲۴" ``` ### ۲. بازنویسی `description` (متا دیسکریپشن) **قواعد:** - طول: **۱۵۰–۱۶۰ کاراکتر** (نه کمتر از ۱۲۰، نه بیشتر از ۱۶۵). - کلیدواژه اصلی front-load شده در ابتدای جمله. - شامل: نام شهر + استان + اشاره به تخصص‌ها/مراکز درمانی + **CTA** («همین حالا رزرو کنید» / «آنلاین نوبت بگیرید»). - **یکتا** — هیچ دو شهری جمله‌ی یکسان نداشته باشند. زاویه‌ی متن را متنوع کن (یکی روی سرعت، یکی روی تنوع پزشکان، یکی روی ۲۴ساعته‌بودن). - بدون تکرار بیش از حد اسم شهر (حداکثر ۲ بار). مثال طلایی: ```json "description": "با نوبت اهواز، آنلاین از پزشکان متخصص و عمومی خوزستان نوبت بگیرید. جستجوی دکتر بر اساس تخصص، بیمه و منطقه در اهواز؛ رزرو نوبت ۲۴ساعته بدون مراجعه حضوری. همین حالا نوبت خود را ثبت کنید." ``` ### ۳. بهینه‌سازی `keywords` - ۶–۹ عبارت هدفمند، جدا با `, `. - ترکیب: `نوبت {شهر}`، `دکتر {شهر}`، `پزشک متخصص {شهر}`، `رزرو نوبت اینترنتی {شهر}`، `کلینیک {شهر}`، `بهترین پزشک {استان}`، عبارات intent-محور («نوبت آنلاین دکتر»). - بدون تکرار عین‌به‌عین عبارات بین شهرها (اسم شهر متفاوت کافی نیست برای long-tailها). ### ۴. بازنویسی `slogan` یکتا - کوتاه (۴–۸ کلمه)، جذاب، دارای کلیدواژه شهر، متمایز بین شهرها. - نمونه: `"در اهواز، نوبت دکتر فقط با چند کلیک"`. ### ۵. بازنویسی `footer_description` یکتا - ۱–۲ جمله، محلی، دارای شهر+استان، متفاوت از `description` (نه کپی). - برای فوتر — لحن معرفی برند محلی. ### ۶. (اختیاری، اگر کاربر خواست) بهبود فنی در `layout.js` اگر زمان بود، این‌ها را هم پیشنهاد بده (اما JSON اولویت است): - `openGraph.images` و `twitter.images` الان هاردکد `nobat724.com/assets/images/logo.png` است — می‌توان per-city کرد. - JSON-LD فعلی فقط `Organization` + `WebSite` با `siteUrl` ثابت `nobat724.com` است؛ افزودن `MedicalBusiness`/`LocalBusiness` per-city با دامنه و نام شهر، سیگنال Local SEO قوی می‌دهد. ### ۷. رکورد برند اصلی (`id: 600`) این رکورد شهر نیست (دامنه‌ی مادر `nobat724.com`, `province_name: null`). برایش `title`/`description`/`keywords` سراسری و برندمحور بنویس (نه شهرمحور): تمرکز روی «سامانه سراسری نوبت‌دهی آنلاین پزشکی ایران». ## نکات مهم - **فقط `data/city.json` ویرایش شود** (به‌جز task ۶ اختیاری). ساختار JSON، ترتیب رکوردها، و فیلدهای `id`, `uuid`, `status`, `weight`, `province_id`, `province_name`, `representation_id`, `contact_phone`, `email`, `domain`, `social_media`, `logo_url` **دست‌نخورده** بمانند. - فیلد `title` جدید را بعد از `site_name` قرار بده (سازگاری با ترتیب منطقی). - **اعداد در `title` فارسی** باشند (`۷۲۴` نه `724`) برای هماهنگی با UI فارسی — مگر اینکه در برند لاتین لازم باشد. - **یکتایی سراسری**: بعد از اتمام، هیچ دو رکوردی نباید `title` یا `description` با ساختار جمله‌ی یکسان (فقط تغییر اسم شهر) داشته باشند. حداقل ۳–۴ الگوی متنی متفاوت بین ۳۴ رکورد بچرخان. - **صحت جغرافیایی**: `province_name` هر رکورد را به‌عنوان واقعیت بپذیر (قبلاً بهبهان=خوزستان، دهدشت=کهگیلویه اصلاح شده). محتوای استان را با همین مقدار بنویس. - بعد از ویرایش، **اعتبار JSON را تأیید کن**: ```bash python3 -c "import json; d=json.load(open('data/city.json')); print('valid', len(d)); \ t=[c.get('title') for c in d]; assert all(t), 'missing title'; \ print('unique titles:', len(set(t))==len(t)); \ print('unique descriptions:', len(set(c['description'] for c in d))==len(d))" ``` باید: valid، همه title دارند، titleها و descriptionها یکتا. - زبان فارسی رسمی و روان، RTL، بدون غلط املایی. اسامی شهر/استان دقیقاً مطابق فایل. - این تغییر داده است، نه endpoint API → نیازی به به‌روزرسانی `docs/api/` نیست. - بعد از ویرایش، یک build سریع بزن که فایل درست پارس می‌شود: `npm run build` (یا حداقل import شدن JSON). ```