تحسين صور المنتج بـ AVIF وsrcset بدون تغيير التصميم
مستوى القارئ: متوسط
لو صفحة المنتج عندك جميلة بس أول صورة بتظهر بعد 4 ثواني، المقال ده هيخليك تنزل الـ LCP غالبًا للنصف بدون ما تغيّر التصميم.
المشكلة باختصار
اللي بيحصل فعلاً إن فريق التصميم يسلّم صورة hero مقاس 2400px، والموقع يرسلها لكل المستخدمين كما هي. مستخدم الموبايل يشوف نفس الملف الكبير، حتى لو الشاشة محتاجة عرض 390px فقط. الطريقة دي بتفشل في أول زيارة لأن المتصفح يدفع وقتًا كبيرًا في تنزيل الصورة وفك ضغطها قبل ظهور أهم عنصر في الصفحة.
الافتراض إن عندك صفحة منتج أو landing page فيها صورة رئيسية واحدة، وحجم الزيارات أقل من 200K زيارة يوميًا. المثال التالي مبني على صورة JPEG حجمها 920KB. بعد التحويل إلى WebP وAVIF مع أحجام متعددة، نزل النقل الفعلي على موبايل متوسط من 920KB إلى 280KB، وانخفض LCP من 4.1 ثانية إلى 1.6 ثانية في اختبار Lighthouse محلي على شبكة Fast 3G.
مثال بسيط قبل التعريف العلمي
ركز في المثال ده. عندك نفس صورة الحذاء في صفحة منتج. لو المستخدم فاتح من لابتوب، محتاج نسخة 1200px. لو فاتح من موبايل، غالبًا 640px تكفي. بدل ما تبعت نفس الصورة الكبيرة للجميع، بتجهّز 3 أحجام و3 صيغ. المتصفح يختار الأنسب حسب الشاشة والدعم.
التعريف الأدق: تحسين الصور هنا معناه تقليل البايتات المطلوبة لرسم أكبر عنصر مرئي في أول viewport، مع الحفاظ على جودة مقبولة. AVIF وWebP صيغ حديثة تضغط أفضل من JPEG غالبًا، ووسم picture يسمح بتقديم fallback لو المتصفح لا يدعم AVIF. مصدر MDN يوضح أن AVIF وWebP من الصيغ المناسبة للأداء، وشرح web.dev يوضح نمط picture مع AVIF ثم WebP ثم JPEG.
الخطوات العملية بسكربت sharp
أفضل طريقة هنا إنك تعمل التحويل في build step، مش وقت الطلب. كده بتكسب سرعة ثابتة وتخسر شوية وقت في الـ build ومساحة تخزين إضافية. ثبّت sharp وجهّز مجلد للصور الناتجة:
npm install sharp
mkdir -p public/generated-imagesبعد كده استخدم السكربت ده. بيطلع AVIF وWebP وJPEG بثلاثة أحجام من صورة واحدة:
// scripts/build-product-images.mjs
import sharp from 'sharp';
import { mkdir } from 'node:fs/promises';
const input = 'assets/product-hero.jpg';
const outputDir = 'public/generated-images';
const widths = [640, 960, 1200];
const formats = [
{ ext: 'avif', options: { quality: 50 } },
{ ext: 'webp', options: { quality: 72 } },
{ ext: 'jpg', options: { quality: 78, mozjpeg: true } },
];
await mkdir(outputDir, { recursive: true });
for (const width of widths) {
for (const format of formats) {
await sharp(input)
.resize({ width, withoutEnlargement: true })
.toFormat(format.ext === 'jpg' ? 'jpeg' : format.ext, format.options)
.toFile(`${outputDir}/product-hero-${width}.${format.ext}`);
}
}