Brotli Compression للمتوسط: نزّل حجم JavaScript bundle 24% بسطر NGINX
مستوى المقال: متوسط — يفترض إنك تعرف NGINX وتعدّل ملفات config بنفسك، وفاهم أساسيات HTTP headers و Content-Encoding. لو إنت مبتدئ تمامًا، الفقرة اللي بمثال شنط السفر مكتوبة عشانك بالظبط.
لو الـ JavaScript bundle بتاعك حجمه 1.1MB بعد minify، السيرفر بيبعته للزائر بـ gzip كـ 312KB. Brotli على نفس الـ bundle بينزّله لـ 237KB، يعني توفير 24% إضافي بـ 4 سطور تكوين، وأول رسم شاشة بيبان للزائر المصري على 4G من 1.42 ثانية لـ 1.08 ثانية. الفرق ده مش رفاهية — هو الفرق بين زائر بيتفاعل مع الصفحة وزائر بيقفلها.
المشكلة باختصار
السيرفر بياخد كود JavaScript أو CSS أو HTML، ويبعته للمتصفح عبر الإنترنت. كل byte بيتنقل بيكلّفك bandwidth، وبيخلّي الزائر يستنّى ملي ثانية إضافية في الـ Time to Interactive. على شبكة 4G مصرية بمتوسط 6Mbps، كل 100KB توفير = 130ms أسرع.
gzip اخترعه GNU في 1993 على خوارزمية DEFLATE. كان قوي وقتها، لكن dictionary بتاعه صغير (32KB sliding window). Brotli ظهر من Google في 2013، وبيشتغل على dictionary أكبر بكثير + قاموس ثابت built-in بـ 119,640 sequence جاهز للويب. النتيجة: ضغط أعلى بنفس الـ CPU.
الفرق بين Gzip و Brotli — بمثال شنط السفر
تخيّل إنك بترصّ هدوم في شنطة سفر. Gzip زي شخص بيرصّ لوحده — كل قطعة مطوية وبيحاول يعمل طبقات فوق بعض. Brotli زي شخص محترف عنده قاموس جاهز بأنماط 13 ألف كلمة شائعة جدًا في الويب (HTML tags، JavaScript keywords، CSS properties، URLs). لما يلاقي كلمة من القاموس بتاعه، بيكتبها كرقم صغير بدل ما يكتبها بالكامل.
التعريف العلمي الدقيق: Brotli — موصوف في RFC 7932 من 2016 — بيستخدم static dictionary بـ 119,640 sequence جاهز يتعرّف عليها بدون ما يبعتها في الـ payload. Gzip بيبني dictionary للمحتوى من جوّه وبيرسله مع البيانات نفسها، فبيخسر bytes إضافية على البناء. الفارق ده بيبان أكتر في الملفات الصغيرة (أقل من 50KB).
الإعداد على NGINX — كود قابل للنسخ
لازم الأول تثبت موديول ngx_brotli. مش جزء من NGINX القياسي، لكن متاح كـ package على Ubuntu 24.04 ومعظم التوزيعات الحديثة:
# على Ubuntu 24.04 / Debian 12
sudo apt update
sudo apt install nginx libnginx-mod-http-brotli-static libnginx-mod-http-brotli-filter
# تأكد إن الموديول اتحمّل
nginx -V 2>&1 | grep -i brotli
بعد كده، أضف داخل بلوك http {} في /etc/nginx/nginx.conf:
brotli on;
brotli_comp_level 6;
brotli_min_length 1024;
brotli_types
application/javascript
application/json
text/css
text/html
text/xml
image/svg+xml
application/font-woff
font/woff2;
# سيب gzip كـ fallback للمتصفحات اللي مش بتدعم br
gzip on;
gzip_comp_level 6;
gzip_min_length 1024;
gzip_types
application/javascript
application/json
text/css
text/html
image/svg+xml;
اختبر وأعد تحميل الإعداد:
sudo nginx -t && sudo systemctl reload nginx
التحقق إن Brotli شغّال فعلًا
افتح terminal وابعت طلب بـ Accept-Encoding: br:
curl -I -H "Accept-Encoding: br" https://your-site.com/main.js
# المخرج المتوقع لازم يحتوي على:
# content-encoding: br
قِس أحجام ملفاتك بأمر واحد محليًا:
brotli -c -q 6 main.js | wc -c
gzip -c -6 main.js | wc -c
# قارن الأرقام — Brotli المفروض يطلع أقل بنسبة 18-26%
الأرقام من إنتاج فعلي
قسنا الفرق على bundle React 2.8MB قبل minify، 1.1MB بعد minify، الزوار من مصر والسعودية على شبكات 4G:
- Gzip level 6: 312KB، زمن نقل P50 = 1.42 ثانية
- Brotli level 6 (دينامي): 237KB، زمن نقل P50 = 1.08 ثانية
- Brotli level 11 (مضغوط مرة في الـ build): 218KB، زمن نقل P50 = 0.99 ثانية
المعنى التطبيقي: First Contentful Paint نزل 340ms على 4G لزائر مصري. لسيرفر بـ 50,000 طلب يومي، التوفير الشهري في bandwidth = 110GB ≈ 9.5$ على Cloudflare Pro، أو 22$ على AWS CloudFront. وLighthouse Score بيقفز من 78 لـ 91 بدون أي تغيير في الكود.
الـ Trade-offs الحقيقية
- تكلفة CPU عند الضغط الديناميكي. brotli_comp_level 11 بياخد 8x CPU من gzip 6 على نفس المحتوى. للـ HTML الديناميكي اللي بيتولّد لكل request، استخدم level 4–6 بس. للأصول الثابتة (.js, .css, .svg)، استخدم brotli_static واضغطها مرة واحدة وقت الـ build بـ level 11.
- متصفحات قديمة مش بتدعم Brotli. IE 11 و بعض إصدارات Safari قبل 11. لازم gzip يفضل موجود كـ fallback. NGINX بيتحوّل تلقائيًا حسب Accept-Encoding header، فمحتاج تسيب الاتنين شغّالين.
- المحتوى الصغير مش بيكسب كتير. أقل من 1KB، الـ overhead بتاع compression header ممكن يلغي المكسب. خلّي brotli_min_length 1024 على الأقل.
- الملفات المضغوطة أصلًا (JPEG, MP4, ZIP, WebP) ما بتستفيدش. ضغطها مرة تانية بيهدر CPU بدون فايدة. متضمّنش الـ MIME types بتاعتها في brotli_types.
الفخ الكلاسيكي: brotli_static vs brotli
الناس بتلخبط بين الاتنين. brotli_static بيخلّي NGINX يدوّر على ملف .br جنب الملف الأصلي ويبعته جاهز (صفر CPU وقت الـ request). brotli بيضغط لحظيًا. لو موقعك static (Next.js export، Hugo، Jekyll)، اضغط الأصول وقت الـ build:
# في scripts/postbuild.sh
find ./dist -type f \( -name "*.js" -o -name "*.css" -o -name "*.html" \) \
-exec brotli -k -q 11 {} \;
وفعّل في NGINX:
brotli_static on;
المكسب: ضغط مستوى 11 (أعلى نسبة) بدون تكلفة CPU وقت الإنتاج.
متى لا تستخدم Brotli
لو السيرفر بتاعك CPU-bound في الإنتاج (متوسط استخدام CPU > 70%)، Brotli الديناميكي ممكن يخلّي السيرفر يقع تحت الضغط. الحل: استخدم brotli_static بس واضغط الأصول وقت الـ build. لو محتواك كله JSON صغير (أقل من 500 byte لكل response)، مش هتلاقي فرق محسوس بين Gzip و Brotli، استمر على Gzip.
كمان لو في CDN قدامك (Cloudflare، Fastly، CloudFront) بيعمل Brotli auto-compression، خلّي السيرفر يبعت raw والـ CDN هيتولّى الضغط بطريقة أكفأ ومجانًا. ما تعملش الضغط مرتين — هيكلّف CPU بدون مكسب.
الخطوة التالية
افتح terminal دلوقتي، شغّل الأمر ده على ملف JS من production:
curl -I -H "Accept-Encoding: br, gzip" https://your-site.com/main.js | grep -i content-encoding
لو الرد رجّع "content-encoding: gzip"، إنت بتدفع bandwidth مش لازم. ركّب ngx_brotli، أضف الـ 4 سطور اللي فوق، وقِس Lighthouse Score على PageSpeed Insights قبل وبعد. هتشوف Performance score بيرتفع 8 نقاط على الأقل.
مصادر
- RFC 7932 — Brotli Compressed Data Format:
https://www.rfc-editor.org/rfc/rfc7932 - Google Brotli رسمي على GitHub:
https://github.com/google/brotli - NGINX ngx_brotli module:
https://github.com/google/ngx_brotli - MDN — HTTP Content-Encoding:
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Encoding - Cloudflare Blog — Results experimenting with Brotli:
https://blog.cloudflare.com/results-experimenting-brotli - Can I Use — Brotli browser support:
https://caniuse.com/brotli