Skip to content

Support webp and avif formats for og image #3478

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion packages/gitbook/src/lib/images/resizer/resizeImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,15 @@ export async function resizeImage(
input: string,
options: CloudflareImageOptions & {
signal?: AbortSignal;
/**
* Bypass the check to see if the image can be resized.
* This is useful for some format that are not supported by @next/og and need to be transformed
*/
bypassSkipCheck?: boolean;
}
): Promise<Response> {
const action = checkIsSizableImageURL(input);
if (action === SizableImageAction.Skip) {
if (action === SizableImageAction.Skip && !options.bypassSkipCheck) {
throw new Error(
'Cannot resize this image, this function should have never been called on this url'
);
Expand Down
29 changes: 17 additions & 12 deletions packages/gitbook/src/routes/ogimage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
height: 630,
fonts: fonts.length ? fonts : undefined,
headers: {
// We don't want to cache the image for too long in the browser
'cache-control': 'public, max-age=300, s-maxage=31536000',
'cache-tag': [
getCacheTag({
tag: 'site',
Expand Down Expand Up @@ -366,19 +368,22 @@ const SUPPORTED_IMAGE_TYPES = [
async function fetchImage(url: string, options?: ResizeImageOptions) {
// Skip early some images to avoid fetching them
const parsedURL = new URL(url);
if (UNSUPPORTED_IMAGE_EXTENSIONS.includes(getExtension(parsedURL.pathname).toLowerCase())) {
return null;
}

// We use the image resizer to normalize the image format to PNG.
// as @vercel/og can sometimes fail on some JPEG images.
const response =
checkIsSizableImageURL(url) !== SizableImageAction.Resize
? await fetch(url)
: await resizeImage(url, {
...options,
format: 'png',
});
let response: Response;
if (
UNSUPPORTED_IMAGE_EXTENSIONS.includes(getExtension(parsedURL.pathname).toLowerCase()) ||
checkIsSizableImageURL(url) === SizableImageAction.Resize
) {
// We use the image resizer to normalize the image format to PNG.
// as @vercel/og can sometimes fail on some JPEG images, and will fail on avif and webp images.
response = await resizeImage(url, {
...options,
format: 'png',
bypassSkipCheck: true, // Bypass the check to see if the image can be resized
});
} else {
response = await fetch(url);
}

// Filter out unsupported image types
const contentType = response.headers.get('content-type');
Expand Down