1
1
import { CustomizationDefaultFont , CustomizationHeaderPreset } from '@gitbook/api' ;
2
2
import { colorContrast } from '@gitbook/colors' ;
3
3
import { type FontWeight , getDefaultFont } from '@gitbook/fonts' ;
4
+ import { imageSize } from 'image-size' ;
4
5
import { redirect } from 'next/navigation' ;
5
6
import { ImageResponse } from 'next/og' ;
6
7
@@ -156,14 +157,14 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
156
157
{ String . fromCodePoint ( Number . parseInt ( `0x${ customization . favicon . emoji } ` ) ) }
157
158
</ span >
158
159
) ;
159
- const src = await readSelfImage (
160
+ const iconImage = await fetchImage (
160
161
linker . toAbsoluteURL (
161
162
linker . toPathInSpace (
162
163
`~gitbook/icon?size=medium&theme=${ customization . themes . default } `
163
164
)
164
165
)
165
166
) ;
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" /> ;
167
168
} ;
168
169
169
170
const [ favicon , { fontFamily, fonts } ] = await Promise . all ( [ faviconLoader ( ) , fontLoader ( ) ] ) ;
@@ -187,21 +188,23 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
187
188
{ /* Grid */ }
188
189
< img
189
190
tw = "absolute inset-0 w-[100vw] h-[100vh]"
190
- src = { await readStaticImage ( gridAsset ) }
191
+ src = { ( await fetchStaticImage ( gridAsset ) ) . src }
191
192
alt = "Grid"
192
193
/>
193
194
194
195
{ /* Logo */ }
195
196
{ 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 >
205
208
) : (
206
209
< div tw = "flex" >
207
210
{ favicon }
@@ -289,34 +292,6 @@ async function loadCustomFont(input: { url: string; weight: 400 | 700 }) {
289
292
} ;
290
293
}
291
294
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
-
320
295
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
321
296
const staticCache = new Map < string , any > ( ) ;
322
297
@@ -335,16 +310,32 @@ async function getWithCache<T>(key: string, fn: () => Promise<T>) {
335
310
/**
336
311
* Read a static image and cache it in memory.
337
312
*/
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 ) ) ;
341
315
}
342
316
343
317
/**
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.
345
320
*/
346
- async function readSelfImage ( url : string ) {
321
+ async function fetchImage ( url : string ) {
347
322
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
+ }
350
341
}
0 commit comments