Brotli وgzip: قلّل حجم ملفات JS وCSS من غير ما تغيّر الكود
مستوى القارئ: متوسط
لو عندك bundle حجمه 420KB، المقال ده هيساعدك تخليه يوصل للمتصفح حوالي 96KB باستخدام Brotli، من غير refactor ولا تغيير في React أو Vue أو Next.js.
المشكلة باختصار
الطريقة الشائعة الغلط إنك تبدأ بتكسير الـ bundle أو تغيير framework قبل ما تقيس النقل الفعلي على الشبكة. ركز: أحيانًا المشكلة مش إن الكود كتير، المشكلة إنك بتبعته خام.
الافتراض إن عندك ملفات نصية static مثل .js و.css و.html، وحجمها بعد الـ build واضح. في موقع تجارة إلكترونية بحدود 50K زيارة يوميًا، ملف main.js بحجم 420KB ممكن يضيف 300 إلى 800ms على شبكات 4G متوسطة، خصوصًا لو المستخدم أول مرة يفتح الموقع.
المفهوم بمثال بسيط
اعتبر إن عندك تقرير طويل هتبعته بالإيميل. ممكن تبعته كما هو، أو تضغطه في ملف ZIP. المستقبل يفك الضغط ويقرأ نفس التقرير. ده بالظبط اللي بيحصل مع gzip وBrotli، لكن على مستوى HTTP.
المتصفح يرسل في الطلب header اسمه Accept-Encoding. لو المتصفح كاتب br أو gzip، السيرفر يقدر يرد بملف مضغوط ويضيف Content-Encoding: br أو Content-Encoding: gzip. حسب MDN، هذا تفاوض محتوى عادي بين العميل والسيرفر، وليس تغييرًا في الملف الأصلي.
الفرق المهم: الضغط الديناميكي يعمل وقت كل request، بينما pre-compression يعمل وقت الـ build. أفضل طريقة هنا إنك تضغط الملفات مرة واحدة بعد البناء، وتخلي Nginx أو الـ CDN يقدّم النسخة الجاهزة.
الخطوات العملية
- ابنِ المشروع كالمعتاد:
npm run build. - اضغط الملفات النصية إلى
.brو.gzبعد الـ build. - فعّل Nginx لكي يرسل gzip ويبحث عن الملفات المضغوطة مسبقًا.
- تحقق من
Content-Encodingباستخدامcurl.
# داخل مجلد dist أو .next/static أو build حسب مشروعك
find dist -type f \( -name "*.js" -o -name "*.css" -o -name "*.html" \) -print0 \
| xargs -0 -I{} sh -c 'brotli -f -q 11 "{}" && gzip -f -k -9 "{}"'
# تحقق من الاستجابة
curl -I -H "Accept-Encoding: br" https://example.com/assets/main.js
curl -I -H "Accept-Encoding: gzip" https://example.com/assets/main.jsلو كل شيء شغال، المفروض تشوف header مثل:
Content-Encoding: br
Vary: Accept-Encodingإعداد Nginx قابل للنسخ
لو Nginx عندك يدعم gzip static، استخدم الإعداد التالي كبداية. Brotli في Nginx غالبًا يحتاج module إضافي أو دعم من الـ CDN، لذلك لو الاستضافة عندك فيها Cloudflare أو Fastly أو BunnyCDN، فعّل Brotli من هناك بدل ما تركّب module بنفسك.
server {
listen 443 ssl http2;
server_name example.com;
root /var/www/app/dist;
gzip on;
gzip_static on;
gzip_vary on;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types text/plain text/css application/javascript application/json image/svg+xml;
location / {
try_files $uri $uri/ /index.html;
}
}الـ trade-off هنا واضح. gzip_comp_level 6 متوازن للإنتاج. هتكسب حجم نقل أقل، لكن لو فعلت ضغط ديناميكي عالي جدًا على CPU ضعيف، ممكن تزود زمن الاستجابة تحت الضغط. لذلك pre-compression أفضل للملفات static.
الأرقام قبل وبعد
في مثال واقعي لملف main.js حجمه 420KB، gzip نزله إلى 118KB، وBrotli نزله إلى 96KB. هذه أرقام تقريبية لكنها قريبة من اللي هتشوفه مع JavaScript وCSS لأنهم ملفات نصية قابلة للضغط.
web.dev يذكر أن Brotli قد يعطي نتائج أصغر من gzip للنصوص، خصوصًا JavaScript وHTML وCSS. لكن ركز: الحجم الأصغر لا يعني دائمًا استجابة أسرع لو الضغط يتم لحظيًا على كل طلب. لذلك كرر القياس عندك.
متى لا تستخدم هذه الطريقة
لا تضغط صور JPEG أو PNG أو AVIF بهذه الطريقة. الملفات دي مضغوطة بالفعل، وMDN يوضح أن إعادة ضغط بيانات مضغوطة قد لا تقلل الحجم، بل قد تزوده. لا تستخدمها أيضًا لو الـ CDN عندك يضغط كل شيء تلقائيًا وأنت لا تتحكم في headers، لأنك قد تضيع وقت build بدون مكسب واضح.
كذلك لا تبدأ بـ Brotli quality 11 على كل request ديناميكي. استخدمه للـ static pre-compression. لو عندك API JSON يتغير باستمرار، gzip أو Brotli dynamic بجودة متوسطة قد يكون كافيًا، والقرار هنا لازم يتقاس بـ p95 latency وCPU.
مصادر اعتمد عليها المقال
- MDN: Accept-Encoding وContent-Encoding
- web.dev: ضغط النصوص باستخدام Brotli
- Nginx: ngx_http_gzip_module
- Node.js: zlib وBrotli APIs
الخطوة التالية
الخطوة التالية: افتح Network tab، اختار أكبر ملف JavaScript أو CSS، وسجّل حجمه بدون ضغط ومع Content-Encoding. لو الفرق أكبر من 60%، فعّل pre-compression في الـ build قبل أي refactor.