خلّصت منتجك في Lovable أو Bolt أو Cursor، ورفعته على Vercel أو Netlify أو سيرفرك، والنشر نجح بدون أي خطأ. تفتح الرابط فتلقى صفحة بيضاء فاضية. تضغط F12 وتفتح Console، وتلقى سطر أحمر شكله:
Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html". Strict MIME type checking is enforced for module scripts per HTML spec.
المعنى: المتصفح طلب ملف كود، ووصلته صفحة HTML.
إيش يعني بالضبط
لما يتبني منتج React مع Vite، الناتج ملف index.html شبه فاضي، ومعه ملفات JavaScript في مجلد assets بأسماء فيها رمز مثل index-a1b2c3.js. الرمز يتغيّر مع كل بناء، وهذي الملفات هي اللي ترسم الصفحة، فإذا ما اشتغلت تبقى بيضاء.
كل رد من السيرفر معه عنوان اسمه Content-Type يقول نوع الملف: text/javascript للكود، و text/html لصفحات الويب. Vite يحمّل الكود كـ «module»، يعني <script type="module">. السكربتات العادية يتساهل معها المتصفح لأسباب تاريخية، لكن معيار HTML يلزمه يرفض أي module ما يوصل بنوع JavaScript. فلما يرجع السيرفر text/html، المتصفح ما يشغّل شي.
والسؤال الحقيقي: ليش السيرفر رجّع HTML لرابط ينتهي بـ .js؟ غالبًا لأن الملف مو موجود، وقاعدة «رجّع index.html لأي رابط» اللي يحتاجها تطبيق الصفحة الوحدة مسكت الطلب ورجّعت الصفحة بدل 404. نفس فكرة خطأ Unexpected token '<'، لكن مع الكود بدل البيانات.
ليش يصير في المنتجات المبنية بالذكاء الاصطناعي
في معاينة Lovable أو Bolt ما تشوفه، لأن سيرفر التطوير يقدّم الملفات بنفسه. يطلع لما تنقل المنتج لاستضافة ثانية، لواحد من أربعة أسباب:
- المجلد الغلط انرفع: Vite يطلّع الملفات الجاهزة في مجلد
dist. إذا إعدادات النشر تأشّر على جذر المشروع، أو علىbuildلأن الذكاء الاصطناعي نسخه من مشروع قديم، الاستضافة تقدّمindex.htmlالأصلي اللي يطلب/src/main.tsx. هذا ملف TypeScript ما يشغّله المتصفح كما هو، وما يجهّزه إلا سيرفر التطوير. - المنتج تحت مسار فرعي: حطيته على
example.com/app/أو على GitHub Pages تحت اسم المستودع، وإعدادbaseفي Vite باقي على/. فالصفحة تطلب/assets/index-a1b2c3.jsمن جذر الدومين، والملف ساكن في/app/assets/. - نشر جديد وصفحة قديمة: كل تعديل من الذكاء الاصطناعي يعني نشر بأسماء ملفات جديدة، وبعض الاستضافات تحذف ملفات النشر القديم، أو سيرفرك يستبدل المجلد كله. فالمستخدم اللي عنده
index.htmlقديم، في تبويب مفتوح أو محفوظ في متصفحه، يطلب ملف ما عاد موجود. - قاعدة توجيه تمسك حتى الملفات الموجودة: Vercel و Netlify و Cloudflare Pages يقدّمون الملف الموجود قبل القاعدة. لكن الذكاء الاصطناعي أحيانًا يكتبها بطريقة تتجاوز هذا، مثل علامة
!في آخر سطر_redirectsفي Netlify، أو إعداد في سيرفرك يرجّعindex.htmlلكل رابط بدون ما يشيّك على الملف.
كيف تتأكد بنفسك
- افتح الموقع المنشور، واضغط F12، وافتح تبويب Network، ثم حدّث الصفحة.
- اختار فلتر JS فوق القائمة، ودوّر على ملف
.js، غالبًا اسمه يبدأ بـindex-. - اضغط عليه، وفي تبويب Headers شوف Request URL، وStatus، وتحت Response Headers قيمة Content-Type. إذا كانت
text/html، هذا هو الطلب. - افتح تبويب Response. إذا شفت
<!DOCTYPE html>بدل كود، السيرفر رجّع صفحة. - اقرأ الرابط: فيه
/src/main.tsx؟ المجلد غلط. ناقصه المسار الفرعي، مثل/assets/بدل/app/assets/؟ المشكلة فيbase. الملف مو موجود في آخر نشر؟ الصفحة قديمة. موجود ومع ذلك يرجع HTML؟ قاعدة التوجيه تمسكه. - جرّب في نافذة خاصة (Incognito). إذا اشتغل هناك فقط، عندك نسخة قديمة محفوظة.
الحل اللي لا تسويه
- حذف type="module" من index.html. الرسالة تختفي، لكن الملف اللي يوصل للحين صفحة HTML، فيطلع لك بدالها
Unexpected token '<'، والصفحة باقية بيضاء. - حذف قاعدة التوجيه كلها. الملف الناقص يصير 404 واضح، لكن الصفحات الداخلية ترجع تتعطّل لما أحد يحدّثها، وهذي مشكلة 404 عند التحديث.
- تطلب من المستخدمين يمسحون الكاش. أغلبهم ما يدري إن فيه مشكلة، يشوف صفحة بيضاء ويطلع.
الحل الصح
حسب السبب اللي لقيته:
- المجلد: في إعدادات النشر خلّ أمر البناء
npm run build، ومجلد المخرجات (Output Directory أو Publish directory)dist، ثم أعد النشر. ترفع الملفات الجاهزة، مو ملفات المشروع. - المسار الفرعي: في
vite.configحطbaseبنفس المسار، مثل'/app/'، أو'/اسم-المستودع/'في GitHub Pages، وأعد البناء. Vite يعدّل روابط الملفات بنفسه، وأي رابط تبنيه في الكود ابنه منimport.meta.env.BASE_URL. - قاعدة التوجيه: خلّها تقدّم الملفات الموجودة أولًا، وما ترجّع
index.htmlلطلبات مجلدassets. في Vercel مثلًا:{"rewrites":[{"source":"/((?!assets/).*)","destination":"/index.html"}]}. في Netlify شيل علامة!إذا ما تحتاجها. وفي سيرفرك على nginx،try_files $uri $uri/ /index.html;يشيّك على الملف قبل ما يرجّع الصفحة، وtry_files $uri =404;داخل قسم/assets/يخلّي الملف الناقص يرجع 404 بدل صفحة تضلّلك. - الكاش:
index.htmlبـCache-Control: no-cache، يعني المتصفح يتأكد من السيرفر قبل ما يستخدم نسخته، وملفاتassetsبـmax-ageطويل معimmutable، لأن اسمها يتغيّر مع كل تعديل. وللأجزاء اللي تتحمّل لاحقًا، Vite يطلق حدثvite:preloadErrorلما يفشل تحميلها، والمعتاد إعادة تحميل الصفحة فيه.
تبي أحد يتأكد منها؟
إذا شفت الطلب ومو واضح لك ليش رجع HTML، مكالمة استشارية نفتح فيها Network معك ونوصل للسبب، ويوصلك التسجيل وخطوات مكتوبة. وإذا تبي النشر والتوجيه والكاش يترتّبون مرة وحدة، فيه الإصلاح المباشر. وإذا منتجك من Lovable، شوف مشاكل Lovable الأشهر، وللأسباب الثانية شوف يشتغل عندك ثم يتعطّل بعد النشر؟.