import { ImageResponse } from "next/og"; import { notFound } from "next/navigation"; import { join } from "path"; import { existsSync } from "fs"; import { readFile } from "fs/promises"; import { getSlugs, 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 dynamicParams = false; export const generateStaticParams = async () => { const slugs = await getSlugs(); // 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) { console.error(`[og-image] found "${imagePath}" but couldn't read it:`, error); // fail silently and return a 1x1 transparent gif instead of crashing return NO_IMAGE; } }; const OpenGraphImage = async ({ params }: { params: Promise<{ slug: string }> }) => { try { const { slug } = await params; // get the post's title and image filename from its frontmatter const frontmatter = await getFrontMatter(slug); // template is HEAVILY inspired by https://og-new.clerkstage.dev/ return new ImageResponse( (
')`, backgroundRepeat: "repeat", }} >
')`, maskImage: "radial-gradient(rgb(0, 0, 0) 0%, rgba(0, 0, 0, 0) 80%)", }} >
Notes
{frontmatter!.title}
{new Date(frontmatter!.date).toLocaleDateString("en-US", { year: "numeric", month: "long", day: "numeric", })}
{frontmatter!.image && (
)}
), { ...size, fonts: [ { name: "Geist-Regular", // 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-Regular.ttf")), style: "normal", weight: 400, }, { name: "Geist-SemiBold", data: await readFile(join(process.cwd(), "node_modules/geist/dist/fonts/geist-sans/Geist-SemiBold.ttf")), style: "normal", weight: 700, }, ], } ); } catch (error) { console.error("[og-image] error generating image:", error); notFound(); } }; export default OpenGraphImage;