React
قراءة في 7 دقائقتحسين أداء Next.js بالأدلة لا بالتخمين
استخدم مؤشرات الويب الأساسية وتحليل الحزم وأحجام الصور ومراجعة حدود العميل لاكتشاف الاختناقات وقياس أثر التغيير.

يجب أن يعتمد تحسين الأداء على الأدلة. أنشئ أولًا خط أساس قابلًا للتكرار، ثم حدد ما إذا كان الاختناق في الشبكة أو استجابة الخادم أو تحميل الصور والخطوط أو تنفيذ JavaScript أو ثبات التخطيط. يغطي هذا الدليل تقنيات Next.js العملية ومتى تفيد ومتى لا تفيد.
١. افهم المؤشرات قبل التحسين
يقيس LCP وقت ظهور أكبر عنصر محتوى مرئي، ويعكس INP استجابة التفاعلات الفعلية، ويقيس CLS تحركات التخطيط غير المتوقعة. يساعد TTFB في تشخيص الانتظار حتى أول بايت، بينما لا يكشف حجم الحزمة وحده مقدار JavaScript المنفذ. درجة Lighthouse مفيدة، لكن بيانات المستخدمين ومسار الاستخدام مهمان أيضًا.
٢. أنشئ خط أساس موثوقًا
قس نسخة الإنتاج مع المسار وحجم العرض وملف الجهاز والشبكة وإصدار المتصفح نفسها. كرر الاختبار وقارن الوسيط لأن القياسات المختبرية تتغير. استخدم Network لاكتشاف الموارد الكبيرة أو المتأخرة، وPerformance لفحص المهام الطويلة، وLighthouse للتدقيق القابل للتكرار. سجل الفرضية والنتيجة.
# Compare like-for-like runs
1. Run a production build, not only `next dev`.
2. Record route, viewport, device profile, network, and build hash.
3. Run Lighthouse 3-5 times and compare the median, not the best score.
4. Inspect LCP element, long tasks, layout shifts, and network waterfall.
5. Change one bottleneck, repeat the same test, and document the result.٣. تقديم خطوط WOFF2 محلية باستخدام next/font/local
يعد WOFF2 غالبًا صيغة جيدة لخطوط الويب بسبب ضغطه الفعال. استخدم مجموعة محلية فرعية للأحرف والأوزان المطلوبة فقط؛ لا تحمل جميع الأوزان وعائلة متعددة اللغات كاملة إن كانت الصفحة تحتاج وزنَين فقط. يستضيف next/font/local الخط محليًا ويولد CSS أثناء البناء. يبقي display: "swap" النص ظاهرًا أثناء تحميل الخط، ويجب قصر preload على الخطوط المطلوبة مبكرًا. استخدم أسماء الملفات والأوزان الفعلية.
import localFont from "next/font/local";
const siteFont = localFont({
src: [
{ path: "./fonts/Inter-Regular.woff2", weight: "400", style: "normal" },
{ path: "./fonts/Inter-Medium.woff2", weight: "500", style: "normal" },
{ path: "./fonts/Inter-Bold.woff2", weight: "700", style: "normal" },
],
display: "swap",
variable: "--font-site",
preload: true,
});
export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return <html lang="en" className={siteFont.variable}><body>{children}</body></html>;
}٤. اربط الخط بمتغيرات التصميم
عرّف متغير CSS مرة واحدة في الجذر وأعد استخدامه في نظام التصميم. إذا كنت تستخدم Tailwind CSS v4 فاربطه عبر @theme inline كما في المثال. تحقق من أن ملف الخط يحتوي على محارف الفارسية أو العربية عند تفعيل تلك اللغات، لأن المحارف الناقصة تسبب خطًا بديلًا وتغير قياسات النص.
@theme inline {
--font-sans: var(--font-site);
}
body {
font-family: var(--font-site), system-ui, sans-serif;
}٥. إعداد وتشغيل محلل الحزمة
ثبت @next/bundle-analyzer كاعتماد تطوير ولف إعداد Next.js به. يجعل متغير البيئة التحليل اختياريًا حتى لا تنتج كل عملية بناء تقارير. يجعل cross-env الأمر قابلًا للنقل على Windows، أو استخدم صيغة البيئة الخاصة بصدفتك. شغّل المحلل على بناء الإنتاج لا خادم التطوير.
import type { NextConfig } from "next";
import bundleAnalyzer from "@next/bundle-analyzer";
const withBundleAnalyzer = bundleAnalyzer({
enabled: process.env.ANALYZE === "true",
});
const nextConfig: NextConfig = {};
export default withBundleAnalyzer(nextConfig);{
"scripts": {
"dev": "next dev",
"build": "next build",
"analyze": "cross-env ANALYZE=true next build"
}
}
# Install:
npm install -D @next/bundle-analyzer cross-env
# Run on Windows, macOS, or Linux:
npm run analyze٦. كيف تقرأ نتائج المحلل
يكون التقرير غالبًا خريطة مستطيلات؛ يمثل كل مستطيل وحدة ويقارب حجمه مساهمتها في الحزمة. ابحث عن مكتبات كبيرة غير متوقعة وإصدارات مكررة وبيانات لغات وحزم أيقونات واعتماديات يستوردها Client Component. تقرير العميل مهم خصوصًا لـ JavaScript المتصفح. تتبع سلسلة الاستيراد لمعرفة المكوّن الذي جلب الاعتمادية، ولا تحذف مكتبة لمجرد كبر حجمها قبل التحقق من الحاجة وإمكانية استيراد جزء أصغر.
٧. تقسيم الميزات الثقيلة بالاستيراد الديناميكي
قد لا يحتاج المستخدم إلى محرر الرسوم أو النصوص الغنية أو الخريطة أو عارض PDF حتى يفتح لوحة. يمكن لـ next/dynamic فصل الكود في chunk مستقل. يقلل ذلك التنزيل الأولي فقط إذا لم تكن الميزة مطلوبة فورًا. أضف حالة تحميل مفيدة وافحص تسلسل الشبكة؛ فقد تضر القطع الصغيرة الكثيرة أو تأخر رسم ظاهر أعلى الصفحة بالتجربة.
import dynamic from "next/dynamic";
const HeavyChart = dynamic(() => import("./heavy-chart"), {
loading: () => <div aria-busy="true">Loading chart…</div>,
});
export function AnalyticsPanel() {
return <section><h2>Analytics</h2><HeavyChart /></section>;
}٨. useMemo وuseCallback وmemo: قِس قبل إضافتها
يخزن useMemo قيمة محسوبة بين عمليات العرض ما دامت التبعيات ثابتة؛ ويحافظ useCallback على مرجع دالة ثابت؛ ويمكن لـ memo تخطي عرض الطفل عندما لا تتغير الخصائص. قد يستحق تصفية قائمة كبيرة التخزين المؤقت في المثال، ويساعد رد النداء الثابت الصف المحفوظ. للقائمة الصغيرة قد تفوق الكلفة الفائدة. لا تقلل هذه الأدوات حجم الحزمة المنزلة ولا تستبدل القياس.
"use client";
import { memo, useCallback, useMemo, useState } from "react";
type Product = { id: string; name: string; price: number };
const ProductRow = memo(function ProductRow({ product, onSelect }: {
product: Product; onSelect: (id: string) => void;
}) {
return <button onClick={() => onSelect(product.id)}>{product.name} — {product.price}</button>;
});
export function ProductList({ products }: { products: Product[] }) {
const [query, setQuery] = useState("");
const filtered = useMemo(() => {
const normalized = query.trim().toLowerCase();
return products.filter((p) => p.name.toLowerCase().includes(normalized));
}, [products, query]);
const handleSelect = useCallback((id: string) => {
console.log("selected", id);
}, []);
return <>
<input value={query} onChange={(e) => setQuery(e.target.value)} aria-label="Search products" />
{filtered.map((product) => <ProductRow key={product.id} product={product} onSelect={handleSelect} />)}
</>;
}٩. الصور وLCP وثبات التخطيط
استخدم next/image مع أبعاد معروفة أو نسبة ثابتة ليحجز المتصفح المساحة. اضبط sizes وفق العرض الفعلي وإلا قد ينزل المستخدم صورة أكبر من اللازم. أعط الأولوية لصورة LCP المحتملة فقط. استخدم صيغًا حديثة عند الحاجة وافحص الشبكة لتأكيد أن الصور هي الاختناق قبل تغيير الصيغة.
import Image from "next/image";
export function Hero() {
return <Image
src="/images/hero.webp"
alt="Product dashboard preview"
width={1600}
height={900}
priority
sizes="(max-width: 768px) 100vw, 50vw"
/>;
}١٠. ترتيب عملي للتحسين
- أزل حدود العميل غير الضرورية والاعتماديات الكبيرة.
- حسن صورة LCP واستضف أوزان الخط المطلوبة فقط.
- استخدم العرض الثابت أو إعادة التحقق للمحتوى العام عندما تسمح الحداثة.
- قسّم الأدوات الثقيلة الاختيارية ولا تؤخر المحتوى المهم أعلى الصفحة.
- استخدم useMemo/useCallback فقط بعد قياس اختناق في الحساب أو العرض.
- كرر اختبار الإنتاج نفسه ووثق النتيجة قبل وبعد.