Skip to content

Commit 6859f7d

Browse files
SamyPesseemmerich
andauthored
Fix rendering of ogimage when logo or icon are AVIF images (#3336)
Co-authored-by: Steven H <steven@gitbook.io>
1 parent dfa8a37 commit 6859f7d

File tree

2 files changed

+64
-21
lines changed

2 files changed

+64
-21
lines changed

.changeset/pretty-balloons-fold.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"gitbook": patch
3+
---
4+
5+
Fix rendering of ogimage when logo or icon are AVIF images.

packages/gitbook/src/routes/ogimage.tsx

Lines changed: 59 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ImageResponse } from 'next/og';
88
import { type PageParams, fetchPageData } from '@/components/SitePage';
99
import { getFontSourcesToPreload } from '@/fonts/custom';
1010
import { getAssetURL } from '@/lib/assets';
11+
import { getExtension } from '@/lib/paths';
1112
import { filterOutNullable } from '@/lib/typescript';
1213
import { getCacheTag } from '@gitbook/cache-tags';
1314
import type { GitBookSiteContext } from '@v2/lib/context';
@@ -32,7 +33,6 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
3233
}
3334

3435
// Compute all text to load only the necessary fonts
35-
const contentTitle = customization.header.logo ? '' : site.title;
3636
const pageTitle = page
3737
? page.title.length > 64
3838
? `${page.title.slice(0, 64)}...`
@@ -52,7 +52,7 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
5252
const fontFamily = customization.styling.font ?? CustomizationDefaultFont.Inter;
5353

5454
const regularText = pageDescription;
55-
const boldText = `${contentTitle}${pageTitle}`;
55+
const boldText = `${site.title} ${pageTitle}`;
5656

5757
const fonts = (
5858
await Promise.all([
@@ -164,10 +164,28 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
164164
)
165165
)
166166
);
167+
if (!iconImage) {
168+
throw new Error('Icon image should always be fetchable');
169+
}
170+
167171
return <img {...iconImage} alt="Icon" width={40} height={40} tw="mr-4" />;
168172
};
169173

170-
const [favicon, { fontFamily, fonts }] = await Promise.all([faviconLoader(), fontLoader()]);
174+
const logoLoader = async () => {
175+
if (!customization.header.logo) {
176+
return null;
177+
}
178+
179+
return await fetchImage(
180+
useLightTheme ? customization.header.logo.light : customization.header.logo.dark
181+
);
182+
};
183+
184+
const [favicon, logo, { fontFamily, fonts }] = await Promise.all([
185+
faviconLoader(),
186+
logoLoader(),
187+
fontLoader(),
188+
]);
171189

172190
return new ImageResponse(
173191
<div
@@ -193,22 +211,14 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
193211
/>
194212

195213
{/* Logo */}
196-
{customization.header.logo ? (
214+
{logo ? (
197215
<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-
/>
216+
<img {...logo} alt="Logo" tw="h-[60px]" />
207217
</div>
208218
) : (
209219
<div tw="flex">
210220
{favicon}
211-
<h3 tw="text-4xl my-0 font-bold">{contentTitle}</h3>
221+
<h3 tw="text-4xl my-0 font-bold">{site.title}</h3>
212222
</div>
213223
)}
214224

@@ -295,7 +305,9 @@ async function loadCustomFont(input: { url: string; weight: 400 | 700 }) {
295305
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
296306
const staticCache = new Map<string, any>();
297307

298-
// Do we need to limit the in-memory cache size? I think given the usage, we should be fine.
308+
/**
309+
* Get or initialize a value in the static cache.
310+
*/
299311
async function getWithCache<T>(key: string, fn: () => Promise<T>) {
300312
const cached = staticCache.get(key) as T;
301313
if (cached) {
@@ -311,19 +323,46 @@ async function getWithCache<T>(key: string, fn: () => Promise<T>) {
311323
* Read a static image and cache it in memory.
312324
*/
313325
async function fetchStaticImage(url: string) {
314-
return getWithCache(`static-image:${url}`, () => fetchImage(url));
326+
return getWithCache(`static-image:${url}`, async () => {
327+
const image = await fetchImage(url);
328+
if (!image) {
329+
throw new Error('Failed to fetch static image');
330+
}
331+
332+
return image;
333+
});
315334
}
316335

336+
/**
337+
* @vercel/og supports the following image formats:
338+
* Extracted from https://github.com/vercel/next.js/blob/canary/packages/next/src/compiled/%40vercel/og/index.node.js
339+
*/
340+
const UNSUPPORTED_IMAGE_EXTENSIONS = ['.avif', '.webp'];
341+
const SUPPORTED_IMAGE_TYPES = [
342+
'image/png',
343+
'image/apng',
344+
'image/jpeg',
345+
'image/gif',
346+
'image/svg+xml',
347+
];
348+
317349
/**
318350
* Fetch an image from a URL and return a base64 encoded string.
319351
* We do this as @vercel/og is otherwise failing on SVG images referenced by a URL.
320352
*/
321353
async function fetchImage(url: string) {
354+
// Skip early some images to avoid fetching them
355+
const parsedURL = new URL(url);
356+
if (UNSUPPORTED_IMAGE_EXTENSIONS.includes(getExtension(parsedURL.pathname).toLowerCase())) {
357+
return null;
358+
}
359+
322360
const response = await fetch(url);
323361

362+
// Filter out unsupported image types
324363
const contentType = response.headers.get('content-type');
325-
if (!contentType || !contentType.startsWith('image/')) {
326-
throw new Error(`Invalid content type: ${contentType}`);
364+
if (!contentType || !SUPPORTED_IMAGE_TYPES.some((type) => contentType.includes(type))) {
365+
return null;
327366
}
328367

329368
const arrayBuffer = await response.arrayBuffer();
@@ -334,8 +373,7 @@ async function fetchImage(url: string) {
334373
try {
335374
const { width, height } = imageSize(buffer);
336375
return { src, width, height };
337-
} catch (error) {
338-
console.error(`Error reading image size: ${error}`);
339-
return { src };
376+
} catch {
377+
return null;
340378
}
341379
}

0 commit comments

Comments
 (0)