diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 3cae676..128ddd7 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -1,9 +1,10 @@
import { lazy, Suspense } from 'react'
-import { BrowserRouter, Routes, Route, Navigate, Outlet } from 'react-router-dom'
+import { BrowserRouter, Routes, Route, Navigate, Outlet, useLocation } from 'react-router-dom'
import { AuthProvider, useAuth } from './context/AuthContext'
import { ThemeProvider } from './context/ThemeContext'
import Navbar from './components/Navbar'
import SiteFooter from './components/SiteFooter'
+import ErrorBoundary from './components/ErrorBoundary'
const LoginPage = lazy(() => import('./pages/LoginPage'))
const RegisterPage = lazy(() => import('./pages/RegisterPage'))
@@ -54,11 +55,15 @@ function LoadingFallback() {
// Layout wrapper for authenticated app pages (Navbar + container + footer)
function AppLayout() {
+ // Keyed by path so navigating away from a broken page clears the error.
+ const location = useLocation()
return (
<>
-
+
+
+
>
diff --git a/frontend/src/components/ErrorBoundary.css b/frontend/src/components/ErrorBoundary.css
new file mode 100644
index 0000000..7b5fdb1
--- /dev/null
+++ b/frontend/src/components/ErrorBoundary.css
@@ -0,0 +1,13 @@
+/* A failure the reader can see, report, and get out of. */
+.eb-card {
+ max-width: 640px; margin: 48px auto; padding: 26px;
+ background: var(--card-bg); border: 1px solid var(--wrong-bd); border-radius: 12px;
+}
+.eb-card h1 { margin: 0 0 8px; font-size: 1.2rem; color: var(--wrong-fg); }
+.eb-card p { margin: 0 0 14px; color: var(--text-muted); font-size: 0.9rem; }
+.eb-card pre {
+ margin: 0 0 16px; padding: 12px; overflow-x: auto;
+ background: var(--bg); border: 1px solid var(--border); border-radius: 8px;
+ font-size: 0.8rem; color: var(--text-muted); white-space: pre-wrap;
+}
+.eb-actions { display: flex; gap: 8px; flex-wrap: wrap; }
diff --git a/frontend/src/components/ErrorBoundary.jsx b/frontend/src/components/ErrorBoundary.jsx
new file mode 100644
index 0000000..66fc6d7
--- /dev/null
+++ b/frontend/src/components/ErrorBoundary.jsx
@@ -0,0 +1,42 @@
+import { Component } from 'react'
+import './ErrorBoundary.css'
+
+/**
+ * Catch a render error and say so, rather than leaving a white page.
+ *
+ * Without this a single thrown error unmounts the whole tree — navbar included —
+ * and the reader gets a blank screen with nothing to act on and nothing to
+ * report. What broke is usually one page; the rest of the app still works, and
+ * the message is what makes the difference between "it's broken" and a bug
+ * anyone can chase.
+ */
+export default class ErrorBoundary extends Component {
+ constructor(props) {
+ super(props)
+ this.state = { error: null }
+ }
+
+ static getDerivedStateFromError(error) {
+ return { error }
+ }
+
+ componentDidCatch(error, info) {
+ // The console is where a developer will look; keep the component trail.
+ console.error('Render failed:', error, info?.componentStack)
+ }
+
+ render() {
+ if (!this.state.error) return this.props.children
+ return (
+
+
This page failed to load
+
The rest of the app is still working — the error was on this screen.