لماذا يتباطأ تطبيق Next.js الخاص بك: 7 حلول فعالة لتحسين الأداء
يستغرق تطبيق Next.js الخاص بك أكثر من 4 ثوانٍ للتحميل. وتبلغ درجة Lighthouse 60. ويقوم المستخدمون بمغادرة الموقع. إليك 7 حلول ملموسة مصحوبة بقياسات قبل وبعد — لا نصائح غامضة، بل كود برمجي فقط.
1. أنت ترسل 500 كيلوبايت من جافا سكريبت إلى المتصفح
قم بتشغيل الأمر npx @next/bundle-analyzer وتحقق من محتويات حزمة العميل الخاصة بك. العناصر التي تسبب زيادة الحجم عادةً: moment.js (300 كيلوبايت → استبدلها بـ date-fns)، lodash (70 كيلوبايت → استورد الوظائف المحددة)، مكتبات الرموز (استيراد جميع الرموز بدلاً من الرموز المحددة).
// BAD: imports entire library (70KB)
import _ from 'lodash';
const result = _.debounce(fn, 300);
// GOOD: imports only what you need (2KB)
import debounce from 'lodash/debounce';
const result = debounce(fn, 300);
// BAD: imports all 1000+ icons (200KB)
import { ArrowRight } from 'lucide-react';
// GOOD: tree-shakeable import
import ArrowRight from 'lucide-react/dist/esm/icons/arrow-right';2. كل مكون هو "استخدام العميل"
إذا قمت بإضافة "use client" إلى كل مكون، فأنت بذلك ترسل كل جافا سكريبت إلى المتصفح. أما مكونات الخادم (Server Components) فتُعرض على الخادم وترسل HTML — بدون أي جافا سكريبت. استخدم "use client" فقط للمكونات التي تحتاج إلى تفاعلية (onClick، useState، useEffect).
// Server Component (default) - sends HTML, zero JS
async function BlogList() {
const posts = await db.posts.findMany();
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
{/* Only the button is client-side */}
<LikeButton postId={post.id} />
</article>
))}
</div>
);
}
// Client Component - only the interactive part
'use client';
function LikeButton({ postId }: { postId: string }) {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>Like</button>;
}3. الصور التي لا تحتوي على علامة `next/image`
تقوم علامات <img> الأولية بتحميل الصورة بالحجم الكامل. بينما تقوم علامة next/image تلقائيًا بتغيير حجم الصورة وتحويلها إلى صيغة WebP/AVIF، بالإضافة إلى التحميل المؤجل. وهذا وحده كفيل بتقليل مؤشر LCP بنسبة 40-60%.
// BAD: loads full 4MB image
<img src="/hero.jpg" />
// GOOD: Next.js optimizes, lazy loads, serves WebP
import Image from 'next/image';
<Image
src="/hero.jpg"
width={1200}
height={600}
priority // for above-the-fold images
alt="Hero image"
/>4. استرداد البيانات على جانب العميل في الحالات التي يمكن فيها استخدام SSR
إذا كنت تستخدم useEffect + fetch لتحميل البيانات عند كل زيارة للصفحة، فإنك تجعل المستخدم ينتظر ما يلي: تنزيل HTML → تحليل JS → تهيئة React → طلب fetch → العرض. أما مع مكونات الخادم (Server Components)، فيتم جلب البيانات على الخادم ويحصل المستخدم على HTML الكامل على الفور.
5. عدم وجود رؤوس التخزين المؤقت في الموارد الثابتة
الملفات الثابتة في Next.js الموجودة ضمن المجلد /_next/static/ هي ملفات تم تجزئتها حسب المحتوى وهي غير قابلة للتعديل. يجب أن يقوم خادم nginx بتخزينها مؤقتًا بشكل مكثف:
# Nginx: cache Next.js static files for 1 year
location /_next/static {
proxy_pass http://frontend:3000;
add_header Cache-Control "public, max-age=31536000, immutable";
}6. عدم استخدام React.lazy للمكونات الثقيلة
تعد المكونات مثل محررات النص المنسق والرسوم البيانية والخرائط كبيرة الحجم. قم بتحميلها بشكل مؤجل حتى لا تعيق عملية العرض الأولي:
import dynamic from 'next/dynamic';
// Only loads when component is rendered
const Chart = dynamic(() => import('@/components/Chart'), {
loading: () => <div className="h-64 animate-pulse bg-gray-800" />,
ssr: false, // chart library needs window
});7. عدم تحسين الخطوط
تتسبب الخطوط المخصصة في حدوث تغير في التخطيط (CLS) عندما يتم تحميلها متأخراً. استخدم next/font لاستضافة الخطوط بنفسك وتحميلها مسبقاً:
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap', // show fallback font until loaded
});
export default function Layout({ children }) {
return <body className={inter.className}>{children}</body>;
}النتائج المتوقعة
| النظام المتري | قبل | بعد إجراء جميع الإصلاحات |
|---|---|---|
| أداء لايت هاوس | 58 | 94 |
| أول رسم محتوى | 2.8 ثانية | 0.8 ثانية |
| أكبر عنصر يتم عرضه | 4.2 ثانية | 1.4 ثانية |
| حزمة JS الكاملة | 520 كيلوبايت | 180 كيلوبايت |
| التغير التراكمي في التخطيط | 0.25 | 0.02 |
