Skip to content

Fix crash during rendering of ogimage for VA sites with default icon #3347

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 6 commits into from
Jun 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
5 changes: 5 additions & 0 deletions .changeset/thick-cups-shout.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"gitbook": patch
---

Fix crash during rendering of ogimage for VA sites with default icon.
53 changes: 37 additions & 16 deletions packages/gitbook/src/routes/icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,19 @@ const SIZES = {
},
};

type RenderIconOptions = {
size: keyof typeof SIZES;
theme: 'light' | 'dark';
};

/**
* Generate an icon for a site content.
*/
export async function serveIcon(context: GitBookSiteContext, req: Request) {
const options = getOptions(req.url);
const size = SIZES[options.size];

const { site, customization } = context;
const { customization } = context;
const customIcon = 'icon' in customization.favicon ? customization.favicon.icon : null;

// If the site has a custom icon, redirect to it
Expand All @@ -45,17 +50,45 @@ export async function serveIcon(context: GitBookSiteContext, req: Request) {
);
}

return new ImageResponse(<SiteDefaultIcon context={context} options={options} />, {
width: size.width,
height: size.height,
headers: {
'cache-tag': [
getCacheTag({
tag: 'site',
site: context.site.id,
}),
].join(','),
},
});
}

/**
* Render the icon as a React node.
*/
export function SiteDefaultIcon(props: {
context: GitBookSiteContext;
options: RenderIconOptions;
style?: React.CSSProperties;
tw?: string;
}) {
const { context, options, style, tw } = props;
const size = SIZES[options.size];

const { site, customization } = context;
const contentTitle = site.title;

return new ImageResponse(
return (
<div
tw={tcls(options.theme === 'light' ? 'bg-white' : 'bg-black', size.boxStyle)}
tw={tcls(options.theme === 'light' ? 'bg-white' : 'bg-black', size.boxStyle, tw)}
style={{
width: '100%',
height: '100%',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
...style,
}}
>
<h2
Expand All @@ -70,19 +103,7 @@ export async function serveIcon(context: GitBookSiteContext, req: Request) {
? getEmojiForCode(customization.favicon.emoji)
: contentTitle.slice(0, 1).toUpperCase()}
</h2>
</div>,
{
width: size.width,
height: size.height,
headers: {
'cache-tag': [
getCacheTag({
tag: 'site',
site: context.site.id,
}),
].join(','),
},
}
</div>
);
}

Expand Down
51 changes: 27 additions & 24 deletions packages/gitbook/src/routes/ogimage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@ import {
getResizedImageURL,
resizeImage,
} from '@v2/lib/images';
import { SiteDefaultIcon } from './icon';

/**
* Render the OpenGraph image for a site content.
*/
export async function serveOGImage(baseContext: GitBookSiteContext, params: PageParams) {
const { context, pageTarget } = await fetchPageData(baseContext, params);
const { customization, site, linker, imageResizer } = context;
const { customization, site, imageResizer } = context;
const page = pageTarget?.page;

// If user configured a custom social preview, we redirect to it.
Expand Down Expand Up @@ -148,34 +149,36 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
}

const faviconLoader = async () => {
if (customization.header.logo) {
// Don't load the favicon if we have a logo
// as it'll not be used.
return null;
}

const faviconSize = {
width: 48,
height: 48,
};

if ('icon' in customization.favicon)
return (
<img
src={customization.favicon.icon[theme]}
width={40}
height={40}
tw="mr-4"
{...(await fetchImage(customization.favicon.icon[theme], faviconSize))}
{...faviconSize}
alt="Icon"
/>
);
if ('emoji' in customization.favicon)
return (
<span tw="text-4xl mr-4">
{String.fromCodePoint(Number.parseInt(`0x${customization.favicon.emoji}`))}
</span>
);
const iconImage = await fetchImage(
linker.toAbsoluteURL(
linker.toPathInSpace(
`~gitbook/icon?size=medium&theme=${customization.themes.default}`
)
)
);
if (!iconImage) {
throw new Error('Icon image should always be fetchable');
}

return <img {...iconImage} alt="Icon" width={40} height={40} tw="mr-4" />;
return (
<SiteDefaultIcon
context={context}
options={{
size: 'small',
theme,
}}
style={faviconSize}
/>
);
};

const logoLoader = async () => {
Expand Down Expand Up @@ -226,9 +229,9 @@ export async function serveOGImage(baseContext: GitBookSiteContext, params: Page
<img {...logo} alt="Logo" tw="h-[60px]" />
</div>
) : (
<div tw="flex">
<div tw="flex flex-row items-center">
{favicon}
<h3 tw="text-4xl my-0 font-bold">{transformText(site.title)}</h3>
<h3 tw="text-4xl ml-4 my-0 font-bold">{transformText(site.title)}</h3>
</div>
)}

Expand Down