Menu

express.static: تقديم الملفات الثابتة في Express

يقدّم app.use(express.static(path.join(__dirname, 'public'))) كل ملف في المجلد public على عنوان URL المطابق: الملف public/style.css يجيب على /style.css. أضف مسار تركيب لتقديمها تحت بادئة.

تحتوي هذه الصفحة على محررات قابلة للتشغيل. حرّر، شغّل، وشاهد النتيجة فوراً.

express.static هو الـ middleware المدمج الذي يقدّم الملفات من مجلد. يجعل app.use(express.static(path.join(__dirname, 'public'))) كل ملف في public متاحًا على المسار نفسه في عنوان URL: الملف public/style.css يجيب على /style.css، والملف public/index.html يجيب على /. يضبط Express الترويسة Content-Type من امتداد الملف، وحين لا يطابق أي ملف يمرر الطلب إلى مساراتك بدلًا من الإجابة بـ 404.

الإجابة على GET /style.css هي نص الملف، مع Content-Type: text/css; charset=utf-8 في تبويب Headers. أرسل GET / لتحصل على index.html، ثم GET /api/time: لا يوجد ملف كهذا، فيجيب المسار.

لماذا يحتاج مسار المجلد إلى __dirname

يحلّ express.static('public') الاسم 'public' نسبةً إلى المجلد الذي شغّلت منه Node، لا المجلد الذي فيه server.js. يعمل ذلك حين تشغّل node server.js داخل المشروع، وينكسر حين يشغّل مدير عمليات أو سكربت التطبيق من مكان آخر:

// project/server.js uses express.static('public')
// cd project && node server.js      -> serves project/public
// node project/server.js            -> looks for ./public, every file is 404

path.join(__dirname, 'public') هو المجلد المجاور للملف الذي يحتوي السطر، أينما شُغّل Node. في ES module استخدم path.join(import.meta.dirname, 'public') (في Node 20.11 وما بعده). تغطي صفحة وحدة path الكائن __dirname والدالة path.join بالتفصيل.

تقديم الملفات تحت بادئة

مرّر مسار تركيب أولًا لتقديم المجلد تحت بادئة في عنوان URL. البادئة ليست جزءًا من مسار الملف على القرص:

يعيد GET /assets/app.js النص console.log("hi"); بنوع text/javascript، ويصل GET /assets/img/dot.svg إلى المجلد الفرعي. أرسل GET /app.js دون البادئة: النتيجة 404، لأن الملفات موجودة فقط تحت /assets. استخدم بادئة لتمنع تصادم عناوين الملفات مع مسارات واجهة API.

ترويسات التخزين المؤقت عبر maxAge

يرسل Express افتراضيًا Cache-Control: public, max-age=0، فتتحقق المتصفحات مع الخادم في كل استخدام (وتحصل على 304 Not Modified سريعة حين لا يتغير الملف، بفضل الترويسة ETag). أما الملفات التي تتغير أسماؤها مع كل بناء، مثل app.3f9a1c.js، فأخبر المتصفحات أن تحتفظ بها:

افتح تبويب Headers: صارت Cache-Control هي public, max-age=604800، أي سبعة أيام بالثواني. يوقف index: false تقديم index.html لعناوين المجلدات. ومن الخيارات المفيدة الأخرى extensions: ['html']، الذي يسمح لـ /about بإيجاد about.html، وdotfiles، وقيمته 'ignore' افتراضيًا فلا تُقدَّم أبدًا ملفات مثل .env.

تطبيقات الصفحة الواحدة: الرجوع إلى index.html

تطبيق React أو Vue مبني للتوجيه في جهة العميل يحتاج إلى أن يعيد كل مسار غير معروف index.html، كي لا تنتهي إعادة تحميل /settings بـ 404. ضع هذا المسار الاحتياطي بعد express.static وبعد مسارات واجهة API:

app.use(express.static(path.join(__dirname, 'dist')));
app.use('/api', apiRouter);

// Express 5 wildcard syntax
app.get('/*splat', (req, res) => {
    res.sendFile(path.join(__dirname, 'dist', 'index.html'));
});

أخطاء شائعة

تضمين اسم المجلد في عنوان URL. مع app.use(express.static('public')) يكون العنوان /style.css، لا /public/style.css. إن أردت /public في العنوان فركّبه: app.use('/public', express.static(...)).

ربط الملفات بمسارات نسبية في HTML. يطلب <link href="style.css"> في الصفحة /blog/post الملف /blog/style.css. ابدأ المسار بشرطة مائلة: /style.css.

وضع الأسرار في المجلد الثابت. كل ما فيه علني. أبقِ ملفات الإعدادات والمفاتيح والشيفرة المصدرية خارجه.

الأسئلة الشائعة

هل يمكنني استخدام express.static أكثر من مرة؟

نعم. سجّل واحدًا لكل مجلد؛ يبحث Express فيها بالترتيب الذي أضفتها به ويقدّم أول ملف يجده. أعطِ كلًا منها مسار تركيب خاصًا به حين قد تحتوي المجلدات على ملفات بالاسم نفسه.

هل يجب أن يقدّم Express الملفات الثابتة في الإنتاج؟

للتطبيقات الصغيرة لا بأس بذلك. أما مع حركة مرور كبيرة، فضع أمامه CDN أو وكيلًا عكسيًا مثل Nginx، فهو يقدّم الملفات أسرع ويخزّنها أقرب إلى المستخدمين، واترك Express لواجهة API.

كيف أرسل ملفًا محددًا من مسار؟

استخدم res.sendFile(path.join(__dirname, 'files', 'report.pdf')). يجب أن يكون المسار مطلقًا (أو تمرر الخيار root). واستخدم res.download() بدلًا من ذلك حين يجب أن يحفظ المتصفح الملف لا أن يعرضه.

رسم توضيحي للغات البرمجة في Coddy

تعلّم البرمجة مع Coddy

ابدأ الآن