TakumiTakumi

Nitro

Render OG images from a Nitro server route.

Install Takumi

npm i takumi-js

Create a server route

Build the node tree with helpers, so no JSX setup is needed. Nitro handlers can return a Response, so return an ImageResponse directly.

routes/og.png.ts
import { defineHandler } from "nitro";
import { container, text } from "takumi-js/helpers";
import ImageResponse from "takumi-js/response";

export default defineHandler((event) => {
  const url = new URL(event.req.url);
  const title = url.searchParams.get("title") ?? "Takumi + Nitro";
  const description =
    url.searchParams.get("description") ?? "Render OG images from a Nitro route.";

  return new ImageResponse(
    container({
      style: {
        width: "100%",
        height: "100%",
        display: "flex",
        flexDirection: "column",
        justifyContent: "center",
        padding: "64px",
        backgroundImage: "linear-gradient(to bottom right, #fff1f2, #fecdd3)",
      },
      children: [
        text(title, { fontSize: 72, fontWeight: 700, color: "#111827" }),
        text(description, { fontSize: 42, fontWeight: 500, color: "#4b5563" }),
      ],
    }),
    {
      width: 1200,
      height: 630,
    },
  );
});

Request the endpoint

Visit /og.png?title=Hello&description=From%20Nitro.

Every Nitro preset renders on the WebAssembly backend. Nitro sets the unwasm export condition while wasm is on (the default), so the WASM binary ships inside your server bundle. Nothing has to resolve a native addon at runtime, so the same route deploys to an edge preset and runs in a WebContainer (StackBlitz, CodeSandbox).

Prefer the faster native bindings on the Node preset? Drop the condition:

nitro.config.ts
export default defineNitroConfig({
  exportConditions: ["!unwasm"],
});

wasm: false gets you the native bindings too, but it also unplugs unwasm from the build, so .wasm imports elsewhere in your app stop resolving. Negating the condition leaves the plugin in place.

Last updated on

On this page