أطر العمل
Next.js في الإنتاج: المصادقة والأداء والنشر
دليل خبير لأخذ تطبيق موجّه تطبيق Next.js إلى الإنتاج مع TypeScript — المصادقة بطبقات (الوسيط، وطبقة الوصول للبيانات، والجلسات بكوكيز httpOnly)، ومتغيّرات البيئة وإبقاء الأسرار على الخادم، والأداء بـ next/image وnext/font والاستيراد الديناميكيّ وتحليل الحزمة، ومعالجة الأخطاء بـ error.tsx وnot-found، والنشر على Vercel أو ذاتيًّا بخرج standalone — مع أخطاء شائعة وتمارين.
بناء الميزات شيء؛ وإطلاق تطبيق Next.js آمنٍ وسريعٍ وموثوقٍ في الإنتاج مهارةٌ أخرى كليًّا. تغطّي هذه التدوينة الأخيرة من المسار المتقدّم الهمومَ التي لا تهمّ إلا حين يظهر مستخدمون حقيقيّون: المصادقة بأمان، وإبقاء الأسرار خارج العميل، وعصر الأداء من مدمجات الإطار، ومعالجة الأخطاء بلطف، والنشر — على Vercel أو خادمك الخاصّ. تبني على العرض والتخزين، وأفعال الخادم، والتوجيه المتقدّم. كل شيءٍ بـ TypeScript، وNext.js 15+.
عقلية الإنتاج: في موجّه التطبيق، يعمل الكود في مكانين مختلفين جدًّا — الخادم (موثوق، لديه أسرار، قرب البيانات) والعميل (عامّ، عدائيّ، يُرسَل لكل مستخدم). كل همٍّ إنتاجيّ — المصادقة، والأسرار، والأداء، والأخطاء — يعود إلى وضع العمل الصحيح في المكان الصحيح وعدم الثقة بالعميل أبدًا. أصِب حدّ الخادم/العميل فيصير الإنتاج معظمه انحدارًا سهلًا.
المصادقة بطبقات
أهمّ موضوعٍ إنتاجيّ، وأكثرها خطأً. المصادقة الآمنة في موجّه التطبيق بطبقات، لا فحصًا واحدًا:
- الوسيط — بوّابةٌ سريعة تفاؤلية. اقرأ كوكي الجلسة وأعِد توجيه غير المصادَقين الواضحين بعيدًا عن المناطق المحميّة. يعمل على كل طلبٍ مطابق، فأبقِه على فحص كوكيٍ رخيص — لا تمسّ قاعدة بياناتٍ هنا.
- طبقة الوصول للبيانات (DAL) — التفويض الحقيقيّ، قرب البيانات. وحدةٌ صغيرة تمرّ عبرها كل قراءة/كتابة بياناتٍ، تتحقّق من الجلسة وتفحص الصلاحيات قبل إرجاع أيّ شيء. هذه مصدر حقيقتك.
- تحقّق قرب الاستخدام — في مكوّنات الخادم وأفعاله، استدعِ DAL لتأكيد المستخدم قبيل قراءة بياناته أو تعديلها — لا تفترض أبدًا أن الوسيط عالجها.
// lib/dal.ts — البوّابة الحقيقية، تُستدعى حيثما تُقرأ البيانات
import "server-only";
import { cookies } from "next/headers";
import { cache } from "react";
export const getCurrentUser = cache(async () => {
const token = (await cookies()).get("session")?.value;
if (!token) return null;
return verifySession(token); // تحقّق من التوقيع/الانتهاء، وحمّل المستخدم
});
// في مكوّن خادمٍ أو فعل خادم:
const user = await getCurrentUser();
if (!user) redirect("/login");لماذا بطبقات؟ الوسيط وحده بوّابةٌ خشنة يمكن تجاوزها — وفعل الخادم نقطة نهايةٍ عامّة. التفويض الحقيقيّ يجب أن يعيش حيث تُمسّ البيانات. وتغليف الفحص بـ cache من React يزيل تكراره عبر الطلب الواحد.
الجلسات والكوكيز
الجلسة برهانٌ على أن المستخدم سجّل دخوله، مخزّنٌ في كوكي httpOnly كي لا تقرؤه JavaScript العميل (منعًا لسرقة الرمز عبر XSS). نموذجان: جلسة عديمة الحالة (JWT موقّع/مشفّر يحمل معرّف المستخدم) أو جلسة قاعدة بيانات (معرّفٌ مبهم يشير إلى سجلٍّ خادميٍّ تستطيع إبطاله). اضبط الكوكي من فعل خادمٍ بالرايات الصحيحة:
(await cookies()).set("session", token, {
httpOnly: true, // لا تقرؤه JS
secure: true, // HTTPS فقط
sameSite: "lax", // تخفيف CSRF
maxAge: 60 * 60 * 24 * 7,
path: "/",
});لأيّ شيءٍ يتجاوز مشروع تعلّم، تتولّى مكتبةٌ مصونة مثل Auth.js (NextAuth) المزوّدين وCSRF وإدارة الجلسات بصحّة — من السهل إصابة المصادقة بخطأٍ خفيّ، فلا تكتب التشفير يدويًّا.
متغيّرات البيئة والأسرار
القاعدة الأساسية: الأسرار لا تصل العميل أبدًا. في Next.js، متغيّر البيئة خادميٌّ إلا إن بُدئ بـ NEXT_PUBLIC_، الذي يُضمّنه في حزمة المتصفّح:
DATABASE_URL="postgres://…" # خادميّ — آمنٌ للأسرار
STRIPE_SECRET_KEY="sk_live_…" # خادميّ
NEXT_PUBLIC_ANALYTICS_ID="G-…" # يُرسَل للمتصفّح — قيمٌ عامّة فقطاقرأ أسرار الخادم فقط في مكوّنات الخادم، وأفعاله، ومعالِجات المسار، ووحدات lib — لا في ملفّ "use client" أبدًا، لأن كل ما يستورده ملفّ عميلٍ يُحزَّم ويُرسَل. ولجعل ذلك الضمان قابلًا للفرض، استورد حزمة server-only أعلى الوحدات التي يجب ألّا تصل العميل أبدًا؛ فتحوّل استيرادًا عميلًا عرضيًّا إلى خطأ بناء:
import "server-only"; // يفشل البناء إن استورد مكوّن عميلٍ هذا الملفّالأداء: استخدم المدمجات
يشحن Next.js تحسيناتٍ تتفوّق على أيّ شيءٍ يدويٍّ تقريبًا — استخدمها.
next/image — تحجيمٌ تلقائيّ، وصيغٌ حديثة (AVIF/WebP)، وتحميلٌ كسول، ومساحةٌ محجوزة لمنع انزياح التخطيط. أعطِ دائمًا width/height (أو fill)، وعلّم صور أعلى الطيّة بـ priority:
import Image from "next/image";
<Image src="/hero.jpg" alt="" width={1200} height={600} priority />;next/font — يستضيف الخطوط ذاتيًّا وقت البناء، مزيلًا طلبًا حاجبًا للعرض إلى Google وانزياح التخطيط الذي تسبّبه الخطوط:
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
// طبّق inter.className على <html> / <body>الاستيراد الديناميكيّ — أخرِج مكوّنات العميل الكبيرة أو التي تحت الطيّة من الحزمة الأولية بـ next/dynamic، فتُحمَّل فقط عند الحاجة:
import dynamic from "next/dynamic";
const Chart = dynamic(() => import("@/components/Chart")); // قطعة منفصلةوأكبر رافعةٍ بنيوية: أبقِ المكوّنات مكوّنات خادم وقلّل "use client" — فكل مكوّن عميلٍ يضيف JavaScript ينزّله المستخدم وينفّذه. قِس بـ @next/bundle-analyzer وراقب مؤشّرات الويب الأساسية (LCP، CLS، INP). النمط: اعرض على الخادم كل ما تستطيع، واشحن JS فقط للأوراق التفاعلية.
معالجة الأخطاء
تطبيقات الإنتاج تفشل؛ والسؤال هل يرى المستخدم شاشةً فارغة أم رسالةً لطيفة. تغطّي الملفّات الخاصّة لموجّه التطبيق هذا:
// app/dashboard/error.tsx — يلتقط أخطاء العرض في هذا المقطع (مكوّن عميل)
"use client";
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<div role="alert">
<p>Something went wrong.</p>
<button onClick={reset}>Try again</button>
</div>
);
}error.tsxحدّ خطأٍ لمقطع مساره، بـreset()لإعادة المحاولة. ويمكن أن يملك كل مقطعٍ متداخلٍ واحدًا، فيُحتوى الفشل بدل تعطيل التطبيق كله.global-error.tsxيلتقط الأخطاء في التخطيط الجذريّ نفسه (الملاذ الأخير).not-found.tsxيُعرَض لـnotFound()والمسارات غير المطابقة.
اقرِن هذه بخدمة مراقبة (Sentry ومثيلاتها) كي تعلم بأخطاء الإنتاج بدل سماعها من المستخدمين. سجّل أخطاء الخادم بسياقٍ كافٍ لإعادة إنتاجها، ولا تسرّب أبدًا آثار المكدّس أو الأسرار في رسالة خطأٍ تُعرَض للمستخدمين.
النشر
مساران، حسب التحكّم والكلفة.
Vercel — الخيار بلا تهيئةٍ من صانعي Next.js. ادفع إلى Git فيبني، ويخدم الأصول الساكنة من CDN، ويشغّل مكوّنات الخادم والدوال، ويصل طبقات التخزين وISR وتحسين الصور تلقائيًّا. لمعظم التطبيقات هذا أسرع طريقٍ للإنتاج.
الاستضافة الذاتية — تحكّمٌ كامل (خادمك الخاصّ، Docker، سحابةٌ أخرى). ابنِ بـخرج standalone كي تشحن خادم Node أدنى بالاعتماديات التي تستخدمها فقط:
// next.config.ts
const nextConfig = { output: "standalone" };
export default nextConfig;# ثم شغّل الخادم الناتج: node .next/standalone/server.jsللاستضافة الذاتية دقّةٌ إنتاجية تستحقّ التنبيه: ذاكرة البيانات وISR تُحفَظان إلى نظام الملفّات افتراضيًّا، فعبر نُسخٍ متعدّدة تحتاج تخزين ذاكرةٍ مشترك (أو معالِج ذاكرةٍ مخصّص) كي تكون إعادة التحقّق متّسقة — شيءٌ يديره Vercel لك. أيًّا اخترت، اضبط متغيّرات بيئة الإنتاج في المضيف (لا مُودَعةً في Git)، وشغّل next build لالتقاط أخطاء الأنواع والتدقيق قبل بلوغها المستخدمين.
الأخطاء الشائعة
- المصادقة في الوسيط فقط — إنه بوّابةٌ تفاؤلية يمكن تجاوزها؛ أجرِ التفويض الحقيقيّ في طبقة وصول بياناتٍ قرب البيانات، وتحقّق في كل مكوّن/فعل خادمٍ يمسّ بيانات المستخدم.
- قراءة الأسرار في مكوّن عميل — كل ما يستورده ملفّ
"use client"يُرسَل للمتصفّح. أبقِ الأسرار في كود الخادم؛ واحرُس بـserver-only. - بدء سرٍّ بـ
NEXT_PUBLIC_— هذا يُضمّنه في الحزمة العامّة. القيم العامّة حقًّا فقط تأخذ البادئة. <img>عاديّ و<link>خطوطٍ يدويّ — تخسر التحسين وتدعو انزياح التخطيط. استخدمnext/imageوnext/font.- تعليم صفحاتٍ كاملة
"use client"— يشحن JavaScript بلا داعٍ. أبقِ الصفحات معروضةً على الخادم؛ واعزل التفاعلية في أوراقٍ صغيرة. - بلا حدود أخطاء — رميةٌ غير معالَجة تُفرِغ الشاشة. أضِف
error.tsxلكل مقطعٍ وglobal-error.tsx. - استضافةٌ ذاتية بلا ذاكرةٍ مشتركة — نُسخٌ متعدّدة بذاكرة بياناتٍ محلّية تعطي إعادة تحقّقٍ غير متّسقة. هيّئ معالِج ذاكرةٍ مشترك.
- تخطّي
next buildمحلّيًّا — بناء الإنتاج يُظهِر أخطاء الأنواع، والتهيئة غير الصالحة، وانتهاكات حدّ الخادم/العميل التي يتسامح معها خادم التطوير.
تمارين
جرّب كلًّا قبل فتح الحلّ.
تمرين 1 — ضَع السرّ
لديك STRIPE_SECRET_KEY وNEXT_PUBLIC_MAPS_KEY عامّ. أيّهما يُقرأ في مكوّن "use client"، ولماذا؟
اعرض الحل
NEXT_PUBLIC_MAPS_KEY فقط. بادئة NEXT_PUBLIC_ تُضمّن المتغيّر في حزمة المتصفّح، فيصير مقروءًا على العميل (ولذا يجب أن يكون غير سرّ). أما STRIPE_SECRET_KEY فبلا بادئة، فيوجد على الخادم فقط — قراءته في مكوّن عميلٍ ستكون undefined في أحسن الأحوال وتسريبًا في أسوئها. اقرأه في فعل خادمٍ أو معالِج مسار.
تمرين 2 — أين ينتمي التفويض؟
لوحة تحكّمٍ محميّة بوسيطٍ يعيد توجيه المستخدمين بلا كوكي جلسة. هل هذا كافٍ لمنع مستخدمٍ من قراءة بيانات آخر عبر فعل خادم؟
اعرض الحل
لا. يفحص الوسيط فقط وجود كوكي جلسةٍ على المسارات المطابقة؛ ولا يتحقّق من الملكية، وفعل الخادم نقطة نهايةٍ عامّة يمكن بلوغها مباشرة. يجب أن يستدعي الفعل طبقة الوصول للبيانات ليصادق المستخدم ويؤكّد أنه يُسمَح له بالوصول إلى ذلك السجلّ تحديدًا — التفويض يعيش قرب البيانات، لا على الحافة فقط.
تمرين 3 — قلّص الحزمة
مكوّن رسمٍ بيانيٍّ ثقيل يُعرَض فقط حين ينقر المستخدم "التحليلات". كيف تبقيه خارج الحزمة الأولية؟
اعرض الحل
import dynamic from "next/dynamic";
const Chart = dynamic(() => import("@/components/Chart"));next/dynamic يقسّم المكوّن إلى قطعته الخاصّة التي تُحمَّل فقط حين يُعرَض Chart فعلًا، فيبقى تنزيل الصفحة الأوّليّ صغيرًا.
النموذج الذهني الذي تحتفظ به
Next.js الإنتاجيّ عن وضع العمل على الجانب الصحيح من حدّ الخادم/العميل وعدم الثقة بالعميل أبدًا. أجرِ المصادقة بطبقات — الوسيط لإعادة توجيهٍ تفاؤليٍّ رخيص، وطبقة وصول بيانات للتفويض الحقيقيّ قرب البيانات، وخطوة تحقّقٍ في كل مكوّن/فعل خادم — مع جلساتٍ في كوكيز httpOnly ومكتبةٍ مصونة للتشفير. أبقِ الأسرار خادمية (بلا بادئة NEXT_PUBLIC_؛ واحرُس بـ server-only)، واتّكئ على مكاسب الأداء المدمجة — next/image، و**next/font، والاستيراد الديناميكيّ**، وقبل كل شيءٍ أقلّ "use client" كي تشحن JavaScript أقلّ. احتوِ الأعطال بـ error.tsx/global-error/not-found وخدمة مراقبة، وانشُر على Vercel بلا تهيئةٍ أو استضِف ذاتيًّا بـخرج standalone (متذكّرًا تخزين الذاكرة المشترك عبر النُّسخ). وشغّل دائمًا next build قبل الإطلاق. أمسِك خطّ "الخادم موثوق، العميل عامّ" عبر كلٍّ من هذه، فيصير تطبيقك جاهزًا للإنتاج — وهو بالضبط الحدّ بين عرضٍ توضيحيّ وشيءٍ يعتمد عليه مستخدمون حقيقيّون.