Skip to content

feat(web): proper 404/500 pages + fix og:image URL mismatch#2441

Merged
andrew-bierman merged 1 commit into
developmentfrom
feat/web-error-pages
May 17, 2026
Merged

feat(web): proper 404/500 pages + fix og:image URL mismatch#2441
andrew-bierman merged 1 commit into
developmentfrom
feat/web-error-pages

Conversation

@andrew-bierman

@andrew-bierman andrew-bierman commented May 17, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to merged #2439. Two fixes addressing 'OG previews don't render' issue.

1. Root cause: live og:image returns 0 bytes / application/octet-stream because:

  • app/opengraph-image.tsx (Next.js file-route) was emitting /opengraph-image?<hash> URLs which don't render to real PNGs in output: 'export'
  • metadata.openGraph.images declared /opengraph-image.png which 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 that scripts/generate-og-images.ts writes). Now og:image emits https://siteurl/og-image.png — real PNG, correct image/png content-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 Lighthouse assertMatrix 404/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

    • Added global error page with recovery action.
    • Redesigned 404 and 500 error pages with improved UI, icons, and navigation options.
  • Improvements

    • Enhanced metadata and accessibility across error pages.
    • Consolidated image assets for Open Graph and social sharing.
    • Updated Lighthouse configuration for better assertions.

Review Change Stack

… 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>
Copilot AI review requested due to automatic review settings May 17, 2026 08:08
@github-actions github-actions Bot added the web label May 17, 2026
@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR consolidates Open Graph image handling to a single /og-image.png asset, enhances error pages with metadata and improved UI across both guides and landing apps, removes separate OG/Twitter image generation routes, adds static export document structure, and simplifies Lighthouse CI assertions.

Changes

Error Pages and OG Image Consolidation

Layer / File(s) Summary
OG Image URL Consolidation
apps/guides/lib/metadata.ts, apps/landing/lib/metadata.ts, apps/guides/__tests__/layout.metadata.test.ts, apps/landing/__tests__/layout.metadata.test.ts, apps/guides/__tests__/og-image.test.ts, apps/landing/__tests__/og-image.test.ts
Both apps update metadata exports and tests to use a single /og-image.png path instead of separate /opengraph-image.png and /twitter-image.png URLs, with tests allowing flexible URL matching via regex.
Error Pages: 404/500 with Metadata & UI
apps/guides/pages/404.tsx, apps/guides/pages/500.tsx, apps/landing/pages/404.tsx, apps/landing/pages/500.tsx
Custom error pages now set document metadata (title, description, robots noindex/nofollow) via next/head, use semantic main elements, mark icons as aria-hidden, and update UI copy and navigation links.
App Router: Not-Found & Global Error Components
apps/guides/app/not-found.tsx, apps/guides/app/global-error.tsx, apps/landing/app/not-found.tsx, apps/landing/app/global-error.tsx
New and redesigned error components in the app directory export metadata, use lucide icons, render improved UI layouts, and wire error recovery actions (Try again buttons linking to home or category views).
Document Structure for Static Export
apps/guides/pages/_document.tsx, apps/landing/pages/_document.tsx
Custom _document.tsx added to Pages Router to ensure statically exported HTML includes lang="en" attribute for Lighthouse/accessibility compliance.
Lighthouse CI Configuration Simplification
apps/guides/.lighthouserc.js, apps/guides/.lighthouserc.mobile.js, apps/landing/.lighthouserc.js, apps/landing/.lighthouserc.mobile.js
Refactored assertions from URL-pattern-scoped assertMatrix to direct assertions objects, keeping the same category thresholds and audit requirements but removing URL-based rule targeting logic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PackRat-AI/PackRat#2367: Prior work refactoring LHCI ci.assert configuration and OG-image URL assertions in the same files.

Suggested labels

web

Suggested reviewers

  • Isthisanmol
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately describes the two main changes: implementing proper 404/500 error pages and fixing the og:image URL mismatch from /opengraph-image.png to /og-image.png.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/web-error-pages

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

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between b0a75b0 and 88f44de.

📒 Files selected for processing (24)
  • apps/guides/.lighthouserc.js
  • apps/guides/.lighthouserc.mobile.js
  • apps/guides/__tests__/layout.metadata.test.ts
  • apps/guides/__tests__/og-image.test.ts
  • apps/guides/app/global-error.tsx
  • apps/guides/app/not-found.tsx
  • apps/guides/app/opengraph-image.tsx
  • apps/guides/app/twitter-image.tsx
  • apps/guides/lib/metadata.ts
  • apps/guides/pages/404.tsx
  • apps/guides/pages/500.tsx
  • apps/guides/pages/_document.tsx
  • apps/landing/.lighthouserc.js
  • apps/landing/.lighthouserc.mobile.js
  • apps/landing/__tests__/layout.metadata.test.ts
  • apps/landing/__tests__/og-image.test.ts
  • apps/landing/app/global-error.tsx
  • apps/landing/app/not-found.tsx
  • apps/landing/app/opengraph-image.tsx
  • apps/landing/app/twitter-image.tsx
  • apps/landing/lib/metadata.ts
  • apps/landing/pages/404.tsx
  • apps/landing/pages/500.tsx
  • apps/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

Comment thread apps/guides/pages/404.tsx
Comment on lines +19 to +95
<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&apos;ve wandered off the trail. This guide doesn&apos;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&apos;ve wandered off the trail. This guide doesn&apos;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>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Comment thread apps/guides/pages/500.tsx
Comment on lines +17 to +78
<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>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
@andrew-bierman andrew-bierman merged commit 7f7325e into development May 17, 2026
15 of 17 checks passed
@andrew-bierman andrew-bierman deleted the feat/web-error-pages branch May 17, 2026 15:10
andrew-bierman added a commit that referenced this pull request May 20, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants