feat: add error boundaries so a crash never blanks the whole app

No error.tsx existed anywhere in the app - an uncaught render error on
any page (a beekeeper's most field-critical moment: mid-inspection,
no signal, nowhere to go) showed nothing recoverable, not even a
message, let alone a way back to a working page.

Two boundaries, matching Next.js App Router's error-boundary scoping:
- (app)/error.tsx covers every authenticated page. It only replaces
  the crashed page's content - the sidebar/header/bottom nav stay
  mounted and usable, so the user can navigate to a different page
  without a full reload.
- app/error.tsx is the outer fallback (login, the public Herkunft
  page, or a crash in (app)/layout.tsx itself, which only a strictly
  higher boundary can catch). Its "Zum Dashboard" action is a plain
  <a> reload rather than client-side navigation, since a crash this
  high may have left client state corrupted.

Both are in German, matching the app's existing card/honeycomb visual
language, and explicitly reassure the user that already-saved data is
unaffected.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Sonnet 5 committed 2026-09-09 10:55:47 +02:00
1 parent 2e6846285d
commit 8bf9aa467d
2 files changed
+90

No files matched your search

+42
View File
@@ -0,0 +1,42 @@
"use client";
import { useEffect } from "react";
import { AlertTriangle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
export default function AppError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<main className="flex flex-col gap-4 p-4 sm:p-6">
<Card className="max-w-lg">
<CardHeader>
<div className="flex items-center gap-2.5">
<span className="flex size-9 items-center justify-center rounded-xl bg-destructive/10 text-destructive">
<AlertTriangle className="size-5" />
</span>
<CardTitle className="text-lg">Etwas ist schiefgelaufen</CardTitle>
</div>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<p className="text-sm text-muted-foreground">
Diese Seite konnte nicht angezeigt werden. Bereits gespeicherte Daten sind davon nicht
betroffen. Über die Navigation kommst du zu jeder anderen Seite.
</p>
<Button onClick={() => reset()} className="w-fit">
Erneut versuchen
</Button>
</CardContent>
</Card>
</main>
);
}
+48
View File
@@ -0,0 +1,48 @@
"use client";
import { useEffect } from "react";
import { AlertTriangle } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { HoneycombPattern } from "@/components/honeycomb-pattern";
export default function RootError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
console.error(error);
}, [error]);
return (
<main className="relative flex min-h-screen items-center justify-center overflow-hidden bg-background p-4">
<HoneycombPattern />
<Card className="relative w-full max-w-md shadow-[var(--shadow-elevated)]">
<CardHeader>
<div className="flex items-center gap-2.5">
<span className="flex size-9 items-center justify-center rounded-xl bg-destructive/10 text-destructive">
<AlertTriangle className="size-5" />
</span>
<CardTitle className="text-lg">Etwas ist schiefgelaufen</CardTitle>
</div>
</CardHeader>
<CardContent className="flex flex-col gap-4">
<p className="text-sm text-muted-foreground">
Es ist ein unerwarteter Fehler aufgetreten. Bereits gespeicherte Daten sind davon nicht
betroffen.
</p>
<div className="flex flex-wrap gap-2">
<Button onClick={() => reset()}>Erneut versuchen</Button>
{/* Plain reload, not client-side navigation - a crash here may
have left the app's client state corrupted, so this must
guarantee a fully clean slate rather than route through it. */}
<Button variant="outline" render={<a href="/dashboard">Zum Dashboard</a>} />
</div>
</CardContent>
</Card>
</main>
);
}