We will create a custom loader taking an effect as parameter and returning a promise containing data or an object containing the effect error details.
import type { LoaderFunctionArgs } from '@remix-run/server-runtime';import { Effect, pipe } from 'effect';import { collectErrorDetails } from './logic/collect-error-details';import { remixThrow } from './logic/remix-throw';export const effectLoader =
 <A, E>(effect: (args: LoaderFunctionArgs) => Effect.Effect<A, E>) =>
 async (args: LoaderFunctionArgs) =>
 await Effect.runPromise(
 pipe(
 effect(args),
 Effect.map((data) => ({ _tag: 'success' as const, data })), Effect.sandbox,
 Effect.catchAll(collectErrorDetails),
 ),
 ).then(remixThrow);
Capturing errors
If the effect fails, we retrieve errors data and related code.
- In dev mode, effect-errors will use sourcemaps to extract code excerpts related to the error.
 - In production however, we must fetch the map file (uploaded in our example on cloudflare R2), and read it to extract sources.
 
export const collectErrorDetails = <E>(cause: Cause<E>) =>
 pipe(
 Effect.gen(function* () { 
 const errorsText = prettyPrint(cause, { stripCwd: false }); console.error(errorsText);
 const { errors } = yield* captureErrors(cause); if (errors.every((e) => e.location !== undefined)) { 
 const errorsWithSources = yield* getErrorSourcesFromMapFile(errors);
 return yield* Effect.succeed({ _tag: 'effect-post-mapped-errors' as const,
 errors: errorsWithSources,
 });
 }
 
 return yield* Effect.succeed({ _tag: 'effect-natively-mapped-errors' as const,
 errors,
 });
 }),
 Effect.scoped,
 Effect.provide(FetchHttpClient.layer),
 Effect.withSpan('collect-error-details'), );
Throwing if there is an error
We need to pipe on the promise because remix expects us to throw a `json` function result from the loader for errors:
import { json } from '@remix-run/server-runtime';import { match } from 'ts-pattern';export interface EffectLoaderSuccess<A> { _tag: 'success';
 data: A;
}
export type EffectPostMappedErrors = { _tag: 'effect-post-mapped-errors';
 errors: EffectErrorWithSources[];
};
export type EffectNativelyMappedErrors = { _tag: 'effect-natively-mapped-errors';
 errors: ErrorData[];
};
export type EffectLoaderError =
 | EffectPostMappedErrors
 | EffectNativelyMappedErrors;
type RemixThrowInput<A> = EffectLoaderSuccess<A> | EffectLoaderError;
const effectHasSucceeded = <A>(
 p: RemixThrowInput<A>,
): p is EffectLoaderSuccess<A> => p._tag === 'success';
export const remixThrow = <A>(input: RemixThrowInput<A>) =>
 Match.value(input).pipe(
 Match.when(effectHasSucceeded, ({ data }) => data), Match.orElse((data) => { throw json(data, { status: 500 }); }),
 );
Finally, we can use an error boundary to display effect errors details. Let's start by creating a hook to transform error data:
import { isRouteErrorResponse,
 useLocation,
 useRouteError,
} from '@remix-run/react';
import type { EffectNativelyMappedErrors,
 EffectPostMappedErrors,
} from '@server/loader/types/effect-loader.types';
import { isUnknownAnEffectError } from './logic/is-uknown-an-effect-error.logic';import { mapEffectErrorTypes } from './logic/map-effect-error-types';export type EffectPostMappedErrorsWithPath = EffectPostMappedErrors & { path: string;
};
export type EffectNativelyMappedErrorsWithPath = EffectNativelyMappedErrors & { path: string;
};
export type ErrorsDetails =
 | { _tag: 'route' | 'error' | 'unknown';
 path: string;
 errors: { message: string;
 }[];
 }
 | EffectPostMappedErrorsWithPath
 | EffectNativelyMappedErrorsWithPath;
export const useErrorDetails = (): ErrorsDetails => { const { pathname } = useLocation(); const error = useRouteError();
 if (isUnknownAnEffectError(error)) { return mapEffectErrorTypes(error, pathname);
 }
 const isRoute = isRouteErrorResponse(error);
 if (isRoute) { return { _tag: 'route' as const,
 path: pathname,
 errors: [
 { message: error.statusText,
 },
 ],
 };
 }
 if (error instanceof Error) { return { _tag: 'error' as const,
 path: pathname,
 errors: [error],
 };
 }
 return { _tag: 'unknown' as const,
 path: pathname,
 errors: [{ message: 'Unknown Error' }], };
};
We can then use it to display our errors!
import { AppErrors } from './children/app-errors';import { Summary } from './children/summary';import { errorBoundaryStyles } from './error-boundary.styles';import { useErrorDetails } from './hooks/use-error-details';export const ErrorBoundary = () => { const css = errorBoundaryStyles();
 const data = useErrorDetails();
 return (
 <div className={css.root}> <Summary {...data} /> <AppErrors {...data} /> </div>
 );
};