Обработка ошибок

В предыдущей главе вы узнали, как изменять данные с помощью Server Actions. Давайте посмотрим, как можно грациозно обрабатывать ошибки с помощью JavaScript-выражений try/catch и API Next.js для неперехваченных исключений.

Добавление try/catch в Server Actions

Сначала добавим выражения try/catch JavaScript в ваши Server Actions, чтобы обрабатывать ошибки корректно.

Если вы знаете, как это сделать, потратьте несколько минут на обновление своих Server Actions или скопируйте код ниже:

Обратите внимание, что redirect вызывается вне блока try/catch. Это потому, что redirect работает путем выбрасывания ошибки, которая будет перехвачена блоком catch. Чтобы избежать этого, вызывайте redirect после try/catch. redirect будет доступен только в случае успешного выполнения try.

Мы грациозно обрабатываем эти ошибки, перехватывая проблемы с базой данных и возвращая информативное сообщение из нашей Server Action.

Что произойдет, если в вашем действии возникнет неперехваченное исключение? Мы можем смоделировать это, вручную выбросив ошибку. Например, в действии deleteInvoice добавьте выброс ошибки в начале функции:

/app/lib/actions.ts
export async function deleteInvoice(id: string) {
  throw new Error('Не удалось удалить счет');
 
  // Недостижимый блок кода
  await sql`DELETE FROM invoices WHERE id = ${id}`;
  revalidatePath('/dashboard/invoices');
}

При попытке удалить счет вы увидите ошибку в локальной среде. В продакшене вы захотите более элегантно показать пользователю сообщение при возникновении непредвиденной ошибки.

Здесь пригодится файл error.tsx в Next.js. Убедитесь, что удалили эту вручную добавленную ошибку после тестирования, прежде чем переходить к следующему разделу.

Обработка всех ошибок с помощью error.tsx

Файл error.tsx можно использовать для определения границы пользовательского интерфейса сегмента маршрута. Он служит универсальным обработчиком непредвиденных ошибок и позволяет отображать запасной UI для пользователей.

Внутри папки /dashboard/invoices создайте новый файл error.tsx и вставьте следующий код:

/dashboard/invoices/error.tsx
'use client';
 
import { useEffect } from 'react';
 
export default function Error({
  error,
  reset,
}: {
  error: Error & { digest?: string };
  reset: () => void;
}) {
  useEffect(() => {
    // Опционально: логирование ошибки в сервис отчетов
    console.error(error);
  }, [error]);
 
  return (
    <main className="flex h-full flex-col items-center justify-center">
      <h2 className="text-center">Что-то пошло не так!</h2>
      <button
        className="mt-4 rounded-md bg-blue-500 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-400"
        onClick={
          // Попытка восстановления путем повторного рендеринга маршрута
          () => reset()
        }
      >
        Попробовать снова
      </button>
    </main>
  );
}

Обратите внимание на несколько моментов в этом коде:

  • "use client" - error.tsx должен быть клиентским компонентом.
  • Он принимает два пропса:
    • error: Экземпляр нативного объекта Error JavaScript.
    • reset: Функция для сброса границы ошибки. При выполнении она попытается перерендерить сегмент маршрута.

При повторной попытке удалить счет вы увидите следующий интерфейс:

Файл error.tsx, показывающий принимаемые пропсы

Обработка 404 ошибок с функцией notFound

Другой способ обработки ошибок - использование функции notFound. В то время как error.tsx полезен для перехвата непредвиденных исключений, notFound можно использовать при попытке получить несуществующий ресурс.

Например, перейдите по адресу http://localhost:3000/dashboard/invoices/2e94d1ed-d220-449f-9f11-f0bbceed9645/edit.

Это поддельный UUID, которого нет в вашей базе данных.

Вы сразу увидите, как срабатывает error.tsx, поскольку это дочерний маршрут /invoices, где определен error.tsx.

Однако если вы хотите быть более конкретными, можно показать ошибку 404, сообщив пользователю, что запрашиваемый ресурс не найден.

Вы можете подтвердить отсутствие ресурса, добавив console.log для возвращаемого invoice в функции fetchInvoiceById в data.ts:

/app/lib/data.ts
export async function fetchInvoiceById(id: string) {
  try {
    // ...
 
    console.log(invoice); // Invoice - пустой массив []
    return invoice[0];
  } catch (error) {
    console.error('Ошибка базы данных:', error);
    throw new Error('Не удалось загрузить счет.');
  }
}

Теперь, когда вы знаете, что счета не существует в базе данных, давайте используем notFound для его обработки. Перейдите в /dashboard/invoices/[id]/edit/page.tsx и импортируйте { notFound } из 'next/navigation'.

Затем можно использовать условие для вызова notFound, если счет не существует:

/dashboard/invoices/[id]/edit/page.tsx
import { fetchInvoiceById, fetchCustomers } from '@/app/lib/data';
import { notFound } from 'next/navigation';
 
export default async function Page(props: { params: Promise<{ id: string }> }) {
  const params = await props.params;
  const id = params.id;
  const [invoice, customers] = await Promise.all([
    fetchInvoiceById(id),
    fetchCustomers(),
  ]);
 
  if (!invoice) {
    notFound();
  }
 
  // ...
}

Затем, чтобы показать пользователю интерфейс ошибки, создайте файл not-found.tsx внутри папки /edit.

Файл not-found.tsx внутри папки edit

Вставьте следующий код в файл not-found.tsx:

/dashboard/invoices/[id]/edit/not-found.tsx
import Link from 'next/link';
import { FaceFrownIcon } from '@heroicons/react/24/outline';
 
export default function NotFound() {
  return (
    <main className="flex h-full flex-col items-center justify-center gap-2">
      <FaceFrownIcon className="w-10 text-gray-400" />
      <h2 className="text-xl font-semibold">404 Не найдено</h2>
      <p>Не удалось найти запрашиваемый счет.</p>
      <Link
        href="/dashboard/invoices"
        className="mt-4 rounded-md bg-blue-500 px-4 py-2 text-sm text-white transition-colors hover:bg-blue-400"
      >
        Назад
      </Link>
    </main>
  );
}

Обновите маршрут, и теперь вы должны увидеть следующий интерфейс:

Страница 404 Not Found

Важно помнить, что notFound имеет приоритет над error.tsx, поэтому его можно использовать для обработки более специфических ошибок!

Дополнительные материалы

Чтобы узнать больше об обработке ошибок в Next.js, ознакомьтесь с документацией: