Add support for jpeg output
Feature Request
Description
I'd like to be able to serve my OG images in JPEG format instead of PNG format. This can provide a huge savings in terms of network bandwidth for certain types of OG images (which can be exacerbated when viewing content on mobile aggregators).
Additional Context
Not sure if this belongs in satori or @vercel/og, but since @vercel/og seems to be closed source for the time being, I'm opening an issue here.
Here are some related resources:
- Facebook's OG guide supports image/jpeg, image/gif and image/png
- Hubspot's guide uses jpeg in all of its examples
- Bannerbear uses jpeg for its OG images
There are quite a few other examples I could find from a cursory search that use either png or jpeg images, so it seems to be 50/50 depending on the nature of the open graph images. PNG will work well for more sparse / vector graphics, whereas JPEG will work better for more photo-like images, and it should be up to the user to choose which format works better for their use case.
As an example, this OG image created with Satori generates a 906 KB PNG. When converted, it is only a 174 KB JPEG.

@transitive-bullshit you can use @napi-rs/canvas package to convert SVG generated by satori to jpeg or any other format, like that:
import satori from "satori";
import { Canvas, Image } from "@napi-rs/canvas";
import { writeFile } from "node:fs/promises";
const svg = await satori(<div />, { width, height });
const image = new Image();
image.src = Buffer.from(svg, "utf8");
const canvas = new Canvas(width, height);
const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
const jpegData = await canvas.encode("jpeg");
await writeFile("./out.jpeg", jpegData);
Perhaps @napi-rs/image might work and it would be even smaller. Although I'm not sure if it supports SVG though 🤔
Thanks, guys; appreciate the quick replies 🙏
I don't think I was clear enough in my original description, however. I meant this issue to be more about supporting JPEG output from Vercel OG images, whereas @napi-rs/canvas looks to be Node.js-only.
As I mentioned above, this issue probably belongs in @vercel/og, but since @vercel/og seems to be closed source for the time being, I'm opening an issue here instead.
ImageResponse from @vercel/og seems to only support PNG output right now, whereas I think it should support JPEG output as well.
Something like:
import { ImageResponse } from '@vercel/og'
export default async function OGImage(req: NextRequest) {
return new ImageResponse(
(
<div />
),
{
width: 1200,
height: 630,
type: 'jpeg' // | 'png' | 'svg'
}
)
}
export const config = {
runtime: 'experimental-edge'
}
@napi-rs/image looks promising.
We are relying on the Resvg dependency which only supports PNG at the moment. If adding another converter (PNG → JPEG) it will be too expensive in both size and speed.
Indeed, we we need to convert SVG → JPEG to make it worth while.
@styfle Yes, converting SVG directly to other image formats is a good option.
Perhaps
@napi-rs/imagemight work and it would be even smaller. Although I'm not sure if it supports SVG though 🤔
Now @napi-rs/image support svg as input! Basic example:
import satori from "satori";
import { Transformer } from "@napi-rs/image"
export async function createOG() {
const svg = await satori(<div>test</div>, { width: 600, height: 400 })
const trasformer = Transformer.fromSvg(svg); // satori result
const jpeg = await trasformer.jpeg();
return jpeg; // buffer
}
Looking forward to a lightweight @napi-rs/image build with only svg → png and svg → jpeg features 👍
getting:
- wait compiling /blog/[slug]/opengraph-image/[[...__metadata_id__]]/route (client and server)...
- error ./node_modules/.pnpm/@[email protected]/node_modules/@napi-rs/image-darwin-arm64/image.darwin-arm64.node
Module parse failed: Unexpected character '�' (1:0)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
(Source code omitted for this binary file)
anyone know what is going on here?
Had to add:
export default {
webpack: (config) => {
config.externalsType = 'commonjs';
config.externals = config.externals || [];
config.externals.push({
'@napi-rs/image': "@napi-rs/image",
});
return config;
},
};
to next.config.js
I was about to use one of the solutions above when I remembered that https://wsrv.nl allows converting images on the fly. For example:
Create your og image as normal
const ogURL = `${baseUrl}/api/og?name=${encodeURIComponent(
name
)}"e=${encodeURIComponent(quote)}&image=${encodeURIComponent(image)}`
Convert it using wsrv
const convertedURL = `https://wsrv.nl/?url=${encodeURIComponent(
ogURL
)}&output=jpg`
@napi-rs/image won't work on Edge Functions fwi.
J