From 23494f90da3d0fdd176e06c47a68bb1e32dcf2b7 Mon Sep 17 00:00:00 2001 From: Bartosz Kaszubowski Date: Mon, 19 Jan 2026 22:28:24 +0100 Subject: [PATCH 1/3] fix OG images loading by API proxy with cache --- components/PageMeta.tsx | 21 +++++++++++---------- pages/api/proxy/og.ts | 39 +++++++++++++++++++++++++++++++++++++++ util/Constants.ts | 5 +++++ 3 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 pages/api/proxy/og.ts diff --git a/components/PageMeta.tsx b/components/PageMeta.tsx index 751223ab2..33268ea99 100644 --- a/components/PageMeta.tsx +++ b/components/PageMeta.tsx @@ -1,32 +1,33 @@ +import { type NextPageContext } from 'next'; import Head from 'next/head'; -const site = { - title: 'React Native Directory', - description: 'An interactive directory to find packages for your React Native apps.', -}; - -const BASE_OG_URL = 'https://og.expo.dev/?theme=rnd'; +import { type Query } from '~/types'; +import { BASE_META } from '~/util/Constants'; +import getApiUrl from '~/util/getApiUrl'; type PageMetaProps = { title?: string; description?: string; path?: string; - searchQuery?: string | string[]; + searchQuery?: Query; }; export default function PageMeta({ title, searchQuery, path, - description = site.description, + description = BASE_META.description, }: PageMetaProps) { - const pageTitle = `${title ? title + ' • ' : ''}${site.title}`; + const pageTitle = `${title ? title + ' • ' : ''}${BASE_META.title}`; const parsedSearchQuery = Array.isArray(searchQuery) ? searchQuery[0] : searchQuery; const finalDescription = parsedSearchQuery ? `Search results for keyword: '${parsedSearchQuery}'` : description; - const socialImage = `${BASE_OG_URL}&title=${encodeURIComponent(pageTitle)}&description=${encodeURIComponent(finalDescription)}`; + const socialImage = getApiUrl( + `/proxy/og?title=${encodeURIComponent(pageTitle)}&description=${encodeURIComponent(finalDescription)}`, + {} as NextPageContext + ); return ( diff --git a/pages/api/proxy/og.ts b/pages/api/proxy/og.ts new file mode 100644 index 000000000..552456409 --- /dev/null +++ b/pages/api/proxy/og.ts @@ -0,0 +1,39 @@ +import { type NextApiRequest, type NextApiResponse } from 'next'; + +import { BASE_META, NEXT_1H_CACHE_HEADER } from '~/util/Constants'; +import { parseQueryParams } from '~/util/parseQueryParams'; + +export default async function handler(req: NextApiRequest, res: NextApiResponse) { + const { title, description } = parseQueryParams(req.query); + + const ogTitle = title ? title.toString().trim() : BASE_META.title; + const ogDescription = description ? description.toString().trim() : BASE_META.description; + + res.setHeader('Cache-Control', 'public, s-maxage=600, stale-while-revalidate=300'); + + const result = await fetch( + `https://og.expo.dev/?theme=rnd&title=${encodeURIComponent(ogTitle)}&description=${encodeURIComponent(ogDescription)}`, + NEXT_1H_CACHE_HEADER + ); + + if (!result.ok) { + res.setHeader('Content-Type', 'application/json'); + res.statusCode = 502; + return res.json({ error: `Failed to fetch OG image.` }); + } + + const upstreamContentType = result.headers.get('content-type') ?? 'image/png'; + const upstreamContentLength = result.headers.get('content-length'); + + if (upstreamContentLength) { + res.setHeader('Content-Length', upstreamContentLength); + } + + res.setHeader('Content-Type', upstreamContentType); + res.statusCode = 200; + + const arrayBuffer = await result.arrayBuffer(); + const buffer = Buffer.from(arrayBuffer); + + return res.send(buffer); +} diff --git a/util/Constants.ts b/util/Constants.ts index 5f4672f8d..862ebae9a 100644 --- a/util/Constants.ts +++ b/util/Constants.ts @@ -1,5 +1,10 @@ import { TimeRange } from '~/util/datetime'; +export const BASE_META = { + title: 'React Native Directory', + description: 'An interactive directory to find packages for your React Native apps.', +}; + export const NUM_PER_PAGE = 30; export const EMPTY_PACKAGE_DATA = { From 9e8c2d55098ffecc8ec686bf9d59a7a080cc8944 Mon Sep 17 00:00:00 2001 From: Bartosz Kaszubowski Date: Mon, 19 Jan 2026 22:32:34 +0100 Subject: [PATCH 2/3] fix typing --- components/PageMeta.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/components/PageMeta.tsx b/components/PageMeta.tsx index 33268ea99..3ff28ac34 100644 --- a/components/PageMeta.tsx +++ b/components/PageMeta.tsx @@ -1,7 +1,6 @@ import { type NextPageContext } from 'next'; import Head from 'next/head'; -import { type Query } from '~/types'; import { BASE_META } from '~/util/Constants'; import getApiUrl from '~/util/getApiUrl'; @@ -9,7 +8,7 @@ type PageMetaProps = { title?: string; description?: string; path?: string; - searchQuery?: Query; + searchQuery?: string | string[]; }; export default function PageMeta({ From 8c209f68f63b2280df67544fe108bdccb6195a10 Mon Sep 17 00:00:00 2001 From: Bartosz Kaszubowski Date: Mon, 19 Jan 2026 23:35:20 +0100 Subject: [PATCH 3/3] temporary code for PR preview --- components/PageMeta.tsx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/components/PageMeta.tsx b/components/PageMeta.tsx index 3ff28ac34..776edf834 100644 --- a/components/PageMeta.tsx +++ b/components/PageMeta.tsx @@ -1,8 +1,6 @@ -import { type NextPageContext } from 'next'; import Head from 'next/head'; import { BASE_META } from '~/util/Constants'; -import getApiUrl from '~/util/getApiUrl'; type PageMetaProps = { title?: string; @@ -23,10 +21,7 @@ export default function PageMeta({ ? `Search results for keyword: '${parsedSearchQuery}'` : description; - const socialImage = getApiUrl( - `/proxy/og?title=${encodeURIComponent(pageTitle)}&description=${encodeURIComponent(finalDescription)}`, - {} as NextPageContext - ); + const socialImage = `https://${window.location.host}/api/proxy/og?title=${encodeURIComponent(pageTitle)}&description=${encodeURIComponent(finalDescription)}`; return (