جاري التحميل...
جاري التحميل...
جلب البيانات، Suspense، loading.js، streaming المحتوى
واحدة من أكبر ميزات مكونات الخادم في Next.js هي القدرة على جلب البيانات مباشرة داخل المكون بدون أي hooks مثل useEffect.
تصبح الدالة async وتستخدم await مباشرة. لا يوجد useEffect، لا يوجد useState، لا تحتاج حالات تحميل.
Next.js يحسّن fetch() الأصلي بميزات التخزين المؤقت والتحقق من الصلابة.
force-cache — الافتراضي للمكونات الثابتة: يخزّن إلى أجل غير مسمى.no-store — لا يخزّن أبداً: يجلب بيانات طازجة في كل طلب.next: { revalidate: seconds } — يحدّث التخزين المؤقت بعد الثواني المحددة (ISR).next: { tags: [...] } — وسوم للتحقق المستهدف.البث يسمح لـ Next.js بإرسال أجزاء الصفحة تدريجياً. استخدم Suspense من React للف المكونات البطيئة.
تظهر المكونات السريعة فوراً بينما تعرض المكونات البطيئة حالة تحميل بديلة.
Next.js يوفر ملف loading.js لكل مجلد يعرض تلقائياً أثناء تحميل الصفحة.
استخدم Promise.all() أو Promise.allSettled() لجلب مصادر بيانات متعددة بالتوازي بدلاً من التسلسلي.
ما هي الميزة الرئيسية لجلب البيانات في مكونات الخادم؟
ما هو الغرض الرئيسي من استخدام Suspense مع جلب البيانات؟
أي خيار fetch() يحدّث البيانات كل 5 دقائق؟
أنشئ لوحة تحكم تستخدم البث لعرض المحتوى التدريجي مع Suspense و loading.js.
الحل:
// الحل الكامل
// 1. ملف app/dashboard/loading.js
export default function DashboardLoading() {
return (
<div className="p-8">
<div className="animate-pulse space-y-6">
<div className="h-8 bg-gray-200 rounded w-1/3"></div>
<div className="grid grid-cols-3 gap-4">
<div className="h-24 bg-gray-200 rounded-xl"></div>
<div className="h-24 bg-gray-200 rounded-xl"></div>
<div className="h-24 bg-gray-200 rounded-xl"></div>
</div>
<div className="space-y-3">
<div className="h-12 bg-gray-200 rounded"></div>
<div className="h-12 bg-gray-200 rounded"></div>
<div className="h-12 bg-gray-200 rounded"></div>
</div>
</div>
</div>
);
}
// 2. ملف app/dashboard/page.js
import { Suspense } from "react";
async function Stats() {
const res = await fetch("https://api.example.com/stats", {
cache: "no-store",
});
const stats = await res.json();
return (
<div className="grid grid-cols-3 gap-4">
<div className="p-6 bg-blue-50 rounded-xl">
<p className="text-sm text-blue-600">المستخدمون</p>
<p className="text-3xl font-bold">{stats.users}</p>
</div>
<div className="p-6 bg-green-50 rounded-xl">
<p className="text-sm text-green-600">المبيعات</p>
<p className="text-3xl font-bold">{stats.sales}</p>
</div>
<div className="p-6 bg-purple-50 rounded-xl">
<p className="text-sm text-purple-600">الطلبات</p>
<p className="text-3xl font-bold">{stats.orders}</p>
</div>
</div>
);
}
async function Activity() {
const res = await fetch("https://api.example.com/activity", {
cache: "no-store",
});
const activities = await res.json();
return (
<div className="space-y-3">
{activities.map((a) => (
<div key={a.id} className="flex items-center gap-3 p-3 bg-gray-50 rounded-lg">
<span className="text-2xl">{a.icon}</span>
<div>
<p className="font-medium">{a.title}</p>
<p className="text-sm text-gray-500">{a.time}</p>
</div>
</div>
))}
</div>
);
}
function StatsSkeleton() {
return (
<div className="grid grid-cols-3 gap-4">
{[1, 2, 3].map((i) => (
<div key={i} className="animate-pulse p-6 bg-gray-100 rounded-xl">
<div className="h-4 bg-gray-300 rounded w-1/2 mb-2"></div>
<div className="h-8 bg-gray-300 rounded w-3/4"></div>
</div>
))}
</div>
);
}
function ActivitySkeleton() {
return (
<div className="animate-pulse space-y-3">
{[1, 2, 3, 4, 5].map((i) => (
<div key={i} className="h-14 bg-gray-100 rounded-lg"></div>
))}
</div>
);
}
export default function DashboardPage() {
return (
<div className="p-8">
<h1 className="text-3xl font-bold mb-8">لوحة التحكم</h1>
<section className="mb-8">
<h2 className="text-xl font-semibold mb-4">الإحصائيات</h2>
<Suspense fallback={<StatsSkeleton />}>
<Stats />
</Suspense>
</section>
<section>
<h2 className="text-xl font-semibold mb-4">آخر النشاطات</h2>
<Suspense fallback={<ActivitySkeleton />}>
<Activity />
</Suspense>
</section>
</div>
);
}جلب البيانات في Server Component:
// بسيط: async/await مباشرة
async function Page() {
const res = await fetch("/api/data");
const data = await res.json();
return <div>{data.name}</div>;
}
// مع التخزين المؤقت
const res = await fetch("/api/data", {
next: { revalidate: 3600 }
});
// بدون تخزين مؤقت
const res = await fetch("/api/data", {
cache: "no-store"
});
// مع الوسوم
const res = await fetch("/api/data", {
next: { tags: ["data"] }
});الجلب المتوازي:
// Promise.all - يفشل إذا فشل أحدهما
const [a, b] = await Promise.all([
fetch("/api/a"),
fetch("/api/b"),
]);
// Promise.allSettled - أكثر أماناً
const results = await Promise.allSettled([
fetch("/api/a"),
fetch("/api/b"),
]);البث باستخدام Suspense:
import { Suspense } from "react";
async function Page() {
return (
<div>
<FastComponent />
<Suspense fallback={<Loading />}>
<SlowComponent />
</Suspense>
</div>
);
}loading.js:
// app/dashboard/loading.js
export default function Loading() {
return <p>جاري التحميل...</p>;
}
// يعرض تلقائياً لكل صفحة في dashboard/كيف تجلب البيانات في Next.js 13+؟
الإجابة:
استخدم async/await مباشرة في Server Components. لا need لـ useEffect.
ما هو streaming؟
الإجابة:
عرض أجزاء من الصفحة تدريجياً بدلاً من الانتظار للتحميل الكامل.
استخدم React Cache
لتجنب جلب البيانات نفسة عدة مرات في مكونات مختلفة