diff --git a/components/PageMeta.tsx b/components/PageMeta.tsx index 751223ab2..776edf834 100644 --- a/components/PageMeta.tsx +++ b/components/PageMeta.tsx @@ -1,11 +1,6 @@ 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 { BASE_META } from '~/util/Constants'; type PageMetaProps = { title?: string; @@ -18,15 +13,15 @@ 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 = `https://${window.location.host}/api/proxy/og?title=${encodeURIComponent(pageTitle)}&description=${encodeURIComponent(finalDescription)}`; 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 = {