Обработка ошибок при загрузке

Основные концепции обработки ошибок

В TanStack Router обработка ошибок при загрузке данных (loaders) реализуется через Error Boundaries. Ошибки, возникающие в асинхронных функциях загрузки, автоматически передаются в соответствующий error boundary, если он определён в маршруте. Это позволяет изолировать ошибки на уровне маршрутов и отображать пользователю информативные сообщения без падения всего приложения.

Error boundary можно задать двумя способами:

  1. На уровне маршрута — для конкретного пути.
  2. На уровне родительского маршрута — для группы маршрутов, наследующих его поведение.
import { Router, Route } from '@tanstack/router'

const router = new Router({
  routeTree: Route({
    path: '/',
    component: RootComponent,
    errorComponent: RootErrorComponent,
    children: [
      Route({
        path: 'dashboard',
        component: Dashboard,
        loader: async () => {
          const data = await fetchDashboardData()
          if (!data) throw new Error('Ошибка загрузки данных панели')
          return data
        },
        errorComponent: DashboardErrorComponent,
      }),
    ],
  }),
})

В этом примере DashboardErrorComponent будет вызван только при ошибках загрузки dashboard, тогда как RootErrorComponent перехватывает ошибки на уровне родителя.


Асинхронные функции загрузки и обработка ошибок

Все функции загрузки в TanStack Router являются асинхронными. Любая ошибка, выброшенная в loader, автоматически передается в error boundary маршрута. Для управления состоянием ошибки часто используют стандартный объект ошибки Error или собственные классы ошибок.

class NotFoundError extends Error {}
class UnauthorizedError extends Error {}

const loader = async () => {
  const response = await fetch('/api/user')
  if (response.status === 404) throw new NotFoundError('Пользователь не найден')
  if (response.status === 401) throw new UnauthorizedError('Не авторизован')
  return response.json()
}

В errorComponent можно использовать проверку типа ошибки для отображения различных сообщений пользователю:

function UserError({ error }) {
  if (error instanceof NotFoundError) {
    return <div>Пользователь не найден</div>
  }
  if (error instanceof UnauthorizedError) {
    return <div>Доступ запрещён</div>
  }
  return <div>Произошла непредвиденная ошибка</div>
}

Вложенные маршруты и каскадная обработка ошибок

TanStack Router поддерживает вложенные маршруты с наследованием error boundaries. Если дочерний маршрут не определяет errorComponent, ошибка поднимается к родительскому маршруту. Это позволяет создавать централизованную обработку ошибок для группы маршрутов.

const router = new Router({
  routeTree: Route({
    path: '/',
    component: App,
    errorComponent: AppError,
    children: [
      Route({
        path: 'profile',
        component: Profile,
        loader: fetchProfile,
      }),
      Route({
        path: 'settings',
        component: Settings,
        loader: fetchSettings,
      }),
    ],
  }),
})

Если fetchProfile или fetchSettings выбросит ошибку, и ни один дочерний маршрут не имеет errorComponent, будет вызван AppError.


Управление загрузкой и состояниями ошибок

Для улучшения UX часто необходимо отображать загрузку и ошибки одновременно. TanStack Router предоставляет хуки для получения текущего состояния загрузки и ошибки маршрута:

  • useLoaderData() — данные после успешной загрузки
  • useRouteError() — объект ошибки текущего маршрута
  • useIsFetching() — индикатор активных загрузок

Пример комбинированного использования:

function ProfilePage() {
  const data = useLoaderData()
  const error = useRouteError()
  const isFetching = useIsFetching()

  if (error) return <ProfileError error={error} />
  if (isFetching) return <div>Загрузка...</div>
  return <ProfileDetails data={data} />
}

Предотвращение неожиданных ошибок

Для минимизации критических сбоев рекомендуется:

  • Явно проверять ответы API и выбрасывать ошибки для статусов, отличных от 200.
  • Создавать отдельные классы ошибок для разных типов проблем.
  • Использовать родительские error boundaries для глобального перехвата неожиданных ошибок.
  • Предоставлять fallback-компоненты для отображения сообщений пользователю.

Пример сложного сценария с несколькими уровнями ошибок

const router = new Router({
  routeTree: Route({
    path: '/',
    component: App,
    errorComponent: AppError,
    children: [
      Route({
        path: 'dashboard',
        component: Dashboard,
        loader: fetchDashboardData,
        errorComponent: DashboardError,
        children: [
          Route({
            path: 'stats',
            component: Stats,
            loader: fetchStats,
          }),
        ],
      }),
    ],
  }),
})
  • Ошибка fetchStats → если нет StatsError, будет вызван DashboardError.
  • Ошибка fetchDashboardData → вызов DashboardError.
  • Любая непредвиденная ошибка на уровне / → вызов AppError.

Эта каскадная модель позволяет изолировать ошибки, сохраняя удобство пользовательского интерфейса.


Рекомендации по организации

  • Определять отдельные error boundaries для ключевых маршрутов с критическим контентом.
  • Использовать централизованные классы ошибок для унификации обработки.
  • Разделять логику загрузки данных и отображение ошибок для удобства тестирования.
  • Всегда предоставлять fallback UI для асинхронных загрузок, чтобы приложение оставалось отзывчивым при сбоях.