Statically Generating OpenGraph Images in Astro

Vercel's docs "forces" us to use their edge function. Satori's docs is not clear enough. How about statically generating it?

By Muhammad Rizqi Ardiansyah on 2025-06-05

Prerequisites

This post requires you to read satori’s docs first

Create a static file endpoint

A neat feature in Astro is that you can use static file endpoint to generate files, even other than HTML.

Make the getStaticPaths function first. Below is just an example—if you have other ways to retrieve paths feel free to write it yourself.

export async function getStaticPaths() {
  const posts = await getCollection("posts");

  return posts.map((post) => ({
    params: {
      id: post.id,
    },
    props: {
      data: post.data,
      post: post,
    },
  }));
}

Load fonts. For me I put my font in the /public folder. Load it using Node.js’ readFile function. Also, make sure to not load variable font.

const interFontData = await readFile(
  resolve(
    dirname(fileURLToPath(import.meta.url)),
    "../../../public/styles/fonts/inter/Inter-Regular.ttf",
  ),
);

Create static file endpoints. While Satori supports JSX, as far as I know, you can not use .jsx/.tsx files as the static file endpoint. So, sadly, I need to write the layout in JavaScript object.

const interFontData = await readFile(
  resolve(
    dirname(fileURLToPath(import.meta.url)),
    "../../../public/styles/fonts/inter/Inter-Regular.ttf",
  ),
);

export const GET: APIRoute = async ({ props }) => {
  const svg = await satori(
    {
      /** React nodes represented as JavaScript object. Stripped due to length. **/
    },
    {
      width: 1200,
      height: 630,
      fonts: [
        {
          name: "Inter Regular",
          data: interFontData,
          style: "normal",
        },
      ],
    },
  );
};

At this point, Satori generates SVG file. But, this format is not usable as an OG image. As such, we need to convert it using Resvg.

const interFontData = await readFile(
  resolve(
    dirname(fileURLToPath(import.meta.url)),
    "../../../public/styles/fonts/inter/Inter-Regular.ttf",
  ),
);

export const GET: APIRoute = async ({ props }) => {
  const svg = await satori(
    {
      /** React nodes represented as JavaScript object. Stripped due to length. **/
    },
    {
      width: 1200,
      height: 630,
      fonts: [
        {
          name: "Inter Regular",
          data: interFontData,
          style: "normal",
        },
      ],
    },
  );
+
+  const resvg = new Resvg(svg);
+  const png = resvg.render().asPng();
+
+  return new Response(png);
};

That’s it.