قلّل JavaScript الأولي في Next.js بدون كسر الواجهة
مستوى القارئ: متوسط
هتخرج من المقال بخطة عملية تقلّل JavaScript اللي المستخدم بيحمله في أول زيارة، بدل ما ترمي كل المودالات والخرائط والرسوم في أول bundle.
المشكلة باختصار
لو صفحة Next.js بتفتح بسرعة ظاهريًا لكن أول click بيتأخر، غالبًا المشكلة مش في CSS ولا السيرفر. اللي بيحصل فعلاً إن المتصفح بيحمّل JavaScript كتير، ثم يفسّره وينفذه على الـ main thread. في تطبيق SaaS متوسط، إضافة chart library وmap component ومحرر rich text في الصفحة الرئيسية ممكن تزود الحزمة الأولية من 210KB إلى 420KB gzip. الرقم ده تقديري، لكنه قريب من اللي بتشوفه في مشاريع فيها dependencies ثقيلة.
الطريقة الشائعة الغلط: تحاول تضغط الصور أو تزود CDN وتسيب JavaScript كما هو. ده يساعد في assets، لكنه مش هيحل حجب الـ main thread. أفضل طريقة هنا إنك تقيس الأول، ثم تنقل المكونات الثقيلة التي لا تظهر فورًا إلى تحميل عند الطلب.
الفكرة: حمّل ما يظهر الآن فقط
ركز: code splitting مش معناه إنك تحذف كود. معناه إنك تغيّر توقيت تحميله. مثال بسيط: صفحة dashboard فيها جدول ظاهر فورًا، وزر يفتح مودال فيه رسم بياني كبير. المستخدم لا يحتاج مكتبة الرسم قبل ما يفتح المودال. بدل ما تدخل المكتبة في أول bundle، خلّيها chunk منفصل.
Next.js يوفّر next/dynamic لتحميل Client Components أو libraries عند الحاجة. توثيق Next.js يوضح إن lazy loading يقلل JavaScript المطلوب لرسم route في البداية، ويدعم نفس النمط في App Router وPages Router. وتوثيق web.dev يشرح إن JavaScript الكبير يزود وقت التحليل والتنفيذ، وقد يرفع تأخير التفاعل لأن الـ main thread يبقى مشغول.
مثال تنفيذي: مودال رسوم ثقيل
الافتراض إن عندك صفحة dashboard، والـ chart لا يظهر إلا بعد ضغط المستخدم على زر. قبل التحسين، الكود ممكن يبقى بالشكل ده:
import RevenueChart from "@/components/revenue-chart";
export default function DashboardPage() {
return (
Revenue
);
}لو RevenueChart يعتمد على مكتبة زي recharts أو chart.js، فالمكتبة تدخل غالبًا في الحزمة الأولية. البديل:
"use client";
import { useState } from "react";
import dynamic from "next/dynamic";
const RevenueChart = dynamic(() => import("@/components/revenue-chart"), {
loading: () => ,
ssr: false,
});
export function RevenuePanel() {
const [open, setOpen] = useState(false);
return (
);
}