11import { CustomizationDefaultFont , CustomizationHeaderPreset } from '@gitbook/api' ;
22import { colorContrast } from '@gitbook/colors' ;
33import { type FontWeight , getDefaultFont } from '@gitbook/fonts' ;
4+ import { imageSize } from 'image-size' ;
45import { redirect } from 'next/navigation' ;
56import { ImageResponse } from 'next/og' ;
67
@@ -156,14 +157,14 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
156157 { String . fromCodePoint ( Number . parseInt ( `0x${ customization . favicon . emoji } ` ) ) }
157158 </ span >
158159 ) ;
159- const src = await readSelfImage (
160+ const iconImage = await fetchImage (
160161 linker . toAbsoluteURL (
161162 linker . toPathInSpace (
162163 `~gitbook/icon?size=medium&theme=${ customization . themes . default } `
163164 )
164165 )
165166 ) ;
166- return < img src = { src } alt = "Icon" width = { 40 } height = { 40 } tw = "mr-4" /> ;
167+ return < img { ... iconImage } alt = "Icon" width = { 40 } height = { 40 } tw = "mr-4" /> ;
167168 } ;
168169
169170 const [ favicon , { fontFamily, fonts } ] = await Promise . all ( [ faviconLoader ( ) , fontLoader ( ) ] ) ;
@@ -187,21 +188,23 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
187188 { /* Grid */ }
188189 < img
189190 tw = "absolute inset-0 w-[100vw] h-[100vh]"
190- src = { await readStaticImage ( gridAsset ) }
191+ src = { ( await fetchStaticImage ( gridAsset ) ) . src }
191192 alt = "Grid"
192193 />
193194
194195 { /* Logo */ }
195196 { customization . header . logo ? (
196- < img
197- alt = "Logo"
198- height = { 60 }
199- src = {
200- useLightTheme
201- ? customization . header . logo . light
202- : customization . header . logo . dark
203- }
204- />
197+ < div tw = "flex flex-row" >
198+ < img
199+ { ...( await fetchImage (
200+ useLightTheme
201+ ? customization . header . logo . light
202+ : customization . header . logo . dark
203+ ) ) }
204+ alt = "Logo"
205+ tw = "h-[60px]"
206+ />
207+ </ div >
205208 ) : (
206209 < div tw = "flex" >
207210 { favicon }
@@ -289,34 +292,6 @@ async function loadCustomFont(input: { url: string; weight: 400 | 700 }) {
289292 } ;
290293}
291294
292- /**
293- * Temporary function to log some data on Cloudflare.
294- * TODO: remove this when we found the issue
295- */
296- function logOnCloudflareOnly ( message : string ) {
297- if ( process . env . DEBUG_CLOUDFLARE === 'true' ) {
298- // biome-ignore lint/suspicious/noConsole: <explanation>
299- console . log ( message ) ;
300- }
301- }
302-
303- /**
304- * Read an image from a response as a base64 encoded string.
305- */
306- async function readImage ( response : Response ) {
307- const contentType = response . headers . get ( 'content-type' ) ;
308- if ( ! contentType || ! contentType . startsWith ( 'image/' ) ) {
309- logOnCloudflareOnly ( `Invalid content type: ${ contentType } ,
310- status: ${ response . status }
311- rayId: ${ response . headers . get ( 'cf-ray' ) } ` ) ;
312- throw new Error ( `Invalid content type: ${ contentType } ` ) ;
313- }
314-
315- const arrayBuffer = await response . arrayBuffer ( ) ;
316- const base64 = Buffer . from ( arrayBuffer ) . toString ( 'base64' ) ;
317- return `data:${ contentType } ;base64,${ base64 } ` ;
318- }
319-
320295// biome-ignore lint/suspicious/noExplicitAny: <explanation>
321296const staticCache = new Map < string , any > ( ) ;
322297
@@ -335,16 +310,32 @@ async function getWithCache<T>(key: string, fn: () => Promise<T>) {
335310/**
336311 * Read a static image and cache it in memory.
337312 */
338- async function readStaticImage ( url : string ) {
339- logOnCloudflareOnly ( `Reading static image: ${ url } , cache size: ${ staticCache . size } ` ) ;
340- return getWithCache ( `static-image:${ url } ` , ( ) => readSelfImage ( url ) ) ;
313+ async function fetchStaticImage ( url : string ) {
314+ return getWithCache ( `static-image:${ url } ` , ( ) => fetchImage ( url ) ) ;
341315}
342316
343317/**
344- * Read an image from GitBook itself.
318+ * Fetch an image from a URL and return a base64 encoded string.
319+ * We do this as @vercel/og is otherwise failing on SVG images referenced by a URL.
345320 */
346- async function readSelfImage ( url : string ) {
321+ async function fetchImage ( url : string ) {
347322 const response = await fetch ( url ) ;
348- const image = await readImage ( response ) ;
349- return image ;
323+
324+ const contentType = response . headers . get ( 'content-type' ) ;
325+ if ( ! contentType || ! contentType . startsWith ( 'image/' ) ) {
326+ throw new Error ( `Invalid content type: ${ contentType } ` ) ;
327+ }
328+
329+ const arrayBuffer = await response . arrayBuffer ( ) ;
330+ const buffer = Buffer . from ( arrayBuffer ) ;
331+ const base64 = buffer . toString ( 'base64' ) ;
332+ const src = `data:${ contentType } ;base64,${ base64 } ` ;
333+
334+ try {
335+ const { width, height } = imageSize ( buffer ) ;
336+ return { src, width, height } ;
337+ } catch ( error ) {
338+ console . error ( `Error reading image size: ${ error } ` ) ;
339+ return { src } ;
340+ }
350341}
0 commit comments