Skip to main content

Error Boundaries

In SSR applications errors can occur in different stages:

  • On server initialization
  • At runtime, when server handle user request
  • On browser page loading
  • At runtime, during hydration, or when user interacts with page and make SPA-navigations

Server initialization errors block application deployment, easy to find and almost never reach the user. Moreover, tramvai provides a module @tramvai/module-error-interceptor, that adds global error handlers to the application on the server-side.

Errors during page loading are often caused by network problems. Client application may be more resistant to bad connections with different techniques - e.g. Service Worker, retry resources requests - but such techniques will be specific to each application.

Runtime errors, both on server in browser, can be critical and require send error page in reply to the user with some 5xx status.

This guide will be focused how to customize and show error pages for the users in different scenarios.

Page Error Boundary

If the first rendering of the page on the server fails, tramvai will try to render the page a second time, but already using the Error Boundary with fallback component. Also, we use React Error Boundaries under the hood, so the error fallback will render in case of any rendering errors in the browser.

Error Boundary only wrap Page Component, and Nested Layout with Root Layout will be rendered as usual.

Here is a list of cases when Page Error Boundary will be rendered with priority over Root Error Boundary:

You can provide default fallback for all pages, and specific fallback to concrete page. In this fallback components tramvai will pass url and error properties:

DefaultErrorBoundary.tsx
export const DefaultErrorBoundary = ({ url, error }) => {
return (
<div>
<h1>Something wrong!</h1>
<p>Location: {url.path}</p>
<p>Error: {error.message}</p>
</div>
);
};
info

Default response status for server-side Error Boundary - 500. This status can be changed by adding httpStatus property to Error object.

Default fallback

You can provide default error fallback component for all pages by using token DEFAULT_ERROR_BOUNDARY_COMPONENT:

import { DEFAULT_ERROR_BOUNDARY_COMPONENT } from '@tramvai/tokens-render';

const provider = {
provide: DEFAULT_ERROR_BOUNDARY_COMPONENT,
useValue: DefaultErrorBoundary,
};

Specific fallback

There are two ways to add a specific error boundary to the page.

_error.tsx

You can declare an error boundary to the page by adding a file called _error.tsx near the page component:

src
└── pages
└── login
└── index.tsx
└── _error.tsx

The component signature still be the same as the DefaultErrorBoundary, so properties error and url will be available here.

For manually created route

Concrete fallback for any of application pages can be registered in route configuration:

⌛ Create fallback component in pages directory:

pages/comments-fallback.tsx
export const CommentsFallback = ({ error }) => {
return (
<div>
<h1>Unexpected Error</h1>
<p>Can't show comments, reason: {error.message}</p>
</div>
);
};

And we will get this file structure:

src
└── pages
├── comments.tsx
└── comments-fallback.tsx

⌛ Add errorBoundaryComponent to route configuration:

import { SpaRouterModule } from '@tramvai/modules-router';

createApp({
modules: [
SpaRouterModule.forRoot([
{
name: 'comments',
path: '/comments/',
config: {
pageComponent: '@/pages/comments',
errorBoundaryComponent: '@/pages/comments-fallback',
},
},
]),
],
});

Root Error Boundary

If a critical error occurred during the request handling, e.g. Page Error Boundary rendering was unsuccessful, or an exception has been thrown out in any CommandLineRunner stages before rendering, tramvai provides an opportunity to render both custom 5xx and 4xx page. Root Boundary works only on server side.

Here is a list of cases when Root Error Boundary will be rendered:

  • when NotFoundError thrown out in Actions (4xx)
  • when HttpError thrown out in Actions (4xx or 5xx)
  • Router Guard block navigation (500)
  • Route is not found (404)
  • Error Boundary forced render in Guard or Action (4xx or 5xx)
  • Request Limiter blocked request (429)
  • React page component rendering failed (500)
  • Other unexpected server-side errors, for example Fastify Errors (4xx or 5xx)
info

Root Error Boundary works only server-side

You can provide this boundary by using token ROOT_ERROR_BOUNDARY_COMPONENT_TOKEN:

import { ROOT_ERROR_BOUNDARY_COMPONENT_TOKEN } from '@tramvai/react';

const provider = {
provide: ROOT_ERROR_BOUNDARY_COMPONENT_TOKEN,
useValue: RootErrorBoundary,
};

This components will have access to error and url in properties, and need to render complete HTML page, e.g.:

components/RootErrorBoundary.tsx
import React from 'react';

export const RootErrorBoundary = ({ error, url }) => {
return (
<html lang="ru">
<head>
<title>
Error {error.message} at {url.path}
</title>
</head>
<body>
<h1>Root Error Boundary</h1>
</body>
</html>
);
};
caution

If this component also crashes at the rendering stage, in case of HttpError user will get an empty response.body, otherwise finalhandler library will send default HTML page with some information about error.

How to

Force render Page Error Boundary in Action

caution

setPageErrorEvent - experimental API, and can be changed in future releases.

By default, errors in actions are skipped on server-side, and tramvai try to execute failed actions again in browser. If the action failed to fetch critical data for page rendering, and you want to change response status code, and show error page for user, you need to dispath setPageErrorEvent action:

import { declareAction } from '@tramvai/core';
import { HttpError } from '@tinkoff/errors';
import { setPageErrorEvent } from '@tramvai/module-router';

const action = declareAction({
name: 'action',
fn() {
// set custom response status, `500` by default
const error = new HttpError({ httpStatus: 410 });
this.dispatch(setPageErrorEvent(error));
},
});

Force render Page Error Boundary in Router Guard

caution

setPageErrorEvent - experimental API, and can be changed in future releases.

Errors in router guards will be ignored by default. Like the previous reciepe, if you need to render page fallback from guard, you can dispatch setPageErrorEvent inside:

import { STORE_TOKEN } from '@tramvai/module-common';
import { ROUTER_GUARD_TOKEN, setPageErrorEvent } from '@tramvai/module-router';
import { HttpError } from '@tinkoff/errors';

const provider = {
provide: ROUTER_GUARD_TOKEN,
multi: true,
useFactory: ({ store }) => {
return async ({ to }) => {
// guards runs for every pages, and we need to check next path if want to show error only on specific routes
if (to?.path === '/some-restricted-page/') {
const error = new HttpError({ httpStatus: 503 });
store.dispatch(setPageErrorEvent(error));
}
};
},
deps: {
store: STORE_TOKEN,
},
};
- Next: App Lifecycle