@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises'
22import path from 'node:path'
33import { ImageResponse } from 'next/og'
44import { getPost , getPostSlugs } from '~/lib/utils/posts'
5+ import { getBaseUrl } from '~/lib/utils'
56import { Rubric } from '~/ui/logos/rubric'
67
78export const runtime = 'nodejs'
@@ -15,6 +16,7 @@ export const size = {
1516
1617const fontDataPromise = readFile ( path . join ( process . cwd ( ) , 'src/app/fonts/matter-regular.woff' ) )
1718const bannerSrcCache = new Map < string , string > ( )
19+ const ABSOLUTE_URL_PATTERN = / ^ h t t p s ? : \/ \/ / i
1820
1921export async function generateStaticParams ( ) {
2022 const slugs = await getPostSlugs ( )
@@ -96,22 +98,45 @@ export const Component = ({
9698// Builds a cached data URI for banner images; input '/images/primitives.png' -> output 'data:image/png;base64,...'.
9799const getBannerSrc = async ( bannerImageUrl : string ) => {
98100 const cached = bannerSrcCache . get ( bannerImageUrl )
99- if ( cached ) return cached
101+ if ( cached !== undefined ) return cached
100102
101103 const bannerPath = bannerImageUrl . replace ( / ^ \/ / , '' )
102104 const extension = path . extname ( bannerPath ) . toLowerCase ( )
103- const mimeType =
105+ const fallbackMimeType =
104106 extension === '.png'
105107 ? 'image/png'
106108 : extension === '.jpg' || extension === '.jpeg'
107109 ? 'image/jpeg'
108110 : extension === '.webp'
109111 ? 'image/webp'
110112 : 'image/png'
111- const bannerData = await readFile ( path . join ( process . cwd ( ) , 'public' , bannerPath ) , 'base64' )
112- const bannerSrc = `data:${ mimeType } ;base64,${ bannerData } `
113- bannerSrcCache . set ( bannerImageUrl , bannerSrc )
114- return bannerSrc
113+
114+ try {
115+ const bannerData = await readFile ( path . join ( process . cwd ( ) , 'public' , bannerPath ) , 'base64' )
116+ const bannerSrc = `data:${ fallbackMimeType } ;base64,${ bannerData } `
117+ bannerSrcCache . set ( bannerImageUrl , bannerSrc )
118+ return bannerSrc
119+ } catch {
120+ // Local assets may not exist in every environment. Try HTTP as a fallback.
121+ }
122+
123+ const bannerUrl = ABSOLUTE_URL_PATTERN . test ( bannerImageUrl )
124+ ? bannerImageUrl
125+ : new URL ( bannerImageUrl , getBaseUrl ( ) ) . toString ( )
126+
127+ try {
128+ const response = await fetch ( bannerUrl , { next : { revalidate } } )
129+ if ( ! response . ok ) throw new Error ( 'Failed to fetch banner image' )
130+ const contentType = response . headers . get ( 'content-type' ) ?. split ( ';' ) [ 0 ] || fallbackMimeType
131+ const bannerData = Buffer . from ( await response . arrayBuffer ( ) ) . toString ( 'base64' )
132+ const bannerSrc = `data:${ contentType } ;base64,${ bannerData } `
133+ bannerSrcCache . set ( bannerImageUrl , bannerSrc )
134+ return bannerSrc
135+ } catch {
136+ // Keep image generation resilient even if banner lookup fails.
137+ bannerSrcCache . set ( bannerImageUrl , '' )
138+ return ''
139+ }
115140}
116141
117142export default async function Image ( { params } : { params : Promise < { slug : string } > } ) {
0 commit comments