SEO لتطبيقات الصفحة الواحدة
كيفية جعل تطبيقات الصفحة الواحدة (React Router وVue Router وAngular Router) قابلة للزحف والفهرسة، ومعالجة مشكلات غلاف التطبيق وsoft-404، والمقارنة بين History API والتوجيه بالأجزاء، وتوفير HTML لكل مسار عبر SSR أو العرض المسبق، وضبط العنوان الأساسي وعنوان الصفحة لكل مسار، وإنشاء خريطة الموقع للمسارات من جانب العميل.
اللغات
يحمّل تطبيق الصفحة الواحدة مستندًا واحدًا ويبدّل العروض باستخدام JavaScript بدل طلب صفحة جديدة من الخادم. تنفذ تطبيقات كثيرة ذلك بغلاف تطبيق يعيد HTML حقيقيًا لعنوان URL واحد وغلافًا شبه فارغ لبقية المسارات إلى أن يعرضها JavaScript، لكن هذا تنفيذ شائع لا قاعدة عامة؛ افحص الاستجابة المباشرة لكل مسار. وعندما يكون الغلاف المجرد هو المشكلة، يتكون الإصلاح من جزأين مستقلين: قابلية العنونة عبر History API بدل أجزاء # أو #!، بحيث يكون لكل عرض عنوان URL حقيقي، وتوفر المحتوى عبر SSR أو العرض المسبق أو إطار وسيط، بحيث يعيد كل عنوان HTML فريدًا عند طلبه. تحقق كذلك من عنوان كل مسار وعنوانه الأساسي وحالة robots عند الدخول المباشر وبعد العرض، وعالج soft 404s؛ فالموجّهات تميل إلى إبقاء حالة 200 للعروض غير الموجودة، لذا أعد التوجيه إلى عنوان يعيد 404s بذاته أو أضف noindex بعد العرض، مع الانتباه إلى أن noindex الأولي قد يدفع Google إلى تخطي العرض. وتأكد من أن كل مسار في خريطة الموقع يعيد HTML حقيقيًا قابلًا للفهرسة؛ فلا يوجد تنسيق خاص لخريطة موقع SPA.
الخلاصة — التطبيق ذو الصفحة الواحدة (SPA) يحمّل صفحة واحدة من الخادم ثم يستخدم JavaScript لتبديل “الصفحات” دون إعادة تحميل كاملة. المشكلة: العديد من تطبيقات SPA ترسل نفس الصفحة الأولية شبه الفارغة بغض النظر عن عنوان URL الذي يطلبه محرك البحث — هذا خطر شائع في كيفية بناء تطبيقات SPA عادةً، وليس ضمانًا لكل تطبيق SPA. لإصلاح ذلك، تأكد من أن كل مسار له عنوان URL حقيقي خاص به وHTML حقيقي خاص به — عادةً عن طريق عرض الصفحات على الخادم أو بنائها مسبقًا.
ما هو التطبيق ذو الصفحة الواحدة
يقوم التطبيق ذو الصفحة الواحدة عادةً بتحديث العروض والمسارات من جانب العميل دون تنقل كامل في المستند. Evidence for this claim Primary standard or official documentation supporting the adjacent article claim. Scope: Protocol semantics and Search behavior are kept separate; no indexing, ranking, or migration-timing guarantee is inferred. Confidence: high · Verified: MDN: Single-page application يمكن لـ Google عرض تطبيقات SPA التي تعمل بـ JavaScript، لكن عناوين URL القابلة للزحف، والروابط، ومعالجة الحالة، والمحتوى المعروض تظل ضرورية. Evidence for this claim Primary standard or official documentation supporting the adjacent article claim. Scope: Protocol semantics and Search behavior are kept separate; no indexing, ranking, or migration-timing guarantee is inferred. Confidence: high · Verified: Google: JavaScript SEO basics
التطبيق ذو الصفحة الواحدة هو موقع ويب مبني كصفحة HTML واحدة. عندما تنقر حولك — من صفحة المنتجات إلى صفحة حول — يقوم JavaScript بتبديل ما يظهر على الشاشة بدلاً من طلب صفحة جديدة كاملة من الخادم. React (مع React Router)، Vue (مع Vue Router)، وAngular تعمل جميعها بهذه الطريقة افتراضيًا. يبدو سريعًا ويشبه التطبيقات، ولهذا فهو شائع.
الخطر هو ما يرسله الخادم. تستخدم تطبيقات SPA كثيرة تنفيذ «غلاف التطبيق»:
في المرة الأولى التي يقوم فيها أي شخص (بما في ذلك Google) بتحميل التطبيق، يعيد الخادم
قشرة فارغة في الغالب، ويتم بناء المحتوى الحقيقي في المتصفح بعد ذلك. هذه
طريقة شائعة لبناء تطبيق SPA — وليست تعريفًا له، وليست شيئًا يفعله كل
تطبيق SPA. ولكن عندما يرسل موقع ما قشرة تطبيق فارغة، يكون نمط الفشل حقيقيًا: إذا
طلب Google من الخادم /about و /products بشكل منفصل، فقد يحصل على
نفس القشرة الفارغة تمامًا لكليهما، لأن لا شيء في المسار غيّر ما أرسله
الخادم. طريقة معرفة ما إذا كان تطبيقك في هذا الموقف هي التحقق مما يعيده طلب مباشر
لكل مسار فعليًا — وليس افتراض ذلك من حقيقة أنه تطبيق SPA.
لماذا يضر ذلك بتحسين محركات البحث
تحتاج محركات البحث إلى رؤية المحتوى الخاص بك لترتيبه. مع تطبيق SPA فارغ، تميل ثلاثة أشياء إلى الخطأ:
- كل عنوان URL يبدو نفسه للخادم. الروابط العميقة، والمشاركات، والزاحفون جميعًا يصلون إلى نفس القشرة.
- صفحات 404 لا تزال تقول “200 OK”. يمكن لموجه JavaScript عرض شاشة “غير موجود” بينما تبلغ الصفحة تقنيًا عن النجاح، لذلك قد يفهرس Google صفحات فارغة.
- عناوين URL الخاطئة. استخدمت تطبيقات SPA الأقدم عناوين مثل
example.com/#/products. لا يمكن لـ Google فهرسة تلك بشكل موثوق.
كيفية إصلاح ذلك (النسخة المختصرة)
- امنح كل عرض عنوان URL حقيقي باستخدام واجهة برمجة تطبيقات History في المتصفح (مسارات نظيفة مثل
/products)، وليس تلك القائمة على#. - أرسل HTML حقيقي لكل عنوان URL. اعرض الصفحات على الخادم (SSR) أو قم ببنائها مسبقًا (prerendering). إذا كنت تبدأ من جديد، فإن إطار عمل يقوم بذلك نيابة عنك — Next.js، Nuxt، SvelteKit، SSR الخاص بـ Angular — يوفر عليك العناء.
- امنح كل صفحة عنوانًا ووصفًا خاصين بها يتغيران عندما يتغير المسار.
- اجعل الروابط روابط حقيقية (
<a href>)، وليس عناصر<div>قابلة للنقر.
هل تريد الآليات — لماذا يفشل التوجيه بالأجزاء، وفخ «التوجيه الأكثر تقييدًا» مع canonical، وكيفية إنشاء خريطة موقع للمسارات من جانب العميل؟ انتقل إلى علامة التبويب متقدم.
الخلاصة — الخطر الأساسي لتحسين محركات البحث في تطبيق SPA ليس “JavaScript” بشكل مجرد — بل هو أن التوجيه من جانب العميل يغير ما يراه المستخدم دون تغيير ما يرسله الخادم. الإصلاح له نصفان مستقلان يخلطهما الناس باستمرار: قابلية العنونة (واجهة برمجة تطبيقات History، عناوين URL فريدة لكل مسار، بدون أجزاء
#!) وتوفر المحتوى (SSR، prerendering/SSG، أو إطار عمل وسيط). افعل الأول فقط وستحصل على خريطة موقع مرتبة لعناوين URL تعرض جميعها نفس القشرة. بالإضافة إلى كليهما، يحتاج كل مسار إلى canonical/title/description خاص به في DOM المعروض، وعليك التعامل مع أخطاء soft 404s التي تحتفظ برمز200، ويجب أن يحل كل مسار في خريطة الموقع بشكل مستقل إلى HTML حقيقي.
ما هو تحسين محركات البحث لتطبيقات SPA فعليًا
SPA بنية تطبيق، وليست حالة فشل حتمية في SEO. Evidence for this claim Primary standard or official documentation supporting the adjacent article claim. Scope: Protocol semantics and Search behavior are kept separate; no indexing, ranking, or migration-timing guarantee is inferred. Confidence: high · Verified: MDN: Single-page application يمكن للعرض من الخادم أو العرض المسبق أو العرض المنفذ بعناية من جانب العميل أن يكشف المحتوى، لكن لا يضمن أي منها الفهرسة. Evidence for this claim Primary standard or official documentation supporting the adjacent article claim. Scope: Protocol semantics and Search behavior are kept separate; no indexing, ranking, or migration-timing guarantee is inferred. Confidence: high · Verified: Google: JavaScript SEO basics
هذا هو الغوص العميق في التطبيقات ذات الصفحة الواحدة تحديدًا — التوجيه من جانب العميل مع React Router أو Vue Router أو Angular Router، حيث بعد التحميل الأول لا يرسل الخادم صفحة كاملة أخرى أبدًا. يقع هذا بجانب دليل JavaScript SEO الأوسع (الذي يغطي التكافؤ، والتحميل الكسول، والتمرير اللانهائي، وعرض JavaScript عمومًا) والكتابات الخاصة بكل إطار عمل لـ React و Next.js و Nuxt و Angular و Vue و Svelte و Astro. هنا أنا مهتم فقط بطبقة التوجيه وما تفعله بالزحف والفهرسة.
لماذا تفشل تطبيقات SPA ذات التوجيه من جانب العميل في تحسين محركات البحث
عنوان URL واحد، استجابة HTML واحدة — مشكلة غلاف التطبيق
تصف Google وضع الفشل بدقة: “Some JavaScript sites may use the app shell model where the initial HTML does not contain the actual content and Google needs to execute JavaScript before being able to see the actual page content.” (ترجمة) «قد تستخدم بعض مواقع JavaScript نموذج غلاف التطبيق، حيث لا يحتوي HTML الأولي على المحتوى الفعلي، وتحتاج Google إلى تنفيذ JavaScript قبل أن تتمكن من رؤيته». في تطبيق SPA العاري، يكون «غلاف التطبيق» هو كل ما يرسله الخادم. اجلب /products مباشرةً وستحصل على المستند شبه الفارغ نفسه الذي يعيده /about. لا يختلف المحتوى إلا بعد أن يشغّل المتصفح JavaScript ويقرر الموجّه ما يعرضه.
هذه هي المشكلة كلها في جملة واحدة: التوجيه من جانب العميل يغير ما يراه المستخدم دون تغيير ما سيعيده الخادم. كل ما يتبع ذلك — أخطاء soft 404s، والمحتوى المكرر، والعناوين المفقودة — هو عرض من أعراض ذلك.
الخادم لا يرى أبدًا أي “صفحة” تم طلبها (لماذا يفشل توجيه التجزئة)
تطبيقات SPA الأقدم تحمّل العروض من أجزاء URL — example.com/#/products. كلمات Google الدقيقة: “قد يستخدم تطبيق SPA أجزاء URL (على سبيل المثال https://example.com/#/products) لتحميل عروض مختلفة.” يفشل هذا لسبب ميكانيكي محدد: المتصفحات لا ترسل أبدًا الجزء (أي شيء بعد #) إلى الخادم في طلب HTTP. لا يمكن للخادم حرفيًا معرفة أي “صفحة” تم طلبها، لذا لا يمكنه إرجاع محتوى مختلف أو رمز حالة مختلف لها. الزاحف الذي يجلب عنوان URL مرة واحدة يحصل على HTML متطابق بغض النظر عن الجزء.
لهذا السبب أيضًا أوقفت Google رسميًا مخطط الزحف AJAX لعام 2009 في أكتوبر 2015: “باختصار: لم نعد نوصي باقتراح الزحف AJAX الذي قدمناه في عام 2009.” كان الحل القديم _escaped_fragment_ يسمح للخوادم بعرض مسارات التجزئة مسبقًا عند الطلب — لكنه كان يرقع حول مشكلة التوجيه بدلاً من إصلاحها. توصية Google الخاصة التي تحل محله هي History API، المغطاة أدناه.
أخطاء soft 404s — أجهزة التوجيه من جانب العميل تحتفظ بـ 200 لكل شيء
يكاد ذلك يكون حتميًا في تطبيق SPA ذي الغلاف المجرد. تحتفظ الموجّهات من جانب العميل، بحكم تصميمها، بحالة 200 للصفحة الأصلية في كل تنقل افتراضي، بما في ذلك حالات «غير موجود». تشير Google إلى ذلك صراحة: “In a single-page application (SPA), this can be especially difficult. To prevent error pages from being indexed, you can use one or both of the following strategies.” (ترجمة) «قد يكون ذلك صعبًا بصورة خاصة في تطبيق الصفحة الواحدة. ولمنع فهرسة صفحات الخطأ، يمكنك استخدام إحدى الاستراتيجيتين التاليتين أو كلتيهما». وتشرح السبب: “When a SPA is using client-side JavaScript to handle errors they often report a 200 HTTP status code instead of the appropriate status code.” (ترجمة) «عندما يعالج تطبيق SPA الأخطاء عبر JavaScript من جانب العميل، فإنه غالبًا ما يبلغ عن حالة HTTP 200 بدل الحالة المناسبة».
النتيجة هي فهرسة العروض الفارغة أو عروض الأخطاء كصفحات 200 رقيقة. توثق Google استراتيجيتين بالضبط هنا، بدقة: إعادة التوجيه (أو إجراء طلب كامل) إلى عنوان URL الذي يعيد خادمه 404/رمز خطأ حقيقي، أو إضافة وسم noindex إلى عرض الخطأ باستخدام JavaScript. انتبه للثانية: إذا كان noindex موجودًا من أول رسم بدلاً من إضافته بعد أن يقرر التطبيق أن المسار غير صالح، فقد يتسبب في تخطي Google لعرض الصفحة تمامًا — لذا تحقق مما يعيده الطلب المباشر قبل تشغيل أي JavaScript، وليس فقط ما يظهر في DOM المعروض بعد ذلك. Evidence for this claim For a client-rendered not-found view, Google documents two approaches: redirect to a URL whose server returns a 404 response, or add noindex with JavaScript; an initial noindex can cause rendering to be skipped, so raw and rendered directives require separate testing. Scope: SPAs Confidence: high · Verified: Fix Search-related JavaScript problems
تكرارات القشرة المشتركة — سبب محتمل، وليس تشخيصًا تلقائيًا
هناك نمط فشل ثانٍ أكثر خفاءً: يتم فهرسة مسارات مختلفة كتكرارات لبعضها البعض لأنها تُعرض لتنتج نفس القالب المشترك للترويسة/التنقل/التذييل. وصف غاري إليس طريقة واحدة يحدث بها هذا: “لدي مجموعة من رسائل البريد الإلكتروني في صندوق الوارد الخاص بي حيث تكون المشكلة أن العنصر الرئيسي استغرق وقتًا طويلاً للتحميل، لذا انتهت مهلة العرض (التفسير الأكثر ترجيحًا لدي) وبقينا مع مجموعة من الصفحات التي تحتوي فقط على القالب. مع القالب فقط، تكون تلك الصفحات مكررة.” حله: “حاول إعادة هيكلة استدعاءات الجافاسكريبت بحيث يتم تحميل المحتوى (بما في ذلك القالب الهامشي) أولاً.” (تم نقله عبر منشور على لينكد إن، والذي يقاوم التحقق الآلي؛ تعامل معه كثانوي عالي الثقة.)
لاحظ أن إليس يصوغ ذلك بوصفه «التفسير الأرجح لدي»، لا تشخيصًا مؤكدًا، وينبغي أخذ هذا القيد بجدية. لا يمكن تشخيص «انتهاء مهلة العرض» من الأعراض وحدها؛ بل تحتاج إلى دليل من الطلب المباشر، ودليل من الناتج المعروض، وإشارات من Search Console قبل أن تنسب تكرار محتوى مسار بعينه إلى مهلة، بدل خلل أو مورد محظور أو غلاف متطابق فعلًا. ولا توجد مهلة منشورة ثابتة تبني التصميم عليها؛ إذ تقول وثيقة Google الأساسية إن الصفحة “may stay on this queue for a few seconds, but it can take longer than that,” (ترجمة) «قد تبقى في قائمة الانتظار هذه بضع ثوانٍ، لكنها قد تستغرق وقتًا أطول»، من دون تحديد رقم. لذلك لا تخطط وفق مدة مفترضة لقائمة انتظار العرض؛ اجعل المحتوى الأساسي يُحمّل مبكرًا قدر الإمكان مهما استغرق العرض.
الإصلاح: احصل على HTML حقيقي لكل مسار
الإصلاح الكامل له جزءان مستقلان يخلطهما الناس باستمرار:
- قابلية عنونة URL — واجهة History API، عناوين URL فريدة لكل مسار، بدون أجزاء.
- توفر المحتوى — SSR، أو التقديم المسبق الثابت/SSG، أو إطار عمل وسيط.
افعل فقط #1 وستحصل على عناوين URL نظيفة وقابلة للمشاركة ولكنها جميعًا تعيد نفس القشرة الفارغة. للمسار الذي تريد فعلاً فهرسته، تحتاج إلى كليهما.
هذا ليس تفويضًا عالميًا لإضافة SSR في كل مكان، رغم ذلك. SSR والتقديم المسبق يقللان من اعتمادك على نجاح الزاحف في تنفيذ جافاسكريبت الخاص بك — لا يصبحان إلزاميين بمجرد أن يكون الموقع تقنيًا SPA. قبل اختيار بنية لمسار معين، تحقق من ثلاثة أشياء: ما إذا كان هذا المسار يحتاج إلى الترتيب على الإطلاق (لوحة تحكم داخلية لا تحتاج)، وما الذي يعيده طلب مباشر بدون جافاسكريبت إليه بالفعل (بعض الإعدادات ترسل HTML ذا معنى بالفعل)، وأي الزواحف تحتاج فعلاً إلى إرضائها (جوجل يعرض الجافاسكريبت بشكل موثوق إلى حد ما؛ بينج ومعظم زواحف الذكاء الاصطناعي أقل موثوقية — انظر أدناه). CSR الذي يجتاز هذه الفحوصات بالفعل لا يحتاج إلى أن يصبح SSR فقط لأن الموقع SPA.
العرض من جانب الخادم (SSR)
يقوم الخادم بتشغيل تطبيقك لكل طلب ويعيد HTML مكتمل التكوين لهذا المسار، ثم يقوم العميل “بترطيبه” إلى SPA حي. هذا هو الخيار الأكثر قوة لأن الزاحف يحصل على محتوى كامل في الجلب الأول، دون الحاجة إلى تنفيذ جافاسكريبت.
العرض المسبق الثابت / SSG
بدلاً من العرض لكل طلب، تقوم ببناء HTML لكل مسار مسبقًا عند النشر. مثالي للمحتوى الذي لا يتغير حسب المستخدم. من كتاباتي الخاصة حول SEO للجافاسكريبت: أي نوع من SSR، أو التقديم الثابت، أو إعداد التقديم المسبق سيكون جيدًا لمحركات البحث — الشيء الذي يجب تجنبه هو ترك المحتوى محبوسًا خلف العرض من جانب العميل فقط.
أو: لا تبنِه يدويًا — استخدم إطار عمل وسيطًا
بالنسبة للمشاريع الجديدة، التوصية الصادقة هي عدم بناء توجيه من جانب العميل يدويًا باستخدام React-Router فقط أو Vue-Router فقط على الإطلاق. إطار عمل فوقي — Next.js، Nuxt، Angular مع حزمة SSR الخاصة به، SvelteKit، Remix — يمنحك SSR/SSG وبيانات وصفية قائمة على المسار بشكل جاهز، مما يتجاوز هذه الفئة الكاملة من المشاكل. (كل واحد من هذه الإطارات لديه دليل متعمق خاص به على هذا الموقع.) كن صريحًا بشأن الجانب الآخر: إضافة SSR إلى تطبيق SPA مبني يدويًا هو عمل هندسي حقيقي — مشروع ترحيل، وليس مجرد تبديل إعدادات.
العرض الديناميكي كحل مؤقت، وليس كوجهة نهائية
يمكنك تقديم لقطة HTML معروضة بشكل منفصل لبرامج الزحف (العرض الديناميكي). كل من Google وBing يقبلانها — Bing “يوصي بالعرض الديناميكي كبديل رائع للمواقع التي تعتمد بشكل كبير على JavaScript” (منقول من مدونة Bing لعام 2018؛ الاقتباسان القصيران التاليان حول قدرات bingbot تم التحقق منهما مباشرة، هذا الاقتباس الأطول لم يتم إعادة التحقق منه بشكل مستقل) — لكن Google واضحة أنها حل مؤقت: “كان العرض الديناميكي حلاً مؤقتًا وليس حلاً طويل الأمد… بدلاً من ذلك، نوصي باستخدام العرض من جانب الخادم، أو العرض الثابت، أو الترويب كحل.” (منقول من وثيقة Google حول العرض الديناميكي؛ الصياغة تطابق ما تم الاستشهاد به بالفعل في دليل JavaScript SEO الأوسع لهذا الموقع.) إنه جسر، وليس بنية أساسية.
History API مقابل توجيه الهاش (#!)
خمس حالات، وليس حالتين
اختبار مسار في تطبيق SPA يصبح مربكًا لأن “هل يعمل” يشمل في الواقع خمس حالات مختلفة وقابلة للتحقق بشكل منفصل:
| الحالة | ما هي |
|---|---|
| استجابة الخادم | البايتات التي يحصل عليها طلب HTTP جديد بدون JavaScript إلى عنوان URL. |
| DOM المعروض | ما يبنيه المتصفح (أو مُصيِّر Googlebot) بعد تنفيذ JavaScript مقابل استجابة الخادم تلك. |
| معالجة البحث | كيف يزحف Google بشكل منفصل إلى استجابة الخادم، ثم يعرض الصفحة لاحقًا، ويفهرس بناءً على كلاهما. |
| تنقل المتصفح الكامل للمستند | طلب HTTP جديد فعلي إلى عنوان URL — الحالة الوحيدة التي يمكنها تغيير استجابة الخادم أو رمز الحالة. |
| التنقل الناعم في المتصفح | انتقال عبر History API (pushState/replaceState) يغير عنوان URL المرئي، وسجل المتصفح، وواجهة المستخدم على الشاشة. |
الحالة التي يخلط الجميع بينها: التنقل الناعم يغير عنوان URL وواجهة المستخدم، لكنه لا ينشئ بمفرده استجابة HTTP جديدة أو رمز حالة — يحدث ذلك فقط عند التنقل الكامل (أو طلب مباشر مكافئ، مثل curl). اختبار مسار بالنقر عبر التطبيق من الصفحة الرئيسية يمارس التنقل الناعم؛ اختباره بطلب عنوان URL مباشرة يمارس استجابة الخادم. كلاهما مهم، وقد يختلفان.
ما يمنحك إياه History API
توصية Google لا لبس فيها: “نوصي باستخدام History API لتحميل محتوى مختلف بناءً على عنوان URL في تطبيق SPA.” (في الوثيقة الحية “History API” هو رابط، لذا يستهدف هذا الرابط العميق الجملة التمهيدية.) يتيح لك History API (pushState/replaceState) تغيير عنوان URL المرئي إلى مسار حقيقي قابل للوضع في الإشارات المرجعية — /products، وليس /#/products — دون إعادة تحميل كاملة. هذا يصلح نصف قابلية العنونة: كل عرض الآن لديه عنوان URL يمكن للخادم أن يستجيب له بشكل مختلف.
لماذا تكون عناوين URL ذات الجزء/الهاش غير مرئية للخادم — ولـ Google
لأن الجزء لا يصل أبدًا إلى الخادم (انظر أعلاه)، فإن History API هو الطريقة الوحيدة لإعطاء كل مسار عنوان URL يمكن للخادم تقديمه فعليًا. تضع Google حدًا أدنى لذلك في وثيقتها الأساسية: “لا تستخدم الأجزاء لتحميل محتوى صفحة مختلف. المثال التالي ممارسة سيئة، لأن Googlebot لا يمكنه حل عناوين URL بشكل موثوق.” هذه هي نفس الإرشادات التي كتبتها في مكان آخر — استخدم عناوين URL عادية المظهر مثل /products، وليس عناوين الهاش مثل /#/products، لأن Google لا يمكنه فهرسة عناوين الهاش بشكل موثوق.
إهمال عام 2015، باختصار
انتهى عصر الهاش-بانغ (#!) مع منشور جوجل عام 2015: “لقد تغيرت الأوقات. اليوم، طالما أنك لا تمنع Googlebot من الزحف إلى ملفات JavaScript أو CSS الخاصة بك، فإننا قادرون عمومًا على عرض وفهم صفحات الويب الخاصة بك مثل المتصفحات الحديثة،” و*“يمكنك استخدام History API pushState() لضمان إمكانية الوصول لمجموعة أوسع من المتصفحات (وأنظمتنا).”* هناك فارق دقيق يستحق الاحتفاظ به: لم تقم جوجل بإلغاء فهرسة المواقع القديمة ذات الهاش-بانغ فورًا — “سنقوم عمومًا بالزحف إلى عناوين #! وعرضها وفهرستها” — لكن “لا يزال بإمكاننا المحاولة” ليس “يجب عليك الاستمرار في هذا.”
جعل كل مسار قابلًا للفهرسة بشكل مستقل
العناوين النظيفة وHTML الحقيقي تجعلك تُزحف إليك. للحصول على فهرسة صحيحة، يحتاج كل مسار إلى إشاراته الخاصة.
العنوان الأساسي لكل مسار — وفخ «التوجيه الأكثر تقييدًا»
يحتاج كل مسار إلى rel=canonical خاص به في DOM المُقدَّم. الفخ: إذا تم تضمين canonical مؤقت (أو noindex) في قشرة HTML الخام وكان من المفترض أن تستبدله JavaScript لاحقًا، فقد تحصل على تعارض. تحل جوجل التعارضات بين النسخة الخام والمُقدَّمة من خلال اتخاذ الإشارة الأكثر تقييدًا — كما قلت سابقًا، ستختار جوجل البيانات الأكثر تقييدًا بين HTML والنسخة المُقدَّمة من الصفحة. يمكن أن يؤدي noindex عشوائي أو canonical خاطئ مدمج في القشرة إلى كتم المسار بالكامل بصمت حتى بعد أن “تصلحه” JavaScript.
العناوين والأوصاف التعريفية لكل مسار عبر JavaScript
يجوز تعيين هذه العناصر عبر JavaScript؛ تقول Google مباشرة: “You can use JavaScript to set or change the meta description as well as the <title> element.” (ترجمة) «يمكنك استخدام JavaScript لتعيين وصف meta أو تغييره، وكذلك عنصر <title>». الشرط أن تستقر القيم في DOM المعروض الذي تقيّمه Google، لا أن تظهر لحظةً ثم تختفي. امنح كل صفحة عنوانًا ووصفًا يتغيران مع المسار.
روابط <a href> حقيقية بين المسارات
اجعل الروابط بين المسارات عناصر ارتباط حقيقية بسمات href، لا معالجات نقر على <div>s. تكتشف Google عناوين URL باستخراج hrefs؛ أما <div onClick> الذي يتنقل عبر الموجّه فلا يوفر رابطًا يستخرجه الزاحف. ولا تعتمد على حالة العميل لنقل المحتوى بين عمليات التحميل؛ إذ تقول Google: “WRS does not retain state across page loads: Local Storage and Session Storage data are cleared across page loads. HTTP Cookies are cleared across page loads.” (ترجمة) «لا يحتفظ WRS بالحالة بين عمليات تحميل الصفحات؛ فتُمسح بيانات Local Storage وSession Storage وملفات HTTP Cookies بينها».
إنشاء خريطة موقع لمسارات SPA
لا يوجد تنسيق خاص “خريطة موقع SPA” — إنها مشكلة محاسبية
بروتوكول خريطة الموقع لم يتغير لـ SPAs. العمل الفعلي هو التأكد من أن كل مسار تدرجه بشكل مستقل يتحول إلى HTML حقيقي وفريد ومُقدَّم. خريطة موقع تضم 500 مسار من جانب العميل لا قيمة لها إذا كانت تلك المسارات جميعها تُرجع نفس القشرة. لذا فإن خريطة الموقع تأتي بعد استراتيجية العرض الخاصة بك، وليست بديلاً عنها.
التعامل مع المسارات الديناميكية/المعلمة (/product/:id)
بالنسبة للتطبيقات ذات المسارات المعلمة، لا يمكنك صيانة القائمة يدويًا. يجب أن يعمل مولّد خريطة الموقع على نفس مصدر البيانات الذي يستخدمه التطبيق — سكربت بناء أو نقطة نهاية خادم تُعدّ كل id — بحيث لا تتباعد خريطة الموقع والتطبيق أبدًا. تلك العناوين المُعدَّدة تستحق الإدراج فقط بعد أن تكون SSR’d أو مُسبقة العرض.
إبقاء خريطة الموقع متزامنة مع ما يمكن حله عبر الخادم
أعد توليد خريطة الموقع كجزء من بنائك أو وفقًا لجدول زمني مرتبط بمصدر المحتوى. المسار الذي يُرجع 404s (أو الأسوأ، soft 404s عند 200) لكنه موجود في خريطة موقعك يرسل إشارة جودة سيئة ويهدر ميزانية الزحف.
اختبار ما تراه جوجل فعليًا
لا تفحص عنوان URL واحدًا فقط. بيت القصيد من مشكلة SPA هو أن المسارات يمكن أن تختلف في المتصفح ولكن ليس على الشبكة، لذا اختبر مسارات متعددة على مستوى HTML الخام:
- جلب HTML الخام لكل مسار باستخدام
curl(مع وكيل مستخدم Googlebot حيثما كان ذلك مناسبًا) وتأكد من أن المحتوى فريد لكل URL، وليس الهيكل المشترك. - فحص URL في Search Console — قارن HTML المُزحف/المُعرض لعدة مسارات، وتأكد من وجود العنوان والوصف والكنسي الخاص بكل مسار.
- تأكد من أن مسارات الأخطاء تُرجع الإشارة الصحيحة — يجب أن يعيد عرض “غير موجود” إما توجيهًا إلى حالة خطأ حقيقية أو يحمل
noindexفي DOM المعروض.
خرافات شائعة حول SEO لتطبيقات الصفحة الواحدة
- “لا يمكن لجوجل فهرسة تطبيقات الصفحة الواحدة إطلاقًا.” قديم. تشغل جوجل متصفح Chromium دائم التحديث وتقوم عمومًا بعرض محتوى تطبيقات الصفحة الواحدة. المخاطر الحقيقية محددة: أخطاء soft 404s، توجيه الهاش، مهلات العرض، وبرامج الزحف غير التابعة لجوجل (Bing بشكل أقل موثوقية، ومعظم برامج الزحف الذكاء الاصطناعي لا تفعل ذلك إطلاقًا).
- “إضافة History API يصلح SEO لتطبيقات الصفحة الواحدة.” يصلح قابلية العنونة فقط. إذا كان الخادم لا يزال يُرجع نفس الهيكل لكل مسار، فلا يزال على جوجل تنفيذ JavaScript لرؤية أي شيء.
- “توجيه الهاش لا يزال يعمل كحل بديل.” مهمل منذ 2015 ويُنصح بعدم استخدامه منذ ذلك الحين.
- “العرض من جانب العميل هو عقوبة ترتيب.” لا توجد عقوبة مباشرة للعرض من جانب العميل. الضرر غير مباشر — العرض الفاشل/المتأخر، وأخطاء soft 404s، وتجميع النسخ المكررة الناتج عن مهلات العرض يقلل مما يُفهرس.
- “العرض المسبق للروبوتات هو تمويه.” ليس كذلك عندما يتطابق المحتوى مع ما يراه المستخدمون في النهاية — فقط متى/أين يختلف العرض، وليس المحتوى.
- “تحتاج إلى مولد خريطة موقع خاص لتطبيقات الصفحة الواحدة.” لا يوجد تنسيق خاص؛ العمل هو جعل كل مسار مدرج يتحول إلى HTML حقيقي.
الأسئلة الشائعة
هل يمكن لجوجل فهرسة تطبيق صفحة واحدة؟ نعم، إذا كان كل مسار يتحول إلى HTML حقيقي وفريد (عبر SSR/العرض المسبق) على URL حقيقي. غالبًا لا تفعل تطبيقات الصفحة الواحدة التي تعتمد على العميل فقط ذلك.
هل أحتاج إلى SSR، أم أن العرض من جانب العميل مقبول أحيانًا؟ يمكن أن يعمل CSR للمحتوى الذي لا يحتاج إلى الترتيب، ولكن لأي شيء تريد فهرسته بشكل موثوق، قم بالعرض المسبق أو SSR — لا تراهن على أن المُعرض سينفذ JavaScript في الوقت المناسب.
هل توجيه الهاش (#!) سيء لـ SEO؟ نعم — الجزء لا يصل أبدًا إلى الخادم، لذا لا يمكن لجوجل حل هذه العناوين بشكل موثوق. استخدم History API.
هل يحتاج كل مسار إلى وسم كنسي خاص به؟ نعم، في DOM المعروض — وتأكد من عدم وجود شيء أكثر تقييدًا (noindex عشوائي أو كنسي خاطئ) في الهيكل الخام.
هل خدمة العرض المسبق تمويه؟ لا، بشرط أن يتطابق المحتوى المُقدم للروبوتات مع ما يراه المستخدمون.
هل يجب أن أستخدم إطار عمل فوقي بدلاً من بناء التوجيه بنفسي؟ للبناء الجديد، عادةً نعم — Next.js/Nuxt/Angular-SSR/SvelteKit تمنحك SSR/SSG وبيانات وصفية لكل مسار مجانًا.
لماذا يُرجع تطبيق الصفحة الواحدة الخاص بي 200 لصفحات غير موجودة؟ لأن الموجّه من جانب العميل يحتفظ بحالة 200 الأصلية للتنقلات الافتراضية. أصلح ذلك بإعادة توجيه JavaScript إلى حالة خطأ حقيقية أو noindex معروض.
ملخص الذكاء الاصطناعي
نظرة مختصرة على النسخة المتقدمة:
- SEO لتطبيقات الصفحة الواحدة (SPA) = التوجيه من جانب العميل تحديدًا (React Router، Vue Router، Angular Router). يتم تعريف تطبيق الصفحة الواحدة (SPA) بتحميل مستند واحد وتبديل العروض باستخدام JavaScript — “هيكل التطبيق” الذي يرسل HTML شبه فارغ لكل عنوان URL هو تنفيذ شائع، وليس قاعدة يتبعها كل تطبيق SPA؛ تحقق مما يعيده الطلب المباشر فعليًا بدلاً من الافتراض.
- الخطر الأساسي: يمكن للتوجيه من جانب العميل تغيير ما يراه المستخدم دون تغيير ما سيعيده الخادم. على تطبيق SPA بهيكل عارٍ، يمكن أن يؤدي جلب
/productsو/aboutمباشرة إلى الحصول على HTML خام متطابق بايتًا ببايت. - يتم الخلط بين خمس حالات: استجابة الخادم، DOM المعروض، معالجة البحث، التنقل الكامل للمستند في المتصفح، والتنقل الناعم في المتصفح. انتقال History API يغير عنوان URL وواجهة المستخدم لكنه لا ينشئ بحد ذاته استجابة خادم جديدة أو حالة.
- ثلاثة أعراض للغلاف المجرد: مشكلة غلاف التطبيق، وأخطاء soft 404s (تحافظ الموجّهات على
200لعرض «غير موجود»؛ والإصلاحان اللذان توثقهما Google هما إعادة التوجيه إلى عنوان URL يعيد 404s بذاته، أو إضافةnoindexعبر JavaScript، مع احتمال أن يؤديnoindexالأولي إلى تخطي العرض)، والنسخ ذات الغلاف المشترك التي يعد «انتهاء مهلة العرض» سببًا محتملًا لها، لا تشخيصًا تلقائيًا بلا دليل من الطلب والعرض والبحث. - يفشل توجيه التجزئة ميكانيكيًا: الجزء بعد
#لا يصل أبدًا إلى الخادم، لذا لا يمكن للخادم أن يختلف حسب المسار. تم إهماله بواسطة Google في 2015؛ يعطي History API قابلية العنونة، وليس إصلاحًا كاملاً لقابلية الفهرسة. - الإصلاح له نصفان مستقلان عندما تكون المشكلة هي الهيكل العاري: قابلية العنونة (History API، عناوين URL فريدة لكل مسار) و توفر المحتوى (SSR، أو العرض المسبق/SSG، أو إطار عمل وسيط) — لكن SSR ليس متطلبًا عالميًا؛ استند في القرار إلى ما إذا كان المسار بحاجة إلى الترتيب، وما يعيده مباشرة بالفعل، وأي زاحف يحتاج إلى عرضه.
- لكل مسار: امتلاك canonical/title/description خاص في DOM المعروض؛ انتبه لفخ “التوجيه الأكثر تقييدًا” حيث يتجاوز
noindex/canonical من HTML الخام JavaScript الخاص بك. روابط<a href>حقيقية؛ لا تعتمد على حالة العميل (يمسح WRS التخزين/ملفات تعريف الارتباط عبر التحميلات). - خرائط المواقع: إدراج مسار لا يثبت أنه يعمل — لا يوجد تنسيق خاص؛ يجب أن يحل كل مسار مدرج بشكل مستقل إلى HTML حقيقي، والمسارات الديناميكية تحتاج إلى مولّد مرتبط بمصدر بيانات التطبيق.
- العرض الديناميكي هو حل مؤقت مقبول من Google/Bing، وليس بنية طويلة الأجل.
- اختبر مسارات متعددة على مستوى HTML الخام (طلبات مباشرة)، وليس فقط بالتنقل داخل التطبيق.
الوثائق الرسمية
وثائق من المصدر الأساسي من محركات البحث.
- فهم أساسيات JavaScript SEO — نموذج هيكل التطبيق، “استخدم History API بدلاً من الأجزاء،” وتعيين العناوين والأوصاف عبر JavaScript.
- إصلاح مشكلات JavaScript المتعلقة بالبحث — قسم أخطاء soft-404 في SPA، “لا تستخدم أجزاء URL لتحميل محتوى مختلف،” وتوصية History API.
- العرض الديناميكي كحل بديل — لماذا العرض الديناميكي حل مؤقت، وليس حلاً طويل الأجل.
- إهمال مخطط الزحف AJAX الخاص بنا (2015) — النهاية الرسمية لزحف hash-bang وتوصية pushState.
- إنشاء خريطة موقع وإرسالها — بروتوكول خريطة الموقع العام (لا يوجد تنسيق خاص بـ SPA).
Bing / Microsoft
- سلسلة bingbot: JavaScript، العرض الديناميكي، والإخفاء. يا إلهي! — قدرات bingbot في عرض JavaScript وموقفه من العرض الديناميكي.
اقتباسات من المصدر
تصريحات رسمية من Google وBing. كل رابط هو رابط عميق يقفز إلى المقطع المقتبس في صفحة المصدر.
Google — مشكلة غلاف التطبيق
- “Some JavaScript sites may use the app shell model where the initial HTML does not contain the actual content and Google needs to execute JavaScript before being able to see the actual page content.” (ترجمة) «قد تستخدم بعض مواقع JavaScript نموذج غلاف التطبيق حيث لا يحتوي HTML الأولي على المحتوى الفعلي وتحتاج Google إلى تنفيذ JavaScript قبل أن تتمكن من رؤية محتوى الصفحة الفعلي.» الانتقال إلى الاقتباس
Google — توجيه التجزئة/الأجزاء وواجهة History API
- “A SPA may use URL fragments (for example https://example.com/#/products) for loading different views.” (ترجمة) «قد يستخدم تطبيق الصفحة الواحدة (SPA) أجزاء URL (على سبيل المثال https://example.com/#/products) لتحميل طرق عرض مختلفة.» الانتقال إلى الاقتباس
- “We recommend using the History API to load different content based on the URL in a SPA.” (ترجمة) «نوصي باستخدام واجهة History API لتحميل محتوى مختلف بناءً على URL في تطبيق الصفحة الواحدة (SPA).» الانتقال إلى الاقتباس (في المستند المباشر “History API” هو رابط، لذلك يستهدف هذا الرابط العميق الجملة التمهيدية؛ الجملة الكاملة موجودة حرفيًا بالترتيب.)
- “don’t use fragments to load different page content. The following example is a bad practice, because Googlebot can’t reliably resolve the URLs.” (ترجمة) «لا تستخدم الأجزاء لتحميل محتوى صفحة مختلف. المثال التالي ممارسة سيئة، لأن Googlebot لا يمكنه حل عناوين URL بشكل موثوق.» الانتقال إلى الاقتباس
Google — أخطاء soft 404s في تطبيقات الصفحة الواحدة (SPA)
- “In a single-page application (SPA), this can be especially difficult. To prevent error pages from being indexed, you can use one or both of the following strategies.” (ترجمة) «في تطبيق الصفحة الواحدة (SPA)، قد يكون هذا صعبًا بشكل خاص. لمنع فهرسة صفحات الخطأ، يمكنك استخدام واحدة أو كلتيهما من الاستراتيجيات التالية.» الانتقال إلى الاقتباس
- “When a SPA is using client-side JavaScript to handle errors they often report a
200HTTP status code instead of the appropriate status code.” (ترجمة) «عندما يستخدم تطبيق الصفحة الواحدة (SPA) JavaScript من جانب العميل للتعامل مع الأخطاء، فإنها غالبًا ما تُبلغ عن رمز حالة HTTP200بدلاً من رمز الحالة المناسب.» الانتقال إلى الاقتباس
Google — وسوم meta عبر JavaScript، والعرض بدون حالة
- “You can use JavaScript to set or change the meta description as well as the
<title>element.” (ترجمة) «يمكنك استخدام JavaScript لتعيين أو تغيير وصف meta بالإضافة إلى عنصر<title>.» الانتقال إلى الاقتباس - “WRS does not retain state across page loads: Local Storage and Session Storage data are cleared across page loads. HTTP Cookies are cleared across page loads.” (ترجمة) «لا يحتفظ WRS بالحالة عبر تحميلات الصفحة: يتم مسح بيانات التخزين المحلي وتخزين الجلسة عبر تحميلات الصفحة. يتم مسح ملفات تعريف الارتباط HTTP عبر تحميلات الصفحة.» الانتقال إلى الاقتباس
Google — إهمال الزحف عبر علامة الهاش-بانغ (2015)
- “In short: We are no longer recommending the AJAX crawling proposal we made back in 2009.” (ترجمة) «باختصار: لم نعد نوصي بمقترح الزحف عبر AJAX الذي قدمناه في عام 2009.» الانتقال إلى الاقتباس
- “Times have changed. Today, as long as you’re not blocking Googlebot from crawling your JavaScript or CSS files, we are generally able to render and understand your web pages like modern browsers.” (ترجمة) «لقد تغيرت الأوقات. اليوم، طالما أنك لا تمنع Googlebot من الزحف إلى ملفات JavaScript أو CSS الخاصة بك، فإننا قادرون عمومًا على عرض وفهم صفحات الويب الخاصة بك مثل المتصفحات الحديثة.» الانتقال إلى الاقتباس
Google — العرض الديناميكي هو حل مؤقت (منقول؛ الصياغة مطابقة لما هو مستشهد به بالفعل في دليل JavaScript SEO الأوسع لهذا الموقع، ولم يتم إعادة التحقق منه بشكل مستقل في هذه الجولة)
- “Dynamic rendering was a workaround and not a long-term solution for problems with JavaScript-generated content in search engines.” (ترجمة) «كان العرض الديناميكي حلًا مؤقتًا وليس حلاً طويل الأمد لمشاكل المحتوى المُنشأ عبر JavaScript في محركات البحث.» الانتقال إلى الاقتباس
Bing — bingbot وJavaScript
- “bingbot is generally able to render JavaScript.” — سلسلة bingbot، مدونة Bing Webmaster. (ترجمة) «bingbot قادر عمومًا على عرض JavaScript.» قراءة المنشور
- “bingbot does not necessarily support all the same JavaScript frameworks that are supported in the latest version of your favorite modern browser.” (ترجمة) «لا يدعم bingbot بالضرورة جميع أطر عمل JavaScript التي يدعمها أحدث إصدار من متصفحك الحديث المفضل». — المنشور نفسه. قراءة المنشور
Gary Illyes، Google — مهلات العرض تُنشئ نسخًا مكررة (منقول عبر منشور على LinkedIn، والذي يقاوم التحقق الآلي؛ تعامل معه كمصدر ثانوي عالي الثقة)
- “the centerpiece took forever to load, so rendering timed out… and we were left with a bunch of pages that only had the boilerplate. With only the boilerplate, those pages are dups.” (ترجمة) «استغرق العنصر الرئيسي وقتًا طويلاً للتحميل، لذا انتهت مهلة العرض… وبقينا مع مجموعة من الصفحات التي تحتوي فقط على النص القالب. مع النص القالب فقط، تكون تلك الصفحات نسخًا مكررة.» قراءة المنشور
أي نهج عرض ينبغي أن أختار؟
اعمل من الأعلى إلى الأسفل. السؤال دائمًا هو “هل يحصل الزاحف على HTML حقيقي لهذا المسار بدون تنفيذ JavaScript الخاص بي؟” — وابدأ بتأكيد ما إذا كان المسار يحتاج فعلًا إلى الفهرسة والتحقق مما يعيده طلب مباشر بدون JavaScript؛ ليس كل مسار يحتاج إلى SSR، وبعضها يعيد HTML قابلًا للاستخدام بالفعل.
1. هل تبدأ بناءً جديدًا؟
- نعم ← استخدم إطار عمل فوقي (Next.js، Nuxt، Angular مع SSR، SvelteKit، Remix). SSR/SSG والبيانات الوصفية لكل مسار مدمجة. توقف هنا.
- لا ← تابع.
2. هل يتغير المحتوى الخاص بك لكل مستخدم / لكل طلب؟
- لا (محتوى ثابت في الغالب) ← قم بالعرض المسبق / SSG في وقت البناء. الإصلاح الأبسط والأكثر متانة — كل مسار هو HTML حقيقي على القرص.
- نعم ← العرض من جانب الخادم (SSR) بحيث يعيد كل طلب HTML خاصًا بالمسار.
3. لا يمكنك القيام بـ SSR أو العرض المسبق الآن (تطبيق SPA يدوي قديم)؟
- استخدم العرض الديناميكي كجسر مؤقت — قدّم للزاحفين لقطة معروضة. خطط للترحيل إلى SSR/SSG؛ لا تعامل هذا كوجهة نهائية.
4. أيًا كان المسار الذي اخترته، تأكد من كل ما يلي لكل مسار:
- عنوان URL حقيقي عبر History API (بدون أجزاء
#!). - عنوان صفحة ووصف meta وعنوان أساسي فريدة في DOM المعروض.
- لا توجد إشارة أشد تقييدًا، مثل
noindexأو عنوان أساسي خاطئ، مضمنة في الغلاف الخام. - روابط
<a href>حقيقية بين المسارات. - مسارات الأخطاء تُرجع حالة خطأ حقيقية أو
noindexمعروضًا (من دون أخطاء soft 404s). - كل مسار في خريطة الموقع يُحل بشكل مستقل إلى HTML حقيقي.
قائمة فحص SEO لتطبيقات الصفحة الواحدة (SPA)
قابلية العنونة
- لكل عرض عنوان URL حقيقي عبر History API (
/products)، وليس جزءًا (/#/products). - الروابط بين المسارات هي روابط
<a href>حقيقية، وليست معالجات<div onClick>.
توفر المحتوى
- كل مسار يُرجع HTML حقيقيًا وفريدًا دون أن ينفذ الزاحف JavaScript الخاص بك (SSR، أو العرض المسبق/SSG، أو إطار عمل فوقي).
- محتوى المسار يُحمَّل قبل إغلاق نافذة العرض (تجنب التكرارات التي تحتوي فقط على قالب).
قابلية الفهرسة لكل مسار
-
<title>فريد ووصف meta لكل مسار، موجودان في DOM المعروض. -
rel=canonicalفريد لكل مسار في DOM المعروض. - لا توجد قيمة مؤقتة عشوائية لـ
noindexأو العنوان الأساسي في الغلاف الخام يمكن أن تثبّت إشارة أشد تقييدًا.
الأخطاء
- مسارات “غير موجود” تعيد التوجيه إلى عنوان URL يُرجع الخادم له حالة خطأ حقيقية أو
يحمل
noindexمضافًا بواسطة JavaScript (من دون أخطاء soft 404s عند200). - إذا كنت تستخدم مسار
noindex، تأكد من إضافته بعد أن يقرر التطبيق أن المسار غير صالح، وليس موجودًا من أول رسم —noindexأولي يمكن أن يجعل Google يتخطى عرض الصفحة.
خريطة الموقع
- كل مسار مدرج يُحل بشكل مستقل إلى HTML حقيقي.
- المسارات الديناميكية (
/product/:id) مُعدَّدة من مصدر بيانات التطبيق، وليست مُدارة يدويًا.
التحقق
- فحص HTML الخام عبر مسارات متعددة (curl)، لتأكيد محتوى فريد لكل عنوان URL.
- فحص URL يؤكد المحتوى المعروض والعنوان والوصف والعنوان الأساسي لكل مسار.
النماذج الذهنية
1. النصفان اللذان يجب ألا تخلطهما. قابلية العنونة (History API، عناوين URL فريدة، بدون أجزاء) وتوفر المحتوى (SSR، العرض المسبق، أو إطار عمل فوقي) مستقلان. عناوين URL واضحة من دون HTML لكل مسار = خريطة موقع منظمة لمسارات تعيد أغلفة فارغة. تحتاج إلى الجزأين.
2. «ماذا يعيد الخادم عند طلب مباشر جديد؟»
مشكلة SPA بأكملها هي أن عرض المتصفح يختلف عن استجابة الخادم. لأي
مسار، اسأل ما الذي يحصل عليه curl بدون تنفيذ JS. إذا كان الهيكل، فقد
يرى الزاحف الغلاف نفسه أيضًا.
3. الحالة الافتراضية هي 200 — بما في ذلك للأخطاء.
أجهزة التوجيه من جانب العميل تُبقي 200 الأصلي. افترض أن كل عرض “خطأ” هو soft 404
حتى تجعله عمدًا يعيد حالة خطأ حقيقية أو تضيف noindex بعد العرض.
4. فخ التوجيه الأكثر تقييدًا.
توفّق Google بين HTML الخام والناتج المعروض باختيار الإشارة الأكثر تقييدًا. قد يتجاوز noindex
أو عنوان أساسي خاطئ في الغلاف «إصلاح» JavaScript. دقق في HTML الخام، لا في DOM المعروض وحده.
5. إطار العمل الميتا أولاً للبنى الجديدة. التوجيه اليدوي من جانب العميل يعني امتلاك كل هذه المشاكل. اختيار إطار عمل يوفر SSR/SSG وبيانات meta لكل مسار يعفيك من إدارة هذه المشكلات يدويًا.
مرجع سريع لـSEO في تطبيقات الصفحة الواحدة (SPA)
التوجيه
| النهج | مثال عنوان URL | يصل إلى الخادم؟ | آمن لجوجل؟ |
|---|---|---|---|
| History API | /products | نعم | نعم (موصى به) |
| توجيه الهاش | /#/products | لا (الجزء لا يُرسل أبدًا) | لا — مُهمل منذ 2015 |
Hash-bang (#!) | /#!/products | لا | لا — مخطط AJAX قديم، مُهمل |
استراتيجيات العرض
| الاستراتيجية | هل يحصل الزاحف على HTML حقيقي دون تشغيل JS؟ | الأفضل لـ |
|---|---|---|
| SSR | نعم | المحتوى لكل مستخدم / لكل طلب |
| Prerendering / SSG | نعم | المحتوى الثابت في الغالب |
| إطار عمل فوقي (Next/Nuxt/إلخ) | نعم (مدمج) | البنى الجديدة |
| العرض الديناميكي | نعم، للروبوتات فقط | جسر مؤقت على تطبيقات SPA القديمة |
| CSR خالص (عميل فقط) | لا | لا شيء تريد فهرسته بشكل موثوق |
متطلبات أساسية لكل مسار (كلها في الـ DOM المعروض)
- عنوان URL فريد (History API) ·
<title>فريد · وصف ميتا فريد ·rel=canonicalفريد · روابط<a href>حقيقية · مسارات الأخطاء مع حالة حقيقية أوnoindex.
حقائق سريعة
- الجزء بعد
#لا يُرسل أبدًا إلى الخادم — لهذا يفشل توجيه الهاش. - أجهزة التوجيه من جانب العميل تُبقي
200لكل شيء، بما في ذلك “غير موجود” → soft 404s. - Google يوفق بين الخام والمعروض بأخذ الإشارة الأكثر تقييدًا.
- لا يوجد تنسيق خاص لخريطة موقع SPA — يجب أن يحل كل مسار مدرج إلى HTML حقيقي.
ممارسات يجب تجنبها في SPA SEO
إطلاق SPA بـ CSR خام وإرسال خريطة موقع كاملة. تسرد خريطة الموقع 500 مسار، لكنها جميعًا تعيد الغلاف نفسه عند طلب مباشر. لا تنشئ خريطة الموقع المحتوى؛ بل يجب أن يتوفر في الاستجابة أو بعد العرض.
إضافة History API واعتبار الأمر منتهيًا. تحل عناوين URL الواضحة قابلية العنونة، لا توفر المحتوى. من دون SSR أو العرض المسبق، يظل الخادم يعيد الغلاف نفسه لكل مسار.
توجيه الهاش / الهاش-بانغ “كحل احتياطي.” الجزء لا يصل أبدًا إلى الخادم، لذا لا يمكن للخادم أن يختلف حسب المسار. مهمل منذ 2015؛ ليس حلاً احتياطيًا، بل طريق مسدود.
التنقل باستخدام <div onClick> بدلاً من <a href>.
Google يستخرج hrefs لاكتشاف عناوين URL. التنقل عبر معالج النقر غير مرئي لاستخراج الروابط، لذا قد لا يتم العثور على تلك المسارات أبدًا.
noindex أو عنوان أساسي مؤقت في الغلاف الخام «سيتجاوزه JavaScript».
تختار Google الإشارة الأكثر تقييدًا بين الخام والمعروض. قد تفوز إشارة الغلاف وتحجب المسار بصمت.
إرجاع 200 لطرق عرض “غير موجود”.
تؤدي soft 404s إلى فهرسة صفحات سطحية أو فارغة. أعد التوجيه إلى حالة خطأ حقيقية أو أضف noindex معروضًا.
تحميل محتوى المسار ببطء، بعد القالب المشترك. انتهاء مهلة العرض يترك فقط الهيكل المشترك، وينهار كل مسار إلى نسخة مكررة من كل الآخر (Illyes). حمّل المحتوى الرئيسي أولاً.
الاعتماد على حالة العميل لنقل المحتوى عبر التنقلات. يمسح نظام العرض Local Storage وSession Storage وملفات تعريف الارتباط بين عمليات تحميل الصفحات؛ لذلك قد يغيب المحتوى المحفوظ في حالة العميل وحدها عندما تعرض Google المسار التالي.
مشكلات فهرسة SPA الشائعة
كل مسار يُرجع نفس HTML
العرض: /products و /about لهما طرق عرض مختلفة في المتصفح لكن استجابات خام متطابقة. السبب المحتمل: توجيه History API يوفر قابلية العنونة بدون SSR أو prerendering. الإصلاح: أنتج HTML خاصًا بالمسار وتأكد من أن طلبًا مباشرًا لكل مسار يحتوي على عنوانه ونصه وبياناته الوصفية الخاصة.
المسارات المفقودة تظهر كصفحات ناجحة
العرض: مسار غير موجود يُظهر عرضًا لعدم الوجود بينما يُرجع 200. السبب المحتمل: جهاز التوجيه من جانب العميل يملك الخطأ بعد أن أرسل الخادم بالفعل هيكلًا ناجحًا. الإصلاح: أعد حالة الخادم الصحيحة؛ إذا لم يمكن شحنها بعد، أعد التوجيه إلى URL بحالة خطأ حقيقية أو اعرض noindex. تأكد بطلب مباشر جديد، وليس تنقلًا داخل التطبيق.
المسارات تنهار إلى نسخ مكررة بعد العرض
العرض: تجمع محركات البحث عناوين URL المميزة معًا أو تحتفظ بالغلاف المشترك فقط. السبب المحتمل: يُحمَّل محتوى المسار متأخرًا جدًا فيلتقط نظام العرض القالب المشترك. الإصلاح: أعطِ الأولوية للمحتوى الأساسي في استجابة الخادم أو في أول مراحل العرض. تأكد من أن عدة مسارات تعرض محتوى فريدًا قبل انتهاء البرامج النصية الاختيارية.
اختبار ما يُرجعه الخادم فعليًا لكل مسار
فخ SPA هو أن المسارات تختلف في المتصفح لكن ليس على الشبكة. تحقق من عدة مسارات على مستوى HTML الخام، وليس واحدًا فقط.
جلب HTML الخام لكل مسار (macOS / Linux)
# Fetch a few routes with a Googlebot UA and compare — they should NOT be identical
UA="Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
for path in / /products /about /product/123; do
echo "=== $path ==="
curl -s -A "$UA" "https://example.com$path" | wc -c # byte counts should differ
doneقارن HTML الخام لمسارين (هل هما نفس الهيكل؟)
UA="Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
diff <(curl -s -A "$UA" https://example.com/products) \
<(curl -s -A "$UA" https://example.com/about) \
&& echo "IDENTICAL — bare shell, content is client-only" \
|| echo "Different — routes return distinct HTML (good)"تحقق من العنوان والكنسي لكل مسار في الاستجابة الخام (grep / regex)
UA="Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
curl -s -A "$UA" https://example.com/products \
| grep -Eio '<title>[^<]*</title>|<link[^>]+rel=["'"'"']canonical["'"'"'][^>]*>'تحقق من حالة HTTP لمسار «غير موجود» (كاشف soft-404)
# A missing route should NOT return 200
curl -s -o /dev/null -w "%{http_code}\n" https://example.com/this-route-does-not-existفي وحدة تحكم DevTools بالمتصفح — افحص العنوان/الكنسي المعروض
// Run on each route after client-side navigation to confirm JS set them
console.log('title:', document.title);
console.log('description:',
document.querySelector('meta[name="description"]')?.content);
console.log('canonical:',
document.querySelector('link[rel="canonical"]')?.href);إشارة مرجعية — قم بتمييز “الروابط” التي تعتمد على معالج النقر وليست روابط حقيقية
javascript:(()=>{const bad=[...document.querySelectorAll('[onclick],div[role="link"]')]
.filter(e=>!e.closest('a[href]'));
bad.forEach(e=>e.style.outline='3px solid red');
alert(bad.length+' non-anchor clickable(s) outlined — these are invisible to link extraction');})();XPath — العثور على روابط التجزئة/الهاش التي لا يستطيع الزاحف حلها (الصق في وحدة تحكم DevTools)
$x('//a[starts-with(@href, "#") or contains(@href, "/#/")]')
.map(a => a.getAttribute('href'));
// Any results are hash-routed links; migrate them to History API paths. أدوات لتدقيق تطبيق SPA
- URL Inspection (Google Search Console) — شاهد HTML المُزحف مقابل المعروض لمسار وتأكد من أن العنوان والوصف والكنسي لكل مسار يصلون فعليًا.
- Rich Results Test / URL Inspection “View crawled page” — عرض Google الخاص لمسار واحد، مفيد لتأكيد بقاء المحتوى بعد العرض.
curlمع وكيل مستخدم Googlebot — أسرع طريقة لمقارنة HTML الخام لعدة مسارات واكتشاف هيكل مشترك.- Screaming Frog SEO Spider (وضع عرض JavaScript) — ازحف الموقع مع وبدون العرض لمقارنة المحتوى والعناوين الخام مقابل المعروض عبر كل مسار.
- Ahrefs Site Audit — يكشف مشكلات قابلية الفهرسة، والعناوين والكنسيات المفقودة/المكررة، وأنماط soft-404-like، أي الشبيهة بأخطاء الصفحات غير الموجودة، عبر المسارات.
- DebugBear — مراقبة أداء موجهة لتطبيقات SPA؛ توقيت العرض مهم لأن المسارات البطيئة قد تنتهي مهلتها إلى نسخ مكررة من القالب.
- Bing Webmaster Tools — URL Inspection — يعرض bingbot JavaScript بشكل أقل اتساقًا من Googlebot، لذا تأكد من مساراتك أيضًا على جانب Bing.
إثبات أن مسار SPA قابل للفهرسة بشكل مستقل
اختبار تكافؤ HTML عند الدخول المباشر
الاختبار الذي يجب تشغيله: افتح مسارات ممثلة في جلسة جديدة واجلب نفس عناوين URL
باستخدام curl. النتيجة المتوقعة: يُرجع كل عنوان URL محتواه الأساسي وإشارات
الرأس الخاصة به دون حالة تطبيق سابقة. تفسير الفشل: يعتمد المسار على
التنقل من جانب العميل أو الحالة المخزنة. نافذة المراقبة: فورية. مشغل
التراجع: يعمل المسار فقط بعد الدخول عبر الصفحة الرئيسية.
اختبار معالجة حالة الخطأ
الاختبار الذي يجب تشغيله: اطلب مسارًا غير صالح معروفًا مباشرة وافحص كلًا من الحالة
والتوجيهات المعروضة. النتيجة المتوقعة: حالة خطأ حقيقية، أو البديل الموثق
لـ noindex معروض/إعادة توجيه إلى استجابة خطأ. تفسير
الفشل: يولد تطبيق SPA أخطاء soft 404s. نافذة المراقبة: فورية.
مشغل التراجع: تُنشر المسارات غير الصالحة كصفحات 200 قابلة للفهرسة.
اختبار عزل البيانات الوصفية
الاختبار المطلوب تنفيذه: قارن العنوان الخام والمُعالَج، ووسم robots، والرابط الأساسي (canonical) عبر ثلاثة مسارات على الأقل. النتيجة المتوقعة: كل مسار يحتوي على مجموعة واحدة مقصودة ومتسقة داخليًا. تفسير الفشل: الغلاف المشترك يُسرّب البيانات الوصفية بين المسارات أو تقوم JavaScript بالكتابة فوقها بعد فوات الأوان. نافذة المراقبة: فورًا محليًا وبعد إعادة الزحف في URL Inspection. مشغل التراجع: أي مسار يرث canonical من مسار آخر أو توجيهًا مقيدًا من الغلاف.
اختبر نفسك: SEO لتطبيقات الصفحة الواحدة
خمسة أسئلة سريعة حول جعل تطبيقات الصفحة الواحدة قابلة للزحف والفهرسة. اختر إجابة لكل سؤال، ثم تحقق.
موارد تستحق وقتك
مقالاتي ذات الصلة
- مشكلات JavaScript SEO وأفضل ممارساته — دليلي الكامل لتحسين محركات البحث في JavaScript، بما في ذلك أقسام “لا تستخدم أجزاء في عناوين URL” وتكرار المحتوى في قشرة التطبيق التي يبني عليها هذا المقال.
- دليل المبتدئين إلى SEO التقني — موضع العرض والفهرسة ضمن الصورة الأكبر.
محادثاتي
- كيف يعمل البحث (SlideShare) — شرح خطوة بخطوة للزحف والعرض والفهرسة والترتيب، وهو المسار الذي يجب أن ينجو منه تطبيق الصفحة الواحدة. (ينطبق إخلاء المسؤولية الدائم الخاص بي: “This is my understanding of systems… not going to be 100% complete or accurate.” (ترجمة) «هذا فهمي للأنظمة… ولن يكون كاملًا أو دقيقًا بنسبة 100%.»)
من جميع أنحاء الصناعة
- Google — فهم أساسيات JavaScript SEO — نموذج قشرة التطبيق و”استخدم History API بدلاً من الأجزاء.”
- Google — إصلاح مشكلات JavaScript المتعلقة بالبحث — قسم soft-404 في تطبيقات الصفحة الواحدة وتوصية History API.
- Google — إيقاف مخطط زحف AJAX (2015) — النهاية الرسمية لزحف hash-bang.
- Bing — سلسلة bingbot: JavaScript والعرض الديناميكي والإخفاء — موقف bingbot من عرض JavaScript.
- استخدام خدمة عرض مسبق لصفحات HTML الفارغة في تطوير SPA ليس إخفاءً (Search Engine Roundtable، 2015) — تغطية تصريح Gary Illyes بأن العرض المسبق لتطبيقات الصفحة الواحدة ليس cloaking. (إعادة صياغة SER لتصريح Illyes، بتاريخ 2015 — مصدر ثانوي.)
- SEO لتطبيقات الصفحة الواحدة (Nuxt SEO) — شرح من جانب الإطار لنفس المشكلات.
- كيفية تحسين تطبيقات الصفحة الواحدة لـSEO (DebugBear) — زاوية العرض والأداء في قابلية فهرسة تطبيقات الصفحة الواحدة.
- SPA في مسرد MDN — تعريف محايد لنمط تطبيق الصفحة الواحدة.
فيديوهات
- Google Search Central (YouTube) — سلسلة Martin Splitt حول JavaScript SEO تغطي العرض، وHistory API، وأنماط فشل تطبيقات الصفحة الواحدة التي نوقشت هنا. القناة
سجل التغييرات
تم التحديث في 14 أغسطس 2026.
ملخص تحريري وتفاصيل التغيير المسجلة.تفاصيل التغيير
-
تفاصيل التغيير متاحة حاليًا باللغة الإنجليزية.
-
تفاصيل التغيير متاحة حاليًا باللغة الإنجليزية.
المقارنة الكاملة غير متاحة — لم تُؤرشف لقطة سابقة لهذه المراجعة.
تم التحديث في 18 يوليو 2026.
ملخص تحريري وتفاصيل التغيير المسجلة.تفاصيل التغيير
-
تفاصيل التغيير متاحة حاليًا باللغة الإنجليزية.
-
تفاصيل التغيير متاحة حاليًا باللغة الإنجليزية.
-
تفاصيل التغيير متاحة حاليًا باللغة الإنجليزية.
-
تفاصيل التغيير متاحة حاليًا باللغة الإنجليزية.
-
تفاصيل التغيير متاحة حاليًا باللغة الإنجليزية.
المقارنة الكاملة غير متاحة — لم تُؤرشف لقطة سابقة لهذه المراجعة.