From 1a4ec3fc91e2c3c1dd226edc995ab9bee36bef59 Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 15 May 2026 18:30:54 -0600 Subject: [PATCH 1/3] Fix OG/Twitter image metadata and add coverage --- apps/guides/app/layout.metadata.test.ts | 20 ++++++++++ apps/guides/app/layout.tsx | 43 +------------------- apps/guides/app/metadata.ts | 51 ++++++++++++++++++++++++ apps/landing/app/layout.metadata.test.ts | 20 ++++++++++ apps/landing/app/layout.tsx | 35 +--------------- apps/landing/app/metadata.ts | 43 ++++++++++++++++++++ 6 files changed, 138 insertions(+), 74 deletions(-) create mode 100644 apps/guides/app/layout.metadata.test.ts create mode 100644 apps/guides/app/metadata.ts create mode 100644 apps/landing/app/layout.metadata.test.ts create mode 100644 apps/landing/app/metadata.ts diff --git a/apps/guides/app/layout.metadata.test.ts b/apps/guides/app/layout.metadata.test.ts new file mode 100644 index 0000000000..eb72d1fba3 --- /dev/null +++ b/apps/guides/app/layout.metadata.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest'; +import { siteConfig } from '../lib/config'; +import { guidesMetadata as metadata } from './metadata'; + +describe('guides metadata', () => { + it('includes absolute Open Graph and Twitter image URLs', () => { + const expectedImageUrl = new URL('/opengraph-image', siteConfig.url).toString(); + + expect(metadata.openGraph?.images).toEqual([ + { + url: expectedImageUrl, + width: 1200, + height: 630, + alt: 'PackRat Guides | Hiking & Outdoor Adventures', + }, + ]); + + expect(metadata.twitter?.images).toEqual([expectedImageUrl]); + }); +}); diff --git a/apps/guides/app/layout.tsx b/apps/guides/app/layout.tsx index eb159f716b..1769a3b143 100644 --- a/apps/guides/app/layout.tsx +++ b/apps/guides/app/layout.tsx @@ -3,10 +3,9 @@ import Footer from 'guides-app/components/footer'; import Header from 'guides-app/components/header'; import { QueryProvider } from 'guides-app/components/providers/query-provider'; import { ThemeProvider } from 'guides-app/components/theme-provider'; -import { siteConfig } from 'guides-app/lib/config'; -import type { Metadata } from 'next'; import { Mona_Sans as FontSans } from 'next/font/google'; import type React from 'react'; +import { guidesMetadata } from './metadata'; import './globals.css'; const fontSans = FontSans({ @@ -15,45 +14,7 @@ const fontSans = FontSans({ weight: ['400', '500', '600', '700'], }); -export const metadata: Metadata = { - title: { - default: 'PackRat Guides | Hiking & Outdoor Adventures', - template: '%s | PackRat Guides', - }, - description: 'Expert hiking and outdoor guides to help you prepare for your next adventure', - keywords: [ - 'hiking guides', - 'outdoor adventures', - 'trail guides', - 'camping', - 'backpacking', - 'gear reviews', - 'wilderness skills', - 'outdoor planning', - ], - authors: [{ name: 'PackRat Team', url: 'https://packrat.world' }], - creator: 'PackRat Team', - metadataBase: new URL(siteConfig.url), - openGraph: { - type: 'website', - locale: 'en_US', - url: siteConfig.url, - siteName: 'PackRat Guides', - title: 'PackRat Guides | Hiking & Outdoor Adventures', - description: 'Expert hiking and outdoor guides to help you prepare for your next adventure', - }, - twitter: { - card: 'summary_large_image', - title: 'PackRat Guides | Hiking & Outdoor Adventures', - description: 'Expert hiking and outdoor guides to help you prepare for your next adventure', - creator: '@packratai', - }, - icons: { - icon: [{ url: '/PackRatGuides.ico', type: 'image/x-icon' }], - shortcut: '/favicon-16x16.png', - apple: '/apple-touch-icon.png', - }, -}; +export const metadata = guidesMetadata; export default function RootLayout({ children }: { children: React.ReactNode }) { return ( diff --git a/apps/guides/app/metadata.ts b/apps/guides/app/metadata.ts new file mode 100644 index 0000000000..b51dbd61fa --- /dev/null +++ b/apps/guides/app/metadata.ts @@ -0,0 +1,51 @@ +import type { Metadata } from 'next'; +import { siteConfig } from '../lib/config'; + +export const guidesMetadata: Metadata = { + title: { + default: 'PackRat Guides | Hiking & Outdoor Adventures', + template: '%s | PackRat Guides', + }, + description: 'Expert hiking and outdoor guides to help you prepare for your next adventure', + keywords: [ + 'hiking guides', + 'outdoor adventures', + 'trail guides', + 'camping', + 'backpacking', + 'gear reviews', + 'wilderness skills', + 'outdoor planning', + ], + authors: [{ name: 'PackRat Team', url: 'https://packrat.world' }], + creator: 'PackRat Team', + metadataBase: new URL(siteConfig.url), + openGraph: { + type: 'website', + locale: 'en_US', + url: siteConfig.url, + siteName: 'PackRat Guides', + title: 'PackRat Guides | Hiking & Outdoor Adventures', + description: 'Expert hiking and outdoor guides to help you prepare for your next adventure', + images: [ + { + url: new URL('/opengraph-image', siteConfig.url).toString(), + width: 1200, + height: 630, + alt: 'PackRat Guides | Hiking & Outdoor Adventures', + }, + ], + }, + twitter: { + card: 'summary_large_image', + 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('/opengraph-image', siteConfig.url).toString()], + }, + icons: { + icon: [{ url: '/PackRatGuides.ico', type: 'image/x-icon' }], + shortcut: '/favicon-16x16.png', + apple: '/apple-touch-icon.png', + }, +}; diff --git a/apps/landing/app/layout.metadata.test.ts b/apps/landing/app/layout.metadata.test.ts new file mode 100644 index 0000000000..96900bed12 --- /dev/null +++ b/apps/landing/app/layout.metadata.test.ts @@ -0,0 +1,20 @@ +import { describe, expect, it } from 'vitest'; +import { siteConfig } from '../config/site'; +import { landingMetadata as metadata } from './metadata'; + +describe('landing metadata', () => { + it('includes absolute Open Graph and Twitter image URLs', () => { + const expectedImageUrl = new URL(siteConfig.ogImage, siteConfig.url).toString(); + + expect(metadata.openGraph?.images).toEqual([ + { + url: expectedImageUrl, + width: 1200, + height: 630, + alt: siteConfig.name, + }, + ]); + + expect(metadata.twitter?.images).toEqual([expectedImageUrl]); + }); +}); diff --git a/apps/landing/app/layout.tsx b/apps/landing/app/layout.tsx index beb509206a..26e5b9e7c7 100644 --- a/apps/landing/app/layout.tsx +++ b/apps/landing/app/layout.tsx @@ -2,10 +2,9 @@ import { cn } from '@packrat/web-ui/lib/utils'; import MainNav from 'landing-app/components/main-nav'; import SiteFooter from 'landing-app/components/site-footer'; import { ThemeProvider } from 'landing-app/components/theme-provider'; -import { siteConfig } from 'landing-app/config/site'; -import type { Metadata } from 'next'; import { Mona_Sans as FontSans } from 'next/font/google'; import type React from 'react'; +import { landingMetadata } from './metadata'; import './globals.css'; const fontSans = FontSans({ @@ -14,37 +13,7 @@ const fontSans = FontSans({ weight: ['400', '500', '600', '700'], }); -export const metadata: Metadata = { - title: { - default: siteConfig.name, - template: `%s | ${siteConfig.name}`, - }, - description: siteConfig.description, - keywords: siteConfig.keywords, - authors: [{ name: siteConfig.author, url: siteConfig.url }], - creator: siteConfig.author, - metadataBase: new URL(siteConfig.url), - openGraph: { - type: 'website', - locale: 'en_US', - url: siteConfig.url, - title: siteConfig.name, - description: siteConfig.description, - siteName: siteConfig.name, - }, - twitter: { - card: 'summary_large_image', - title: siteConfig.name, - description: siteConfig.description, - creator: siteConfig.twitterHandle, - }, - icons: { - icon: '/PackRat.ico', - shortcut: '/favicon-16x16.png', - apple: '/apple-touch-icon.png', - }, - manifest: `${siteConfig.url}/site.webmanifest`, -}; +export const metadata = landingMetadata; export default function RootLayout({ children, diff --git a/apps/landing/app/metadata.ts b/apps/landing/app/metadata.ts new file mode 100644 index 0000000000..7c2d3ee984 --- /dev/null +++ b/apps/landing/app/metadata.ts @@ -0,0 +1,43 @@ +import type { Metadata } from 'next'; +import { siteConfig } from '../config/site'; + +export const landingMetadata: Metadata = { + title: { + default: siteConfig.name, + template: `%s | ${siteConfig.name}`, + }, + description: siteConfig.description, + keywords: siteConfig.keywords, + authors: [{ name: siteConfig.author, url: siteConfig.url }], + creator: siteConfig.author, + metadataBase: new URL(siteConfig.url), + openGraph: { + type: 'website', + locale: 'en_US', + url: siteConfig.url, + title: siteConfig.name, + description: siteConfig.description, + siteName: siteConfig.name, + images: [ + { + url: new URL(siteConfig.ogImage, siteConfig.url).toString(), + width: 1200, + height: 630, + alt: siteConfig.name, + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: siteConfig.name, + description: siteConfig.description, + creator: siteConfig.twitterHandle, + images: [new URL(siteConfig.ogImage, siteConfig.url).toString()], + }, + icons: { + icon: '/PackRat.ico', + shortcut: '/favicon-16x16.png', + apple: '/apple-touch-icon.png', + }, + manifest: `${siteConfig.url}/site.webmanifest`, +}; From d1e2f8202bae3c0188e3ca5c4752c3466b3aafde Mon Sep 17 00:00:00 2001 From: Andrew Bierman <94939237+andrew-bierman@users.noreply.github.com> Date: Fri, 15 May 2026 18:51:49 -0600 Subject: [PATCH 2/3] Use Next metadata image routes for OG/Twitter URLs --- apps/guides/app/layout.metadata.test.ts | 6 ++++-- apps/guides/app/metadata.ts | 4 ++-- apps/landing/app/layout.metadata.test.ts | 6 ++++-- apps/landing/app/metadata.ts | 4 ++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/apps/guides/app/layout.metadata.test.ts b/apps/guides/app/layout.metadata.test.ts index eb72d1fba3..1cde25071f 100644 --- a/apps/guides/app/layout.metadata.test.ts +++ b/apps/guides/app/layout.metadata.test.ts @@ -4,7 +4,7 @@ import { guidesMetadata as metadata } from './metadata'; describe('guides metadata', () => { it('includes absolute Open Graph and Twitter image URLs', () => { - const expectedImageUrl = new URL('/opengraph-image', siteConfig.url).toString(); + const expectedImageUrl = new URL('/opengraph-image.png', siteConfig.url).toString(); expect(metadata.openGraph?.images).toEqual([ { @@ -15,6 +15,8 @@ describe('guides metadata', () => { }, ]); - expect(metadata.twitter?.images).toEqual([expectedImageUrl]); + expect(metadata.twitter?.images).toEqual([ + new URL('/twitter-image.png', siteConfig.url).toString(), + ]); }); }); diff --git a/apps/guides/app/metadata.ts b/apps/guides/app/metadata.ts index b51dbd61fa..fca7bcf24e 100644 --- a/apps/guides/app/metadata.ts +++ b/apps/guides/app/metadata.ts @@ -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', siteConfig.url).toString(), + url: new URL('/opengraph-image.png', siteConfig.url).toString(), width: 1200, height: 630, alt: 'PackRat Guides | Hiking & Outdoor Adventures', @@ -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('/opengraph-image', siteConfig.url).toString()], + images: [new URL('/twitter-image.png', siteConfig.url).toString()], }, icons: { icon: [{ url: '/PackRatGuides.ico', type: 'image/x-icon' }], diff --git a/apps/landing/app/layout.metadata.test.ts b/apps/landing/app/layout.metadata.test.ts index 96900bed12..b383a145e4 100644 --- a/apps/landing/app/layout.metadata.test.ts +++ b/apps/landing/app/layout.metadata.test.ts @@ -4,7 +4,7 @@ import { landingMetadata as metadata } from './metadata'; describe('landing metadata', () => { it('includes absolute Open Graph and Twitter image URLs', () => { - const expectedImageUrl = new URL(siteConfig.ogImage, siteConfig.url).toString(); + const expectedImageUrl = new URL('/opengraph-image.png', siteConfig.url).toString(); expect(metadata.openGraph?.images).toEqual([ { @@ -15,6 +15,8 @@ describe('landing metadata', () => { }, ]); - expect(metadata.twitter?.images).toEqual([expectedImageUrl]); + expect(metadata.twitter?.images).toEqual([ + new URL('/twitter-image.png', siteConfig.url).toString(), + ]); }); }); diff --git a/apps/landing/app/metadata.ts b/apps/landing/app/metadata.ts index 7c2d3ee984..0d4c45ab19 100644 --- a/apps/landing/app/metadata.ts +++ b/apps/landing/app/metadata.ts @@ -20,7 +20,7 @@ export const landingMetadata: Metadata = { siteName: siteConfig.name, images: [ { - url: new URL(siteConfig.ogImage, siteConfig.url).toString(), + url: new URL('/opengraph-image.png', siteConfig.url).toString(), width: 1200, height: 630, alt: siteConfig.name, @@ -32,7 +32,7 @@ export const landingMetadata: Metadata = { title: siteConfig.name, description: siteConfig.description, creator: siteConfig.twitterHandle, - images: [new URL(siteConfig.ogImage, siteConfig.url).toString()], + images: [new URL('/twitter-image.png', siteConfig.url).toString()], }, icons: { icon: '/PackRat.ico', From c627e35abce2438ca36cb5ba2fac10e9ee4de227 Mon Sep 17 00:00:00 2001 From: Andrew Bierman Date: Sat, 16 May 2026 16:28:46 -0600 Subject: [PATCH 3/3] Move metadata tests to __tests__ dir so vitest picks them up The guides + landing vitest configs scope tests to __tests__/**, so the layout.metadata.test.ts files added under app/ were silently skipped. --- apps/guides/{app => __tests__}/layout.metadata.test.ts | 0 apps/landing/{app => __tests__}/layout.metadata.test.ts | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename apps/guides/{app => __tests__}/layout.metadata.test.ts (100%) rename apps/landing/{app => __tests__}/layout.metadata.test.ts (100%) diff --git a/apps/guides/app/layout.metadata.test.ts b/apps/guides/__tests__/layout.metadata.test.ts similarity index 100% rename from apps/guides/app/layout.metadata.test.ts rename to apps/guides/__tests__/layout.metadata.test.ts diff --git a/apps/landing/app/layout.metadata.test.ts b/apps/landing/__tests__/layout.metadata.test.ts similarity index 100% rename from apps/landing/app/layout.metadata.test.ts rename to apps/landing/__tests__/layout.metadata.test.ts