Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 14 additions & 24 deletions apps/guides/.lighthouserc.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,20 @@ module.exports = {
},
},
assert: {
assertMatrix: [
{
// Next.js error pages (404/500) inherently lack <title>, <html lang>,
// and meta description. They're never user-shareable; skip them.
matchingUrlPattern: '.*/(404|500)\\.html$',
},
{
matchingUrlPattern: '.*\\.html$',
assertions: {
'categories:performance': ['error', { minScore: 0.8 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:best-practices': ['error', { minScore: 0.9 }],
'categories:seo': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'total-blocking-time': ['error', { maxNumericValue: 300 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'meta-description': 'error',
'document-title': 'error',
'html-has-lang': 'error',
'image-alt': 'error',
},
},
],
assertions: {
'categories:performance': ['error', { minScore: 0.8 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:best-practices': ['error', { minScore: 0.9 }],
'categories:seo': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: 2000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 2500 }],
'total-blocking-time': ['error', { maxNumericValue: 300 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'meta-description': 'error',
'document-title': 'error',
'html-has-lang': 'error',
'image-alt': 'error',
},
},
upload: {
target: 'temporary-public-storage',
Expand Down
36 changes: 14 additions & 22 deletions apps/guides/.lighthouserc.mobile.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,20 @@ module.exports = {
},
},
assert: {
assertMatrix: [
{
matchingUrlPattern: '.*/(404|500)\\.html$',
},
{
matchingUrlPattern: '.*\\.html$',
assertions: {
'categories:performance': ['error', { minScore: 0.8 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:best-practices': ['error', { minScore: 0.9 }],
'categories:seo': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: 3000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 4000 }],
'total-blocking-time': ['error', { maxNumericValue: 600 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'meta-description': 'error',
'document-title': 'error',
'html-has-lang': 'error',
'image-alt': 'error',
},
},
],
assertions: {
'categories:performance': ['error', { minScore: 0.8 }],
'categories:accessibility': ['error', { minScore: 0.9 }],
'categories:best-practices': ['error', { minScore: 0.9 }],
'categories:seo': ['error', { minScore: 0.9 }],
'first-contentful-paint': ['error', { maxNumericValue: 3000 }],
'largest-contentful-paint': ['error', { maxNumericValue: 4000 }],
'total-blocking-time': ['error', { maxNumericValue: 600 }],
'cumulative-layout-shift': ['error', { maxNumericValue: 0.1 }],
'meta-description': 'error',
'document-title': 'error',
'html-has-lang': 'error',
'image-alt': 'error',
},
},
upload: {
target: 'temporary-public-storage',
Expand Down
6 changes: 2 additions & 4 deletions apps/guides/__tests__/layout.metadata.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { guidesMetadata as metadata } from '../lib/metadata';

describe('guides metadata', () => {
it('includes absolute Open Graph and Twitter image URLs', () => {
const expectedImageUrl = new URL('/opengraph-image.png', siteConfig.url).toString();
const expectedImageUrl = new URL('/og-image.png', siteConfig.url).toString();

expect(metadata.openGraph?.images).toEqual([
{
Expand All @@ -15,8 +15,6 @@ describe('guides metadata', () => {
},
]);

expect(metadata.twitter?.images).toEqual([
new URL('/twitter-image.png', siteConfig.url).toString(),
]);
expect(metadata.twitter?.images).toEqual([new URL('/og-image.png', siteConfig.url).toString()]);
});
});
4 changes: 2 additions & 2 deletions apps/guides/__tests__/og-image.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,14 @@ describe('guides layout metadata', () => {
expect(images).toBeDefined();
const first = Array.isArray(images) ? images[0] : images;
const url = typeof first === 'string' ? first : (first as { url: string })?.url;
expect(url).toBe('/og-image.png');
expect(url).toMatch(/\/og-image\.png$/);
});

it('includes twitter.images pointing to /og-image.png', () => {
const images = (guidesMetadata.twitter as { images?: unknown })?.images;
expect(images).toBeDefined();
const first = Array.isArray(images) ? images[0] : images;
expect(first).toBe('/og-image.png');
expect(first).toMatch(/\/og-image\.png$/);
});
});

Expand Down
114 changes: 114 additions & 0 deletions apps/guides/app/global-error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
'use client';

/**
* Next.js global-error replaces the root layout when an error escapes it,
* so this component renders its own <html> and <body>. Styles are inlined
* so a failed stylesheet can't cascade into a blank page.
*/
export default function GlobalError({
error: _error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html lang="en">
<head>
<title>Something went wrong</title>
<meta
name="description"
content="An unexpected error occurred while loading PackRat Guides. Try reloading the page or returning to all guides."
/>
<meta name="robots" content="noindex,nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body
style={{
margin: 0,
minHeight: '100vh',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
padding: '1.5rem',
fontFamily:
"-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif",
color: '#111',
background: '#fff',
}}
>
<main style={{ maxWidth: '32rem', textAlign: 'center' }}>
<p
style={{
fontSize: '0.75rem',
fontWeight: 600,
letterSpacing: '0.2em',
textTransform: 'uppercase',
color: '#0a84ff',
margin: 0,
}}
>
500
</p>
<h1
style={{
fontSize: '2.25rem',
fontWeight: 600,
margin: '0.75rem 0 0',
lineHeight: 1.15,
}}
>
Something went wrong
</h1>
<p style={{ marginTop: '1rem', fontSize: '1.0625rem', color: '#555', lineHeight: 1.5 }}>
An unexpected error occurred while loading this page. You can try again, or head back to
all PackRat guides.
</p>
<div
style={{
marginTop: '2rem',
display: 'flex',
gap: '0.75rem',
justifyContent: 'center',
flexWrap: 'wrap',
}}
>
<button
type="button"
onClick={() => reset()}
style={{
appearance: 'none',
border: 'none',
cursor: 'pointer',
padding: '0.625rem 1.25rem',
borderRadius: '9999px',
background: '#0a84ff',
color: '#fff',
fontSize: '0.9375rem',
fontWeight: 500,
}}
>
Try again
</button>
<a
href="/"
style={{
display: 'inline-flex',
alignItems: 'center',
padding: '0.625rem 1.25rem',
borderRadius: '9999px',
border: '1px solid #d2d2d7',
color: '#111',
fontSize: '0.9375rem',
fontWeight: 500,
textDecoration: 'none',
}}
>
Return to all guides
</a>
</div>
</main>
</body>
</html>
);
}
38 changes: 31 additions & 7 deletions apps/guides/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
import { Button } from '@packrat/web-ui/components/button';
import { Compass, Home } from 'lucide-react';
import type { Metadata } from 'next';
import Link from 'next/link';

export const metadata: Metadata = {
title: 'Page not found',
description:
"We couldn't find the guide you were looking for. Head back to all PackRat guides or explore a different topic.",
robots: { index: false, follow: false },
};

export default function NotFound() {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="text-center space-y-4">
<h1 className="text-4xl font-bold">404</h1>
<p className="text-lg text-muted-foreground">Page not found</p>
<Button asChild>
<Link href="/">Go back home</Link>
</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>
<h1 className="mt-3 text-4xl font-semibold tracking-tight sm:text-5xl">Page not found</h1>
<p className="mt-4 text-lg text-muted-foreground">
The guide you were looking for may have been moved, renamed, or never existed. Try heading
back to all guides or browsing by category.
</p>
<div className="mt-8 flex flex-col items-center justify-center gap-3 sm:flex-row">
<Button asChild size="lg">
<Link href="/">
<Home className="mr-2 h-4 w-4" aria-hidden="true" />
Return to all guides
</Link>
</Button>
<Button asChild size="lg" variant="outline">
<Link href="/?category=gear">
<Compass className="mr-2 h-4 w-4" aria-hidden="true" />
Browse by category
</Link>
</Button>
</div>
</div>
</div>
);
Expand Down
14 changes: 0 additions & 14 deletions apps/guides/app/opengraph-image.tsx

This file was deleted.

14 changes: 0 additions & 14 deletions apps/guides/app/twitter-image.tsx

This file was deleted.

4 changes: 2 additions & 2 deletions apps/guides/lib/metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export const guidesMetadata: Metadata = {
description: 'Expert hiking and outdoor guides to help you prepare for your next adventure',
images: [
{
url: new URL('/opengraph-image.png', siteConfig.url).toString(),
url: new URL('/og-image.png', siteConfig.url).toString(),
width: 1200,
height: 630,
alt: 'PackRat Guides | Hiking & Outdoor Adventures',
Expand All @@ -41,7 +41,7 @@ export const guidesMetadata: Metadata = {
title: 'PackRat Guides | Hiking & Outdoor Adventures',
description: 'Expert hiking and outdoor guides to help you prepare for your next adventure',
creator: '@packratai',
images: [new URL('/twitter-image.png', siteConfig.url).toString()],
images: [new URL('/og-image.png', siteConfig.url).toString()],
},
icons: {
icon: [{ url: '/PackRatGuides.ico', type: 'image/x-icon' }],
Expand Down
Loading
Loading