Skip to content

[Feature/Docs] Best practice for resetting global ErrorBoundary on navigation #145

@k35o

Description

@k35o

Description

I'm currently setting up global error handling by wrapping the <Router /> with a catch-all <ErrorBoundary> (e.g., from react-error-boundary). However, once this outermost error boundary catches an error, navigating away (e.g., clicking the browser's back button) does not clear the error state.

The Context & Pain Point

While route-level errors can be handled by placing an <ErrorBoundary> inside a root Layout component to wrap the <Outlet />, this approach cannot catch errors occurring within the Layout itself or fatal errors during router initialization.

To handle these unhandled exceptions as a last resort, we must place a catch-all boundary outside the <Router>.

Since Funstack Router lacks built-in error handling props, developers naturally use an <ErrorBoundary> outside the router for this safety net. However, being outside the router context means we cannot use hooks like useLocation to reset the boundary.
While we can manually solve this by subscribing to the Navigation API and using NavigationHistoryEntry.key as the reset key, developers migrating from other routing libraries might not realize this pattern, leaving users permanently stuck on the fallback UI.

Proposed Solutions

To improve the Developer Experience regarding global error handling, I suggest the following options:

  1. Provide an Official Recipe in the Docs
    Add a section explaining how to properly handle global errors using the Navigation API and useSyncExternalStore. For example:
import { useSyncExternalStore } from 'react';
import { Router } from '@funstack/router';
import { ErrorBoundary } from 'react-error-boundary';

const subscribe = (callback: () => void) => {
  if (typeof window === 'undefined' || !window.navigation) return () => {};
  window.navigation.addEventListener('currententrychange', callback);
  return () => window.navigation.removeEventListener('currententrychange', callback);
};

const getSnapshot = () => window.navigation?.currentEntry?.key || '';
const getServerSnapshot = () => '';

function useNavigationKey() {
  return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
}

export default function App() {
  const navKey = useNavigationKey();

  return (
    <ErrorBoundary resetKeys={[navKey]} FallbackComponent={GlobalErrorFallback}>
      <Router routes={routes} />
    </ErrorBoundary>
  );
}
  1. Export a Helper Component
    Provide a <GlobalErrorBoundary> wrapper that hooks into the Navigation API internally to automatically reset on location change.

  2. Add an errorComponent property to the route API
    Allow defining an error fallback directly in the route configuration.

const routes = [
  route({
    path: '/',
    component: Layout,
    errorComponent: GlobalErrorFallback, // Automatically resets on navigation
    children: [ ... ]
  })
];

I'd love to hear your thoughts on these options. If there is already a better way or a recommended best practice that I might have missed, please let me know!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions