Sokan Academy

ساخت برنامه‌های وب می‌تواند چالش‌برانگیز باشد، به‌خصوص زمانی که خطاها ظاهر می‌شوند. چه یک مشکل در سرور باشد، چه یک خطای API یا یک باگ در کد، این خطاها می‌توانند باعث از کار افتادن برنامه شوند و کاربران را دلسرد کنند. به همین دلیل، مدیریت صحیح ارورها برای هر اپلیکیشن مدرن ضروری است.

در این مقاله، به بررسی شیوه‌های مدیریت خطا در Next.js پرداخته و با استفاده از مثال‌های کاربردی، نحوهٔ پیاده‌سازی این رویکردها را تشریح خواهیم کرد. مدیریت موثر خطاها نقشی حیاتی در بهبود تجربه کاربری و پایداری برنامه‌ها ایفا می‌کند.

اهمیت مدیریت خطا

مدیریت صحیح خطاها به دو دلیل مهم، اهمیت بالایی داراند:

  1. بهبود تجربه کاربری: کاربران نباید با صفحات خالی یا پیام‌های خطای ناخوشایند روبه‌رو شوند. مدیریت صحیح خطاها امکان نمایش پیام‌های دوستانه مانند "درخواست شما با مشکل مواجه شد، لطفاً بعداً تلاش کنید." را فراهم می‌کند.
  2. بهبود SEO و عملکرد: اگر برنامه شما crash کند یا صفحات خراب نمایش دهد، موتورهای جستجو مانند گوگل ممکن است رتبه سایت شما را کاهش دهند. مدیریت صحیح خطاها به پایداری و عملکرد بهتر کمک می‌کند.

انواع خطاها در Next.js

در Next.js، خطاها به دو دسته‌ی کلی تقسیم می‌شوند:

  1. خطاهای مورد انتظار (Expected Errors): این دسته از خطاها معمولا قابل پیش‌بینی هستند و می‌توان از طریق مدیریت صحیح، تأثیر آن‌ها را بر تجربه کاربری کاهش داد. برای مثال، یک خطای API 404 (عدم وجود داده) را می‌توان مدیریت کرد و به‌جای نمایش یک صفحه خالی، پیام مناسبی به کاربر نشان داد.
  2. خطاهای پیش‌بینی‌نشده (Uncaught Exceptions): این نوع خطاها غیرقابل پیش‌بینی بوده و معمولاً به دلیل مشکلات غیرمنتظره در کد رخ می‌دهند، مانند یک باگ در سرور یا شکست در واکشی (fetch) داده. اگر این نوع خطاها مدیریت نشوند، ممکن است باعث crash شدن برنامه شوند.

مدیریت خطا در Next.js

Next.js ابزارهای قدرتمندی برای مدیریت این خطاها ارائه می‌دهد. در این مقاله، روش‌هایی را بررسی خواهیم کرد که به شما کمک می‌کنند تا:

  1. قبل از اینکه خطاها باعث crash شدن برنامه شوند، آن‌ها را مدیریت کنید.
  2. پیام‌های خطای مناسب به کاربران نمایش دهید.
  3. مشکلات رایج در مسیرهای API و رندر سمت سرور (SSR) را برطرف کنید.
  4. با ادامه این مقاله، یاد خواهید گرفت که چگونه یک سیستم مدیریت خطای بهینه پیاده‌سازی کنید.

مدیریت خطاهای مورد انتظار (expected errors)

در توسعهٔ وب‌اپلیکیشن‌ها، خطاهای مورد انتظار به آن دسته از خطاهایی گفته می‌شود که در جریان عادی عملیات برنامه ممکن است رخ دهند. این خطاها معمولاً به دلیل تعاملات کاربر، ورودی‌های نامعتبر، پاسخ‌های ناموفق از سرور یا دسترسی به داده‌های نامعتبر ایجاد می‌شوند.

برخلاف خطاهای غیرمنتظره که اغلب ناشی از باگ‌های نرم‌افزاری یا مشکلات غیرقابل پیش‌بینی هستند، خطاهای مورد انتظار قابل پیش‌بینی و مدیریت‌پذیرند. به عنوان مثال، در یک فرم ثبت‌نام، اگر کاربر ایمیل نامعتبری وارد کند، سرور یک پاسخ نامعتبر ارسال خواهد کرد. این یک خطای مورد انتظار است که می‌توان آن را مدیریت کرد تا به جای متوقف شدن برنامه، یک پیام مناسب به کاربر نمایش داده شود. در چنین شرایطی، استفاده از بلوک‌های try/catch برای مدیریت این نوع خطاها کارایی بالایی ندارد و باعث افزایش پیچیدگی کد می‌شود.

رویکرد پیشنهادی برای مدیریت خطاهای مورد انتظار در Next.js این است که به‌جای استفاده از try/catch، خطاها را به‌عنوان مقادیر بازگشتی مدل‌سازی کرده و مستقیماً به کاربر (Client) ارسال کنید. این روش کد را خواناتر، ساده‌تر و قابل نگهداری‌تر می‌کند و تجربه کاربری را بهبود می‌بخشد.

Next.js ابزارهای مختلفی را برای مدیریت خطاهای مورد انتظار ارائه می‌دهد، از جمله:

  1. استفاده از useActionState برای مدیریت خطاهای اکشن‌های سرور (Server Actions)
  2. نمایش پیام‌های خطا در کامپوننت‌های سرور (Server Components)
  3. ذخیره‌سازی وضعیت خطا در State در کامپوننت سمت کاربر (Client Components)
  4. در ادامه، هر یک از این روش‌ها را بررسی می‌کنیم.

مدیریت خطاهای مورد انتظار در اکشن‌های سرور

در React 19 هوک جدیدی به نام useActionState معرفی شده است که مدیریت وضعیت اکشن‌های سرور، از جمله خطاها، را ساده‌تر می‌کند. این هوک با جلوگیری از استفاده از بلوک‌های try/catch برای خطاهای مورد انتظار، به شما امکان می‌دهد این خطاها را به‌صورت مقادیر بازگشتی مدل‌سازی کنید.

قدم اول:

برای این کار ابتدا در آدرس app/actions.ts یک اکشن سرور تعریف کنید که ممکن است خطاهای مورد انتظار را برگرداند.  

'use server'
     
    import { redirect } from 'next/navigation'
     
    export async function createUser(prevState: any, formData: FormData) {
      const res = await fetch('https://...')
      const json = await res.json()
     
      if (!res.ok) {
        return { message: 'Please enter a valid email' }
      }
     
      redirect('/dashboard')
    }

قدم دوم:

سپس، در کامپوننت سمت کاربر (برای مثال در آدرس app/auth/signup.tsx)، از هوک useActionState برای مدیریت وضعیت اکشن سرور استفاده کنید:    

'use client'
     
    import { useActionState } from 'react'
    import { createUser } from '@/app/actions'
     
    const initialState = {
      message: '',
    }
     
    export function Signup() {
      const [state, formAction, pending] = useActionState(createUser, initialState)
     
      return (
        <form action={formAction}>
          <label htmlFor="email">Email</label>
          <input type="text" id="email" name="email" required />
          {/* ... */}
          <p aria-live="polite">{state?.message}</p>
          <button disabled={pending}>Sign up</button>
        </form>
      )
    }

مدیریت خطاهای مورد انتظار در کامپوننت‌های سمت سرور (Server Components)

 همچنین در کامپوننت‌های سرور، هنگام fetch، می‌توانید با بررسی پاسخ دریافتی، به‌صورت شرطی پیام خطا را نمایش داده یا کاربر را به مسیر دیگری هدایت (redirect) کنید.

export default async function Page() { 
       const res = await fetch(`https://...`)  
       const data = await res.json()  
       if (!res.ok) {    
          return 'There was an error.'  
       }
     return '...'
    }

مدیریت خطاهای مورد انتظار در Client Components

مدیریت خطاهای مورد انتظار در کامپوننت سمت کاربر (Client Components) در Next.js معمولاً از طریق State انجام می‌شود. در این روش، پیام‌های خطا در State ذخیره شده و در صورت بروز خطا، در رابط کاربری نمایش داده می‌شوند تا کاربر متوجه مشکل شده و بتواند اقدام لازم را انجام دهد. به عنوان مثال، در یک فرم ثبت‌نام، اگر مشکلی در اطلاعات وارد شده وجود داشته باشد (مانند ایمیل نامعتبر یا اطلاعات ناقص)، سرور یک پاسخ نامعتبر ارسال می‌کند که می‌توان این وضعیت را مدیریت کرده و پیغام مناسبی به کاربر نمایش داد. این کار با استفاده از useState در کامپوننت سمت کاربر (Client Components) انجام می‌شود. به این ترتیب، هنگامی که درخواست ارسال می‌شود، مقدار State تغییر کرده و پیام خطا روی صفحه نشان داده می‌شود.   

'use client';
    import { useState } from 'react';
    export function Form() {
      const [errorMessage, setErrorMessage] = useState('');
      const handleSubmit = async (event: React.FormEvent) => {
        event.preventDefault();
        const formData = new FormData(event.target as HTMLFormElement);
        const res = await fetch('/api/submit', {
          method: 'POST',
          body: formData,
        });
        if (!res.ok) {
          const errorData = await res.json();
          setErrorMessage(errorData.message || 'An error occurred.');
        }
      };
      return (
        <form onSubmit={handleSubmit}>
          {errorMessage && <p aria-live="polite">{errorMessage}</p>}
          <button type="submit">Submit</button>
        </form>
      );
    }
عنوان تبلیغ: آموزش Next.js

مدیریت خطاهای پیش‌بینی‌نشده (Uncaught Exceptions)

خطاهای پیش‌بینی‌نشده، خطاهای غیرمنتظره‌ای هستند که معمولاً به دلیل باگ‌های برنامه یا مشکلاتی که نباید در جریان عادی برنامه رخ دهند، ایجاد می‌شوند. این نوع خطاها باید با پرتاب استثنا (throwing errors) مدیریت شوند، که سپس توسط Error Boundaries شناسایی و کنترل می‌شوند.

روش‌های مدیریت خطاهای پیش‌بینی‌نشده در Next.js

  1. روش رایج: برای مدیریت این خطاها در بخش‌های پایین‌تر از Layout اصلی، از error.js در مسیرهای مربوطه استفاده کنید.
  2. روش اختیاری: می‌توان با ایجاد فایل‌های error.js در مسیرهای داخلی (مثلاً app/dashboard/error.js) خطاها را به‌صورت جزئی‌تر مدیریت کرد.
  3. روش کمتر رایج: اگر نیاز به مدیریت خطاها در کل برنامه دارید، از global-error.js در ریشه‌ی پروژه (app/) استفاده کنید تا تمامی خطاهای سطح بالا را کنترل کند.

استفاده از error boundaries

در این روش error boundaries خطاها را در کامپوننت های زیرمجموعه خود دریافت می کنند. برای استفاده از این روش ابتدا لازم است یک فایل مدیریت خطا در این آدرس app/dashboard/error.tsx بسازید. فایل error.tsx به‌طور خودکار یک مرز خطا (React Error Boundary) ایجاد می‌کند که یک بخش فرزند داخلی یا کامپوننت page.tsx را در بر می‌گیرد. اگر هر گونه خطایی در داخل این بخش مسیر رخ دهد، به جای آنکه کل برنامه دچار مشکل شود، کامپوننتی که از فایل error.tsx صادر شده است، به عنوان رابط کاربری جایگزین (Fallback UI) نمایش داده می‌شود.    

'use client' 
     
    import { useEffect } from 'react'
     
    export default function Error({
      error,
      reset,
    }: {
      error: Error & { digest?: string }
      reset: () => void
    }) {
      useEffect(() => {
        // Log the error to an error reporting service
        console.error(error)
      }, [error])
     
      return (
        <div>
          <h2>Something went wrong!</h2>
          <button
            onClick={
              // Attempt to recover by trying to re-render the segment
              () => reset()
            }
          >
            Try again
          </button>
        </div>
      )
    }

مدیریت خطاها در Layouts

در Next.js، اگر در یکی از کامپوننت‌های فرزند Error Boundary خطایی رخ دهد، این خطا شناسایی و مدیریت می‌شود. اما فایل error.tsx که برای مدیریت خطاها در یک مسیر خاص تعریف شده است، نمی‌تواند خطاهای پرتاب‌شده در کامپوننت layout.tsx مربوط به همان بخش (Segment) را مدیریت کند. یعنی اگر یک خطا در خود layout.tsx رخ دهد، error.tsx آن را پوشش نمی‌دهد، بلکه تنها خطاهایی را که در کامپوننت‌های داخل مسیر مربوطه اتفاق می‌افتند، کنترل می‌کند. برای درک بهتر این موضوع، فرض کنید یک پوشه studio/admin/ داریم که شامل دو فایل layout.tsx و error.tsx است:

مدیریت خطاها در Layouts در next js

در اینجا، error.tsx خطاهای مربوط به تمامی فایل های این مسیر را مدیریت می‌کند و در صورت بروز دادن خطا اجازه نمی‌دهد که کل برنامه crash کند.

مدیریت خطاهای سراسری (Global Errors)

اگرچه این روش کمتر رایج است، اما می‌توان خطاهای سطح بالا را در Layout اصلی مدیریت کرد. این کار با ایجاد فایل global-error.js در مسیر app/ انجام می‌شود که تمامی خطاهای برنامه را در سطح ریشه کنترل می‌کند. هنگام استفاده از global-error.js، رابط کاربری خطا (Error UI) باید تگ‌های <html> و <body> را به‌صورت مستقل تعریف کند، زیرا این کامپوننت در صورت فعال شدن، جایگزین Layout اصلی یا Template می‌شود.   

'use client'
     
    export default function GlobalError({
      error,
      reset,
    }: {
      error: Error & { digest?: string }
      reset: () => void
    }) {
      return (
        <html>
          <body>
            <h2>Something went wrong!</h2>
            <button onClick={() => reset()}>Try again</button>
          </body>
        </html>
      )
    }

نتیجه

با بهره‌گیری از رویکردهای نوین در مدیریت خطاها، می‌توان برنامه‌های وب را پایدارتر، قابل اطمینان‌تر و کاربر پسندتر طراحی کرد. تفکیک خطاهای مورد انتظار (Expected Errors) از استثناهای غیرمنتظره (Uncaught Exceptions) یکی از اصول کلیدی در توسعهٔ نرم‌افزارهای مدرن است که به بهبود عملکرد، افزایش خوانایی کد و تسهیل در نگهداری کمک می‌کند.

در Next.js، خطاها را می‌توان با روش‌های مختلفی مدیریت کرد. useActionState و useState در کامپوننت سمت کاربر (Client Components) برای خطاهای مورد انتظار و Error Boundaries با استفاده از error.js برای خطاهای غیرمنتظره به کار می‌روند. در نهایت، بهره‌گیری از این رویکردهای ساختار یافته در مدیریت خطاها، نه‌تنها به افزایش کیفیت کد و کاهش مشکلات پیش‌بینی‌نشده کمک می‌کند، بلکه باعث می‌شود کاربران در هنگام مواجهه با خطاها، بازخورد مناسبی دریافت کنند و تجربهٔ کاربری بهتری داشته باشند.

این محتوا آموزنده بود؟
خطانکست جی اسمدیریت اسثناخطایابی

sokan-academy-footer-logo
کلیه حقوق مادی و معنوی این وب‌سایت متعلق به سکان آکادمی می باشد.