134 lines
3.5 KiB
Plaintext
134 lines
3.5 KiB
Plaintext
---
|
|
title: "Generate OG Images using Satori"
|
|
sidebarTitle: "Satori OG Images"
|
|
description: "Learn how to generate dynamic Open Graph images using Satori and Trigger.dev."
|
|
---
|
|
|
|
## Overview
|
|
|
|
This example demonstrates how to use Trigger.dev to generate dynamic Open Graph (OG) images using Vercel's [Satori](https://github.com/vercel/satori). The task takes a title and image URL as input and generates a beautiful OG image with text overlay.
|
|
|
|
This can be customized and extended however you like, full list of options can be found [here](https://github.com/vercel/satori).
|
|
|
|
## Task code
|
|
|
|
```tsx trigger/generateOgImage.ts
|
|
import { schemaTask } from "@trigger.dev/sdk";
|
|
import { z } from "zod";
|
|
import satori from "satori";
|
|
import sharp from "sharp";
|
|
import { join } from "path";
|
|
import fs from "fs/promises";
|
|
|
|
export const generateOgImage = schemaTask({
|
|
id: "generate-og-image",
|
|
schema: z.object({
|
|
width: z.number().optional(),
|
|
height: z.number().optional(),
|
|
title: z.string(),
|
|
imageUrl: z.string().url(),
|
|
}),
|
|
run: async (payload) => {
|
|
// Load font
|
|
const fontResponse = await fetch(
|
|
"https://github.com/googlefonts/roboto/raw/main/src/hinted/Roboto-Regular.ttf"
|
|
).then((res) => res.arrayBuffer());
|
|
|
|
// Fetch and convert image to base64
|
|
const imageResponse = await fetch(payload.imageUrl);
|
|
const imageBuffer = await imageResponse.arrayBuffer();
|
|
const imageBase64 = `data:${
|
|
imageResponse.headers.get("content-type") || "image/jpeg"
|
|
};base64,${Buffer.from(imageBuffer).toString("base64")}`;
|
|
|
|
const markup = (
|
|
<div
|
|
style={{
|
|
width: payload.width ?? 1200,
|
|
height: payload.height ?? 630,
|
|
display: "flex",
|
|
backgroundColor: "#121317",
|
|
position: "relative",
|
|
fontFamily: "Roboto",
|
|
}}
|
|
>
|
|
<img
|
|
src={imageBase64}
|
|
width={payload.width ?? 1200}
|
|
height={payload.height ?? 630}
|
|
style={{
|
|
objectFit: "cover",
|
|
}}
|
|
/>
|
|
<h1
|
|
style={{
|
|
fontSize: "60px",
|
|
fontWeight: "bold",
|
|
color: "#fff",
|
|
margin: 0,
|
|
position: "absolute",
|
|
top: "50%",
|
|
transform: "translateY(-50%)",
|
|
left: "48px",
|
|
maxWidth: "60%",
|
|
textShadow: "0 2px 4px rgba(0,0,0,0.5)",
|
|
}}
|
|
>
|
|
{payload.title}
|
|
</h1>
|
|
</div>
|
|
);
|
|
|
|
const svg = await satori(markup, {
|
|
width: payload.width ?? 1200,
|
|
height: payload.height ?? 630,
|
|
fonts: [
|
|
{
|
|
name: "Roboto",
|
|
data: fontResponse,
|
|
weight: 400,
|
|
style: "normal",
|
|
},
|
|
],
|
|
});
|
|
|
|
const fileName = `og-${Date.now()}.jpg`;
|
|
const tempDir = join(process.cwd(), "tmp");
|
|
await fs.mkdir(tempDir, { recursive: true });
|
|
const outputPath = join(tempDir, fileName);
|
|
|
|
await sharp(Buffer.from(svg))
|
|
.jpeg({
|
|
quality: 90,
|
|
mozjpeg: true,
|
|
})
|
|
.toFile(outputPath);
|
|
|
|
return {
|
|
filePath: outputPath,
|
|
width: payload.width,
|
|
height: payload.height,
|
|
};
|
|
},
|
|
});
|
|
```
|
|
|
|
## Image example
|
|
|
|
This image was generated using the above task.
|
|
|
|

|
|
|
|
## Testing your task
|
|
|
|
To test this task in the [dashboard](https://cloud.trigger.dev), you can use the following payload:
|
|
|
|
```json
|
|
{
|
|
"title": "My Awesome OG image",
|
|
"imageUrl": "<your-image-url>",
|
|
"width": 1200, // optional, defaults to 1200
|
|
"height": 630 // optional, defaults to 630
|
|
}
|
|
```
|