'use client';
import { useEffect, useRef, useSyncExternalStore } from 'react';
/** The `?debug=1` flag lives in the URL, which React does not own. */
const subscribeToNothing = () => () => {};
const readDebugFlag = () =>
new URLSearchParams(window.location.search).get('debug') === '1';
// Server snapshot: the flag is unknowable until there is a `window`, and claiming `false`
// keeps server and client output identical until React re-renders on the client.
const debugFlagOnServer = () => false;
interface ErrorFallbackProps {
error: Error & { digest?: string };
reset: () => void;
}
/**
* What a visitor sees when a client-side exception escapes to an error boundary.
*
* Without a boundary Next.js renders its own bare "Application error: a client-side
* exception has occurred" stub, which tells the visitor nothing and — more importantly —
* leaves us with no record that anything happened at all.
*
* Raw exception text is deliberately **not** shown by default. Next.js strips server error
* messages in production and substitutes `digest` precisely so internals do not leak, and
* this is a public site. Visitors get the digest as a short reference; the full message and
* stack go to the server log, and are additionally rendered inline when the URL carries
* `?debug=1` so the fault can be read straight off a phone or tablet with no devtools.
*/
export default function ErrorFallback({ error, reset }: ErrorFallbackProps) {
const showDetail = useSyncExternalStore(
subscribeToNothing,
readDebugFlag,
debugFlagOnServer,
);
// Reporting is idempotent per mount: React may re-render the boundary (e.g. Strict Mode
// double-invokes effects in development) and one fault should produce one log line.
const reported = useRef(false);
useEffect(() => {
if (reported.current) return;
reported.current = true;
// Fire-and-forget. A failure to report must never surface to the visitor or throw from
// inside the boundary that is already handling an error. `keepalive` lets the request
// outlive the page if they navigate away immediately.
try {
void fetch('/api/client-error', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: error.message,
stack: error.stack,
digest: error.digest,
pathname: window.location.pathname + window.location.search,
userAgent: navigator.userAgent,
}),
keepalive: true,
}).catch(() => {});
} catch {
// Ignore: serialisation or a blocked request must not escalate.
}
}, [error]);
return (
Something went wrong
This page hit an unexpected error. Trying again usually works — the rankings and
forecast data themselves are unaffected.
{/* Deliberately a plain anchor, not next/link. The rule exists to avoid full
page reloads, but a full reload is the point here: client-side navigation
would route within the same broken runtime the boundary just caught, and
`global-error` renders when the root layout itself failed. */}
{/* eslint-disable-next-line @next/next/no-html-link-for-pages */}
Back to homepage
{error.digest && (
Reference: {error.digest}
Quote this if you report the problem to us.