import { ImageResponse } from "next/og"; import { notFound } from "next/navigation"; import { join } from "path"; import { existsSync } from "fs"; import { readFile } from "fs/promises"; import { getPostSlugs, getFrontMatter } from "../../../lib/helpers/posts"; import { POSTS_DIR, AVATAR_PATH } from "../../../lib/config/constants"; export const contentType = "image/png"; export const size = { // https://developers.facebook.com/docs/sharing/webmasters/images/ width: 1200, height: 630, }; // generate and cache these images at build-time for each slug, since doing this on-demand is mega slow... export const dynamic = "force-static"; export const dynamicParams = false; export const generateStaticParams = async () => { const slugs = await getPostSlugs(); // map slugs into a static paths object required by next.js return slugs.map((slug) => ({ slug, })); }; const getLocalImage = async (src: string): Promise => { // https://stackoverflow.com/questions/5775469/whats-the-valid-way-to-include-an-image-with-no-src/14115340#14115340 const NO_IMAGE = "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="; const imagePath = join(process.cwd(), src); try { if (!existsSync(imagePath)) { console.error(`[og-image] couldn't find an image file located at "${imagePath}"`); // return a 1x1 transparent gif if the image doesn't exist instead of crashing return NO_IMAGE; } // return the raw image data as a buffer return Uint8Array.from(await readFile(imagePath)).buffer; } catch (error) { // fail silently and return a 1x1 transparent gif instead of crashing console.error(`[og-image] found "${imagePath}" but couldn't read it:`, error); return NO_IMAGE; } }; const Image = async ({ params }: { params: Promise<{ slug: string }> }) => { try { const { slug } = await params; // get the post's title and image filename from its frontmatter const { title, image: imagePath } = await getFrontMatter(slug); return new ImageResponse( (
{imagePath && (
{/* eslint-disable-next-line jsx-a11y/alt-text */}
)} {AVATAR_PATH && (
{/* eslint-disable-next-line jsx-a11y/alt-text */}
)}
{title}
), { ...size, fonts: [ { name: "Geist", // load the Geist font directly from its npm package // IMPORTANT: include this exact path in next.config.ts under "outputFileTracingIncludes" data: await readFile(join(process.cwd(), "node_modules/geist/dist/fonts/geist-sans/Geist-SemiBold.ttf")), style: "normal", weight: 600, }, ], } ); } catch (error) { console.error("[og-image] error generating image:", error); notFound(); } }; export default Image;