I recently added an /api/og to create OG imges
import { ImageResponse } from "next/og";
import { mapSanityEvents } from "@/lib/map-sanity-event";
import {
getOgMetaData,
normalizeOgText,
OG_DEFAULT_HOME_TITLE,
} from "./og-config";
import {
getBlogPageOGData,
getGenericPageOGData,
getHomePageOGData,
getSlugPageOGData,
getUpcomingEventsForOg,
} from "./og-data";
import { OgEventsImage } from "./og-events-template";
import { getOgImageOptions } from "./og-fonts";
import { OgPlatformImage } from "./og-platform-template";
import {
OgBrandedImage,
OgErrorImage,
OgSeoImage,
type OgTemplateData,
} from "./og-template";
type ContentProps = Record<string, string>;
type OgFetcherResult = OgTemplateData & { seoImage?: string | null };
type OgPlatformItemResult = {
title?: string | null;
tagline?: string | null;
};
type PageOgResult = OgFetcherResult & {
hasEventsListing?: boolean;
hasPlatformList?: boolean;
platformItems?: OgPlatformItemResult[] | null;
};
function mapPlatformItemsForOg(
items: OgPlatformItemResult[] | null | undefined,
) {
return (items ?? [])
.map((item) => ({
title: normalizeOgText(item?.title),
tagline: normalizeOgText(item?.tagline) || null,
}))
.filter((item) => item.title.length > 0);
}
function renderPlatformOg(
page: PageOgResult,
options?: { defaultTitle?: string; fallbackTitle?: string },
) {
if (!page.hasPlatformList) return null;
const items = mapPlatformItemsForOg(page.platformItems);
if (items.length === 0) return null;
const title =
normalizeOgText(page.title) ||
options?.defaultTitle ||
options?.fallbackTitle ||
"Platform";
return (
<OgPlatformImage
title={title}
logo={normalizeOgText(page.logo) || undefined}
items={items}
/>
);
}
async function resolveOgContent(
{ id }: ContentProps,
fetcher: (
documentId: string,
) => Promise<[OgFetcherResult | null | undefined, string | undefined]>,
options?: { defaultTitle?: string },
) {
if (!id) return null;
const [result, err] = await fetcher(id);
if (err || !result) return null;
const seoImage = normalizeOgText(result.seoImage);
if (seoImage) {
return <OgSeoImage seoImage={seoImage} />;
}
const title =
normalizeOgText(result.title) || options?.defaultTitle || undefined;
return (
<OgBrandedImage
description={result.description}
image={result.image}
logo={result.logo}
title={title}
/>
);
}
async function resolveSlugPageOgContent({ id }: ContentProps) {
if (!id) return null;
const [result, err] = await getSlugPageOGData(id);
if (err || !result) return null;
const page = result as PageOgResult;
const seoImage = normalizeOgText(page.seoImage);
if (seoImage) {
return <OgSeoImage seoImage={seoImage} />;
}
if (page.hasEventsListing) {
const [events, eventsErr] = await getUpcomingEventsForOg();
if (!eventsErr && events && events.length > 0) {
const upcoming = mapSanityEvents(events);
return (
<OgEventsImage
title={normalizeOgText(page.title) || "Events"}
logo={normalizeOgText(page.logo) || undefined}
events={upcoming}
/>
);
}
}
const platformOg = renderPlatformOg(page, { fallbackTitle: "Platform" });
if (platformOg) return platformOg;
return (
<OgBrandedImage
description={page.description}
image={page.image}
logo={page.logo}
title={page.title}
/>
);
}
async function resolveHomePageOgContent({ id }: ContentProps) {
if (!id) return null;
const [result, err] = await getHomePageOGData(id);
if (err || !result) return null;
const page = result as PageOgResult;
const seoImage = normalizeOgText(page.seoImage);
if (seoImage) {
return <OgSeoImage seoImage={seoImage} />;
}
const platformOg = renderPlatformOg(page, {
defaultTitle: OG_DEFAULT_HOME_TITLE,
});
if (platformOg) return platformOg;
const title =
normalizeOgText(page.title) || OG_DEFAULT_HOME_TITLE || undefined;
return (
<OgBrandedImage
description={page.description}
image={page.image}
logo={page.logo}
title={title}
/>
);
}
const getHomePageContent = (props: ContentProps) =>
resolveHomePageOgContent(props);
const getSlugPageContent = (props: ContentProps) =>
resolveSlugPageOgContent(props);
const getBlogPageContent = (props: ContentProps) =>
resolveOgContent(props, getBlogPageOGData);
const getGenericPageContent = (props: ContentProps) =>
resolveOgContent(props, getGenericPageOGData);
const block = {
homePage: getHomePageContent,
page: getSlugPageContent,
blog: getBlogPageContent,
} as const;
export async function GET({ url }: Request): Promise<ImageResponse> {
const { searchParams } = new URL(url);
const type = searchParams.get("type") as keyof typeof block;
const { width, height } = getOgMetaData(searchParams);
const para = Object.fromEntries(searchParams.entries());
const options = await getOgImageOptions({ width, height });
const loadContent = block[type] ?? getGenericPageContent;
try {
const content = await loadContent(para);
return new ImageResponse(content ?? <OgErrorImage />, options);
} catch (error) {
console.error("OG image generation failed:", error);
return new ImageResponse(<OgErrorImage />, options);
}
}
However, I was looking at my usage and dear lord 2 MB of ingress and egress for some images?!?!
