- تاريخ النشر:
كيفية معالجة الأخطاء المخصصة في تطبيقات Next.js
- كتب بواسطة:
- اسم الكاتب
- تويتر الكاتب
بسم الله والحمد لله والصلاة والسلام على سيدنا محمد رسول الله وبعد،
مؤخرًا أثناء عملي على مهمة شيقة في مساق، استخدمت طريقة عادةً ما أستخدمها لمعالجة الأخطاء المخصصة داخل تطبيقات nextjs
. فقررت أن أشارك هذه التجربة وأكتب عنها.
في كثير من الأحيان، عندما يحدث خطأ في تطبيق يعمل بواسطة nextjs
، يتم عرض صفحة الخطأ 500
أو صفحة خطأ مخصصة في حال وجود ملف error.tsx
. ولكن في بعض الأحيان، يكون هناك حاجة لعرض رسالة خطأ مخصصة أكثر تدل على سبب الخطأ. لحسن الحظ، هناك حيلة بسيطة نوعاً ما يمكننا استخدامها لعرض رسالة خطأ مخصصة بدلاً من صفحة الخطأ الافتراضية.
أولاً، لنأخذ نظرة أوسع حول الموضوع. في مساق، في حال انتهاء اشتراك إحدى المنصات، أي محاولة تواصل مع الـ API
تعود بخطأ 402
. والمطلوب عرض رسالة خطأ مخصصة تدل على انتهاء الاشتراك عندما يقوم المستخدم بزيارة أي صفحة خاصة بالمنصة من دون تحويله إلى صفحة جديدة مثل /subscription-expired
.
لقد قمت بتجربة العديد من الحلول لحل هذه المشكلة، ولكن كلها انتهت بأن الكود أصبح متشعبًا أو تم تطبيق حل لا علاقة له بما أريده. على سبيل المثال:
export default async function RootLayout({ children }) {
const tenant = await fetchTenant();
//Handle subscription expired error
if ("error" in tenant && tenant.error.status === 402) {
return <SubscriptionExpired />;
}
return (
<html>
<body>{children}</body>
</html>
);
}
هذا الحل لا يعمل بشكل جيد، لأنني بحاجة إلى معالجة نفس الخطأ في كل مرة يتم التواصل فيها مع الـ API
وفي كل صفحة! وفي بعض الأحيان، يكون هناك أكثر من طلب للـ API
. على سبيل المثال:
export default async function CourseLayout({ children }) {
const tenant = await fetchTenant();
const course = await fetchCourse({ ... });
const content = await fetchContent({ ... });
return <>children</>;
}
في هذه الحالة، يجب عليّ معالجة هذا الخطأ وربما أخطاء أخرى لكل طلب منفصل! وهذا غير منطقي.
هنا عدت إلى التوثيق للقراءة أكثر عن كيفية التعامل مع الأخطاء ومعالجتها، ووجدت أنه في حال كان الخطأ داخلياً يمكنني استخدام throw
في المكان نفسه الذي يتم التواصل فيه مع الـ API
ولكن مع رسالة خطأ مخصصة:
export async function fetchTenant() {
const response = await fetchBaseQuery({ ... });
if ("error" in response) {
if (response.error.status === 402) {
throw new Error("SUBSCRIPTION_EXPIRED");
}
}
return { ... };
}
وبعدها يمكنني أن أعالج الخطأ داخل ملف error.tsx
بناءً على الرسالة الخاصة بالخطأ:
export default function Error({ error }) {
if (error.message === "SUBSCRIPTION_EXPIRED") {
return <SubscriptionExpired />;
}
return <ServerError />;
}
إلى حد هذه اللحظة، الأوضاع لطيفة جداً ومشوقة. قمت بتجربة كل أنواع الأخطاء الخاصة بالنظام الداخلي وتأكدت أنها تعمل بشكل لا شك فيه! وفرحت جداً بأنني وجدت الحل المناسب بعد جهد مطول من البحث والتجربة.
ولكن، واجهت الصدمة أنه عندما يتم بناء التطبيق من أجل البرودكشن، فإن reactjs
داخلياً لا تقوم بإرسال الأخطاء مع كافة تفاصيلها لأسباب أمنية. فانتابني الفضول حول كيفية عمل ()notFound
في nextjs
وكيف يتم عرض الصفحة الخاصة بالخطأ 404
دون تحويل المستخدم إلى صفحة أخرى. عند مراجعة الكود المصدري، وجدت أنه لا يتم الاعتماد على رسالة الخطأ، ولكن يتم الاعتماد على الـ digest
الخاص بالخطأ الذي توفره nextjs
:
function notFound() {
const error = new Error("NEXT_NOT_FOUND");
error.digest = "NEXT_NOT_FOUND";
throw error;
}
الحل
بعد اكتشاف أن nextjs
تعتمد على الـ digest
الخاص بالخطأ، فلماذا لا أعتمد عليه أيضاً في التطبيق الذي أعمل عليه؟
export async function fetchTenant() {
const response = await fetchBaseQuery({...});
if ("error" in response) {
if (response.error.status === 402) {
const error = new Error("SUBSCRIPTION_EXPIRED");
error.digest = "SUBSCRIPTION_EXPIRED";
throw error;
}
}
return {...};
}
وبعد تعديل الكود الخاص بالخطأ داخل الملف error.tsx
:
export default function Error({ error }) {
if (error.digest === "SUBSCRIPTION_EXPIRED") {
return <SubscriptionExpired />;
}
return <ServerError />;
}
بوووووم! 🎉
كل شيء يعمل كما هو متوقع دون أي مشاكل، وباستخدام هذه الطريقة البسيطة يمكننا عرض رسالة خطأ مخصصة بدلاً من صفحة الخطأ الافتراضية مع المحافظة على العرف الذي توصي به nextjs
.
النهاية
في النهاية، الاعتماد على الـ digest
الخاص بالخطأ يعتبر حلاً بسيطاً وأمراً شائعاً جداً، ولكن في بعض الأحيان لا يخطر على البال. لذا، كان من الجيد التحدث عنه قليلاً.
وأنت، ماذا تعتقد؟ هل تستخدم طريقة مختلفة؟ أو لديك حل أفضل من أجل التعامل مع حالات مشابهة؟ تواصل معي على X وأخبرني عن تجربتك ورأيك!