feat(web): proper 404/500 pages + fix og:image URL mismatch#2441
Conversation
… landing)
Two layered fixes:
1. Proper error pages with metadata exports so Lighthouse passes them naturally.
2. Fix og:image URL mismatch — root cause of broken OG previews:
- app/opengraph-image.tsx file-route was emitting /opengraph-image?<hash>
which serves 0 bytes in static export.
- metadata.openGraph.images pointed to /opengraph-image.png which doesn't exist.
- Fix: delete the file-routes, point metadata.ts at /og-image.png (the real
PNG produced by scripts/generate-og-images.ts).
3. Drop Lighthouse 404/500 exclusion (proper pages pass naturally).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WalkthroughThis PR consolidates Open Graph image handling to a single ChangesError Pages and OG Image Consolidation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/guides/pages/404.tsx`:
- Around line 19-95: The 404 page currently uses raw HTML elements and inline
styles (the <main> wrapper, headings, <p> text, <a> links and the Backpack icon
usage) which violates the Guides UI rule to use Radix/Shadcn components; replace
these primitives with the app's design-system components (e.g., Container/Layout
component instead of the <main>, Typography/Heading/Paragraph components for the
h1 and <p>, Icon wrapper for Backpack, and Button/Link components for the two
actions) and remove inline style blobs, applying existing shadcn CSS
classes/variants or Tailwind utility classes via those components so the page
conforms to the Guides UI component standard and theme.
In `@apps/guides/pages/500.tsx`:
- Around line 17-78: Replace the raw HTML and inline styles with the project's
Radix/Shadcn component stack: remove the <main>, layout divs, headings,
paragraphs and the raw <a> and instead compose the UI using the Shadcn/Card,
Shadcn/Stack (or Container), and Shadcn/Button components (keeping the
AlertTriangle icon but use the project's Icons export if available), and switch
inline style blocks to Tailwind classes via className; update imports and
consume Card/Stack/Button where the 500 page currently uses <main>, the outer
wrapper div, the header <h1>, descriptive <p> and the CTA <a> so the page
follows the apps/guides Radix/Shadcn pattern and theme.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 185f8eee-556b-4340-bf16-752247971c0c
📒 Files selected for processing (24)
apps/guides/.lighthouserc.jsapps/guides/.lighthouserc.mobile.jsapps/guides/__tests__/layout.metadata.test.tsapps/guides/__tests__/og-image.test.tsapps/guides/app/global-error.tsxapps/guides/app/not-found.tsxapps/guides/app/opengraph-image.tsxapps/guides/app/twitter-image.tsxapps/guides/lib/metadata.tsapps/guides/pages/404.tsxapps/guides/pages/500.tsxapps/guides/pages/_document.tsxapps/landing/.lighthouserc.jsapps/landing/.lighthouserc.mobile.jsapps/landing/__tests__/layout.metadata.test.tsapps/landing/__tests__/og-image.test.tsapps/landing/app/global-error.tsxapps/landing/app/not-found.tsxapps/landing/app/opengraph-image.tsxapps/landing/app/twitter-image.tsxapps/landing/lib/metadata.tsapps/landing/pages/404.tsxapps/landing/pages/500.tsxapps/landing/pages/_document.tsx
💤 Files with no reviewable changes (4)
- apps/guides/app/twitter-image.tsx
- apps/landing/app/twitter-image.tsx
- apps/guides/app/opengraph-image.tsx
- apps/landing/app/opengraph-image.tsx
| <main | ||
| style={{ | ||
| minHeight: '100vh', | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| background: '#0f172a', | ||
| fontFamily: 'system-ui, sans-serif', | ||
| }} | ||
| > | ||
| <div style={{ textAlign: 'center', padding: '0 1rem' }}> | ||
| <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '1.5rem' }}> | ||
| <div | ||
| style={{ | ||
| background: 'rgba(15,118,110,0.15)', | ||
| borderRadius: '9999px', | ||
| padding: '1.5rem', | ||
| display: 'inline-flex', | ||
| }} | ||
| > | ||
| <Backpack size={48} color="#14b8a6" aria-hidden="true" /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| <h1 | ||
| style={{ | ||
| fontSize: '5rem', | ||
| fontWeight: 800, | ||
| color: '#f8fafc', | ||
| margin: '0 0 0.5rem', | ||
| lineHeight: 1, | ||
| }} | ||
| > | ||
| 404 | ||
| </h1> | ||
| <p style={{ fontSize: '1.25rem', fontWeight: 600, color: '#f8fafc', margin: '0 0 0.5rem' }}> | ||
| Guide not found | ||
| </p> | ||
| <p style={{ color: '#94a3b8', maxWidth: '22rem', margin: '0 auto 2rem' }}> | ||
| Looks like you've wandered off the trail. This guide doesn't exist. | ||
| </p> | ||
| <div | ||
| style={{ display: 'flex', flexWrap: 'wrap', gap: '0.75rem', justifyContent: 'center' }} | ||
| > | ||
| <a | ||
| href="/" | ||
| <h1 | ||
| style={{ | ||
| background: '#0f766e', | ||
| color: '#fff', | ||
| padding: '0.625rem 1.5rem', | ||
| borderRadius: '0.5rem', | ||
| fontWeight: 600, | ||
| textDecoration: 'none', | ||
| fontSize: '1rem', | ||
| fontSize: '5rem', | ||
| fontWeight: 800, | ||
| color: '#f8fafc', | ||
| margin: '0 0 0.5rem', | ||
| lineHeight: 1, | ||
| }} | ||
| > | ||
| Back to guides | ||
| </a> | ||
| 404 | ||
| </h1> | ||
| <p | ||
| style={{ fontSize: '1.25rem', fontWeight: 600, color: '#f8fafc', margin: '0 0 0.5rem' }} | ||
| > | ||
| Guide not found | ||
| </p> | ||
| <p style={{ color: '#94a3b8', maxWidth: '22rem', margin: '0 auto 2rem' }}> | ||
| Looks like you've wandered off the trail. This guide doesn't exist or has | ||
| moved. | ||
| </p> | ||
| <div | ||
| style={{ display: 'flex', flexWrap: 'wrap', gap: '0.75rem', justifyContent: 'center' }} | ||
| > | ||
| <a | ||
| href="/" | ||
| style={{ | ||
| background: '#0f766e', | ||
| color: '#fff', | ||
| padding: '0.625rem 1.5rem', | ||
| borderRadius: '0.5rem', | ||
| fontWeight: 600, | ||
| textDecoration: 'none', | ||
| fontSize: '1rem', | ||
| }} | ||
| > | ||
| Return to all guides | ||
| </a> | ||
| <a | ||
| href="/?category=gear" | ||
| style={{ | ||
| border: '1px solid #334155', | ||
| color: '#f8fafc', | ||
| padding: '0.625rem 1.5rem', | ||
| borderRadius: '0.5rem', | ||
| fontWeight: 600, | ||
| textDecoration: 'none', | ||
| fontSize: '1rem', | ||
| }} | ||
| > | ||
| Browse by category | ||
| </a> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| </main> |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Use Radix/Shadcn components for this new Guides UI.
This page introduces new UI using raw HTML + inline styles, which diverges from the Guides UI component standard.
As per coding guidelines, apps/guides/**: "Use Radix UI / Shadcn components for all new UI."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/guides/pages/404.tsx` around lines 19 - 95, The 404 page currently uses
raw HTML elements and inline styles (the <main> wrapper, headings, <p> text, <a>
links and the Backpack icon usage) which violates the Guides UI rule to use
Radix/Shadcn components; replace these primitives with the app's design-system
components (e.g., Container/Layout component instead of the <main>,
Typography/Heading/Paragraph components for the h1 and <p>, Icon wrapper for
Backpack, and Button/Link components for the two actions) and remove inline
style blobs, applying existing shadcn CSS classes/variants or Tailwind utility
classes via those components so the page conforms to the Guides UI component
standard and theme.
| <main | ||
| style={{ | ||
| minHeight: '100vh', | ||
| display: 'flex', | ||
| alignItems: 'center', | ||
| justifyContent: 'center', | ||
| background: '#0f172a', | ||
| fontFamily: 'system-ui, sans-serif', | ||
| }} | ||
| > | ||
| <div style={{ textAlign: 'center', padding: '0 1rem' }}> | ||
| <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '1.5rem' }}> | ||
| <div | ||
| style={{ | ||
| background: 'rgba(239,68,68,0.15)', | ||
| borderRadius: '9999px', | ||
| padding: '1.5rem', | ||
| display: 'inline-flex', | ||
| }} | ||
| > | ||
| <AlertTriangle size={48} color="#ef4444" aria-hidden="true" /> | ||
| </div> | ||
| </div> | ||
| <h1 | ||
| style={{ | ||
| fontSize: '5rem', | ||
| fontWeight: 800, | ||
| color: '#f8fafc', | ||
| margin: '0 0 0.5rem', | ||
| lineHeight: 1, | ||
| }} | ||
| > | ||
| 500 | ||
| </h1> | ||
| <p | ||
| style={{ fontSize: '1.25rem', fontWeight: 600, color: '#f8fafc', margin: '0 0 0.5rem' }} | ||
| > | ||
| Something went wrong | ||
| </p> | ||
| <p style={{ color: '#94a3b8', maxWidth: '22rem', margin: '0 auto 2rem' }}> | ||
| We hit an unexpected snag on our end. Try reloading, or head back to all guides. | ||
| </p> | ||
| <div | ||
| style={{ display: 'flex', flexWrap: 'wrap', gap: '0.75rem', justifyContent: 'center' }} | ||
| > | ||
| <a | ||
| href="/" | ||
| style={{ | ||
| background: '#0f766e', | ||
| color: '#fff', | ||
| padding: '0.625rem 1.5rem', | ||
| borderRadius: '0.5rem', | ||
| fontWeight: 600, | ||
| textDecoration: 'none', | ||
| fontSize: '1rem', | ||
| }} | ||
| > | ||
| Return to all guides | ||
| </a> | ||
| </div> | ||
| </div> | ||
| </main> |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win
Align this new Guides error UI with Radix/Shadcn components.
The new 500 UI is implemented with raw HTML and inline styles instead of the required Guides component stack.
As per coding guidelines, apps/guides/**: "Use Radix UI / Shadcn components for all new UI."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/guides/pages/500.tsx` around lines 17 - 78, Replace the raw HTML and
inline styles with the project's Radix/Shadcn component stack: remove the
<main>, layout divs, headings, paragraphs and the raw <a> and instead compose
the UI using the Shadcn/Card, Shadcn/Stack (or Container), and Shadcn/Button
components (keeping the AlertTriangle icon but use the project's Icons export if
available), and switch inline style blocks to Tailwind classes via className;
update imports and consume Card/Stack/Button where the 500 page currently uses
<main>, the outer wrapper div, the header <h1>, descriptive <p> and the CTA <a>
so the page follows the apps/guides Radix/Shadcn pattern and theme.
There was a problem hiding this comment.
Pull request overview
This PR hardens the landing and guides web apps for static export by replacing generated OG image routes with static /og-image.png metadata and adding proper 404/500/error page handling so Lighthouse exclusions can be removed.
Changes:
- Points Open Graph/Twitter metadata and tests at the generated static
/og-image.png. - Removes App Router OG image file routes that do not serve correctly under static export.
- Adds/updates 404, 500, global error,
_document, and Lighthouse config coverage for both web apps.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| apps/landing/pages/500.tsx | Adds metadata and structured 500 page markup. |
| apps/landing/pages/404.tsx | Adds metadata and structured 404 page markup. |
| apps/landing/pages/_document.tsx | Adds lang="en" for Pages Router static exports. |
| apps/landing/lib/metadata.ts | Switches OG/Twitter image metadata to /og-image.png. |
| apps/landing/app/twitter-image.tsx | Removes generated Twitter image route. |
| apps/landing/app/opengraph-image.tsx | Removes generated Open Graph image route. |
| apps/landing/app/not-found.tsx | Expands App Router not-found page with metadata and CTAs. |
| apps/landing/app/global-error.tsx | Adds self-contained global error document. |
| apps/landing/.lighthouserc.mobile.js | Re-enables assertions for all collected mobile pages. |
| apps/landing/.lighthouserc.js | Re-enables assertions for all collected desktop pages. |
| apps/landing/tests/og-image.test.ts | Updates metadata expectations for static OG image URL. |
| apps/landing/tests/layout.metadata.test.ts | Updates absolute metadata URL assertions. |
| apps/guides/pages/500.tsx | Replaces null static 500 page with full page markup and metadata. |
| apps/guides/pages/404.tsx | Adds metadata and enhanced static 404 page markup. |
| apps/guides/pages/_document.tsx | Adds lang="en" for Pages Router static exports. |
| apps/guides/lib/metadata.ts | Switches OG/Twitter image metadata to /og-image.png. |
| apps/guides/app/twitter-image.tsx | Removes generated Twitter image route. |
| apps/guides/app/opengraph-image.tsx | Removes generated Open Graph image route. |
| apps/guides/app/not-found.tsx | Expands App Router not-found page with metadata and CTAs. |
| apps/guides/app/global-error.tsx | Adds self-contained global error document. |
| apps/guides/.lighthouserc.mobile.js | Re-enables assertions for all collected mobile pages. |
| apps/guides/.lighthouserc.js | Re-enables assertions for all collected desktop pages. |
| apps/guides/tests/og-image.test.ts | Updates metadata expectations for static OG image URL. |
| apps/guides/tests/layout.metadata.test.ts | Updates absolute metadata URL assertions. |
Comments suppressed due to low confidence (2)
apps/landing/app/global-error.tsx:86
- The primary button uses white text on
#0a84ff, which is below the WCAG AA 4.5:1 contrast requirement for this normal-sized text. Darken the blue or use a foreground color with sufficient contrast so the global error action remains accessible.
background: '#0a84ff',
color: '#fff',
apps/guides/app/global-error.tsx:86
- The primary button uses white text on
#0a84ff, which is below the WCAG AA 4.5:1 contrast requirement for this normal-sized text. Darken the blue or use a foreground color with sufficient contrast so the global error action remains accessible.
background: '#0a84ff',
color: '#fff',
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| fontWeight: 600, | ||
| letterSpacing: '0.2em', | ||
| textTransform: 'uppercase', | ||
| color: '#0a84ff', |
| fontWeight: 600, | ||
| letterSpacing: '0.2em', | ||
| textTransform: 'uppercase', | ||
| color: '#0a84ff', |
| </Button> | ||
| <main className="flex flex-1 items-center justify-center px-4 py-24"> | ||
| <div className="mx-auto max-w-xl text-center"> | ||
| <p className="text-sm font-medium uppercase tracking-widest text-primary">404</p> |
| </Button> | ||
| <div className="container flex flex-1 items-center justify-center px-4 py-24"> | ||
| <div className="mx-auto max-w-xl text-center"> | ||
| <p className="text-sm font-medium uppercase tracking-widest text-apple-blue">404</p> |
Substantial rebase covering 225 dev commits — #2414 type unification, #2422 single-param refactor, #2433 MCP+CLI Eden Treaty rewrite, #2439 OG meta validation, #2441/#2442 OG URL fix, plus many smaller. Conflicts resolved: - apps/expo/features/packs/utils/uploadImage.ts: kept HEAD's userId cache, used dev's object-arg getPresignedUrl call (matches function signature). - apps/expo/features/trips/hooks/useDeleteTrip.ts: kept HEAD's async + optimistic-delete comment, used dev's object-arg obs() call (matches current obs signature in apps/expo/lib/store.ts). Post-merge cleanup of dev-introduced single-param violations: - apps/expo/lib/utils/__tests__/getRelativeTime.test.ts: rewrote 3 test call sites to object args matching the refactored getRelativeTime. - packages/api/src/utils/__tests__/embeddingHelper.test.ts: rewrote 7 test call sites to object args matching the refactored getEmbeddingText; updated Parameters<> type indexes. - packages/overpass/src/client.test.ts: converted makeResponse to single object param and updated all 11 call sites. - scripts/lint/no-owned-max-params.ts: added apps/trails/scripts/generate-og-images.ts to EXCLUDED_FILES (same globalThis.fetch shim pattern as the existing landing/guides entries). Verification: bun install ok; bun check-types 0 errors; biome check 0 errors (2 unrelated warnings); no-owned-max-params 0 violations. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Follow-up to merged #2439. Two fixes addressing 'OG previews don't render' issue.
1. Root cause: live
og:imagereturns 0 bytes /application/octet-streambecause:app/opengraph-image.tsx(Next.js file-route) was emitting/opengraph-image?<hash>URLs which don't render to real PNGs inoutput: 'export'metadata.openGraph.imagesdeclared/opengraph-image.pngwhich doesn't exist (script writes/og-image.png)Fix: delete file-routes in both apps, point metadata at the real
/og-image.png(138 KB PNG thatscripts/generate-og-images.tswrites). Nowog:imageemitshttps://siteurl/og-image.png— real PNG, correctimage/pngcontent-type. Scrapers work.2. Proper 404/500 pages: real metadata exports (title, description, robots), self-contained
<html lang>in global-error.tsx. Replaces #2439's LighthouseassertMatrix404/500 exclusion.3. Drop Lighthouse exclusion: error pages now pass naturally.
Verified locally: typecheck + biome clean, all guides + landing tests pass, build emits
og:image→/og-image.png.Co-Authored-By: Claude Opus 4.7 (1M context) noreply@anthropic.com
Summary by CodeRabbit
New Features
Improvements