From fe2f2926aaa56c3323c1480ca09c2734d187ed14 Mon Sep 17 00:00:00 2001 From: Brian Love Date: Tue, 12 May 2026 20:12:45 -0700 Subject: [PATCH] feat(website): branded 404 + error pages (Group D.2) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds two app-router special pages matching the Statusbrew aesthetic: - apps/website/src/app/not-found.tsx — server component, branded 404. Eyebrow "404" + h1 "Page not found." + helpful copy + two CTAs (Back home, Browse docs). Renders inside the standard Nav+Footer shell from layout.tsx. - apps/website/src/app/error.tsx — client component (required by Next.js for error boundaries). "Something went wrong." with the error digest visible, Try again button (calls `reset`), Back home link. loading.tsx intentionally NOT added: it would trigger Suspense streaming and commit the initial HTTP response with status 200 before notFound() calls could set 404. Confirmed locally — adding loading.tsx caused /solutions/unknown-slug to return 200 instead of 404, which would let bad URLs stay indexed in search. The branded loading state is low-value for this site (marketing routes are largely SSG) and not worth the SEO regression. All 35 website e2e tests pass — including the previously-flagged '/solutions/unknown-slug returns 404' test, which confirms the 404 status flows through the new not-found.tsx correctly. Co-Authored-By: Claude Opus 4.7 --- apps/website/src/app/error.tsx | 87 ++++++++++++++++++++++++++++++ apps/website/src/app/not-found.tsx | 58 ++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 apps/website/src/app/error.tsx create mode 100644 apps/website/src/app/not-found.tsx diff --git a/apps/website/src/app/error.tsx b/apps/website/src/app/error.tsx new file mode 100644 index 00000000..aeef6bf8 --- /dev/null +++ b/apps/website/src/app/error.tsx @@ -0,0 +1,87 @@ +'use client'; + +import { useEffect } from 'react'; +import { tokens } from '@ngaf/design-tokens'; +import { Container } from '../components/ui/Container'; +import { Section } from '../components/ui/Section'; +import { Eyebrow } from '../components/ui/Eyebrow'; +import { Button } from '../components/ui/Button'; + +interface ErrorBoundaryProps { + error: Error & { digest?: string }; + reset: () => void; +} + +/** + * App-router error boundary. Next.js renders this when a server or + * client component throws an uncaught error within this route segment. + * `reset` re-mounts the segment without a full reload. + */ +export default function ErrorBoundary({ error, reset }: ErrorBoundaryProps) { + useEffect(() => { + // Surface errors to whatever observability the app already wires up. + // Logging via console.error so it lands in browser devtools and any + // existing PostHog console-bridge or similar. + console.error('Unhandled error in app router:', error); + }, [error]); + + return ( +
+ +
+ Error +

+ Something went wrong. +

+

+ An unexpected error stopped this page from rendering. The team has been + notified. You can try again, or head back home. +

+ {error.digest ? ( +

+ Error ID: {error.digest} +

+ ) : ( +
+ )} +
+ + +
+
+ +
+ ); +} diff --git a/apps/website/src/app/not-found.tsx b/apps/website/src/app/not-found.tsx new file mode 100644 index 00000000..849f79bf --- /dev/null +++ b/apps/website/src/app/not-found.tsx @@ -0,0 +1,58 @@ +import { tokens } from '@ngaf/design-tokens'; +import { Container } from '../components/ui/Container'; +import { Section } from '../components/ui/Section'; +import { Eyebrow } from '../components/ui/Eyebrow'; +import { Button } from '../components/ui/Button'; + +export const metadata = { + title: 'Page not found — Angular Agent Framework', + description: 'The page you were looking for doesn’t exist.', +}; + +export default function NotFound() { + return ( +
+ +
+ 404 +

+ Page not found. +

+

+ The page you were looking for doesn't exist. It may have moved, or the link + you followed might be broken. +

+
+ + +
+
+
+
+ ); +}