diff --git a/apps/journal/app/root.tsx b/apps/journal/app/root.tsx
index ca53230..f68979f 100644
--- a/apps/journal/app/root.tsx
+++ b/apps/journal/app/root.tsx
@@ -1,5 +1,6 @@
-import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
+import { Links, Meta, Outlet, Scripts, ScrollRestoration, isRouteErrorResponse } from "react-router";
import type { LinksFunction } from "react-router";
+import type { Route } from "./+types/root";
import stylesheet from "@trails-cool/ui/styles.css?url";
export const links: LinksFunction = () => [{ rel: "stylesheet", href: stylesheet }];
@@ -25,3 +26,33 @@ export function Layout({ children }: { children: React.ReactNode }) {
export default function App() {
return ;
}
+
+export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
+ if (isRouteErrorResponse(error)) {
+ return (
+
+
{error.status}
+
+ {error.status === 404 && "Page not found"}
+ {error.status === 503 && "Service temporarily unavailable. Please try again later."}
+ {error.status !== 404 && error.status !== 503 && (error.statusText || "Something went wrong")}
+
+
+ Go home
+
+
+ );
+ }
+
+ return (
+
+
Error
+
+ {error instanceof Error ? error.message : "An unexpected error occurred"}
+
+
+ Go home
+
+
+ );
+}
diff --git a/apps/planner/app/root.tsx b/apps/planner/app/root.tsx
index 17b1eb6..bf39ded 100644
--- a/apps/planner/app/root.tsx
+++ b/apps/planner/app/root.tsx
@@ -1,5 +1,6 @@
-import { Links, Meta, Outlet, Scripts, ScrollRestoration } from "react-router";
+import { Links, Meta, Outlet, Scripts, ScrollRestoration, isRouteErrorResponse } from "react-router";
import type { LinksFunction } from "react-router";
+import type { Route } from "./+types/root";
import stylesheet from "@trails-cool/ui/styles.css?url";
export const links: LinksFunction = () => [{ rel: "stylesheet", href: stylesheet }];
@@ -25,3 +26,31 @@ export function Layout({ children }: { children: React.ReactNode }) {
export default function App() {
return ;
}
+
+export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
+ if (isRouteErrorResponse(error)) {
+ return (
+
+
+
{error.status}
+
+ {error.status === 404 && "Page not found"}
+ {error.status === 503 && "Service temporarily unavailable"}
+ {error.status !== 404 && error.status !== 503 && (error.statusText || "Something went wrong")}
+
+
+
+ );
+ }
+
+ return (
+
+
+
Error
+
+ {error instanceof Error ? error.message : "An unexpected error occurred"}
+
+
+
+ );
+}
diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts
index df18f2e..26f84fa 100644
--- a/packages/db/src/index.ts
+++ b/packages/db/src/index.ts
@@ -20,23 +20,22 @@ export type Database = ReturnType;
*/
export function withDb(handler: () => Promise): Promise {
return handler().catch((error) => {
- // Re-throw React Router responses (redirects, data() throws)
- // Check for Response, ErrorResponseImpl, or anything with a status property
+ // Re-throw anything that looks like a React Router response:
+ // - Response (redirects)
+ // - ErrorResponseImpl from data() throws (has status + data)
if (
error instanceof Response ||
- (error && typeof error === "object" && "status" in error && "data" in error)
+ (error != null && typeof error === "object" && "status" in error)
) {
throw error;
}
- // Any other error from a DB-wrapped handler is treated as DB unavailable
+ // Database error — throw as a 503 that the error boundary will catch
const message = error instanceof Error ? error.message : String(error);
console.error("[withDb] Database error:", message);
- throw new Response(JSON.stringify({ error: "Database unavailable" }), {
- status: 503,
- headers: { "Content-Type": "application/json" },
- });
+ // Use the same shape as data() throw so isRouteErrorResponse works
+ throw new Response("Database unavailable", { status: 503, statusText: "Service Unavailable" });
});
}