Skip to content

Error pages ​

CAUTION

Experimental — not for production. Managed Data Mode SSR is available for evaluation and testing. Do not use it in production yet. The API and behaviour may change. Until then, continue using Webpack SSR.

sku converts route failures to document responses. The nearest React Router ErrorBoundary renders the UI. The streamed response uses the matching HTTP status code.

Add an ErrorBoundary ​

When a loader fails, a route throws, or the URL does not match, readers should see your UI rather than a blank document. Add an ErrorBoundary on a layout route so every child under it shares the same failure UI.

Without a route ErrorBoundary, React Router hydrates its default error UI over the SSR error HTML. That can briefly flash a stack trace before the default “Hey developer” page. RootLayout renders <html>. Attaching ErrorBoundary to the root route itself replaces that layout on failure and drops <html> from the response. Instead, attach ErrorBoundary to a child route under RootLayout so the document shell stays mounted.

sku uses React Router Error Boundaries:

tsx
import type { SkuRouteObject } from 'sku/runtime';

import { ErrorBoundary } from './ErrorBoundary';
import { RootLayout } from './RootLayout';

export const routes: SkuRouteObject[] = [
  {
    Component: RootLayout,
    children: [
      {
        ErrorBoundary, 
        children: [{ index: true, lazy: () => import('./pages/home/home') }],
      },
    ],
  },
];
tsx
import { isRouteErrorResponse, useRouteError } from 'react-router';

export function ErrorBoundary() {
  const error = useRouteError();

  if (isRouteErrorResponse(error)) {
    return (
      <main>
        <h1>
          {error.status} {error.statusText}
        </h1>
        <p>{typeof error.data === 'string' ? error.data : null}</p>
      </main>
    );
  }

  return (
    <main>
      <h1>Something went wrong</h1>
    </main>
  );
}

Use isRouteErrorResponse when you want different copy for HTTP-style failures (404, data() responses) versus unexpected throws.

What gets an ErrorBoundary ​

An ErrorBoundary on a route catches failures that happen while matching or rendering that route and its descendants. That includes:

  • loader failures and thrown data() responses
  • 404 for unmatched routes
  • 405 when a mutation hits a route without an action
  • sync Component throws

Put the boundary on a child route under the root layout so nested pages inherit it while preserving <html>. You can also nest boundaries when a section needs its own failure UI.

Suspense failures during document SSR ​

Render-time data loading can reject a Suspense boundary while sku is still streaming the document. When that happens, sku aborts the first stream. sku then re-renders with the error on the static handler context. The nearest ErrorBoundary then produces the HTML response (status 500 unless the error is a route error response).

Errors above the router ​

A root route ErrorBoundary only covers work inside the router. It does not catch errors thrown above the router, including inside sku’s always-on SkuProvider (see the tree on Providers).

Failures at that level pass to Express. They are hard to convert to a stylised error page for readers. Your route ErrorBoundary never gets a chance to render.

Mount isomorphic and fallible providers in your root layout so the route boundary can cover them.

See also ​