@vercel/og
Satori based Open Graph Image generation adapter for Vercel’s Edge Function.
Basic Usage
Install @vercel/og
, then use it inside Edge API routes in your Next.js project:
import { ImageResponse } from '@vercel/og'
export const config = {
runtime: 'experimental-edge',
}
export default function () {
return new ImageResponse(
(
<div
style={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
}}
>
Hello!
</div>
)
)
}
The React element will be rendered and responded as a PNG from that Edge Function endpoint. For more details and restrictions in image generation, see Satori.
API Reference
@vercel/og
only supports the Edge Runtime. The Node.js runtime will not work.
The package exposes an ImageResponse
constructor, with the following options available:
import { ImageResponse } from '@vercel/og'
new ImageResponse(
element: ReactElement,
options: {
width?: number = 1200
height?: number = 800
emoji?: 'twemoji' | 'blobmoji' | 'noto' | 'openmoji' = 'twemoji',
fonts?: {
name: string,
data: ArrayBuffer,
weight: number,
style: 'normal' | 'italic'
}[]
debug?: boolean = false
status?: number = 200
statusText?: string
headers?: Record<string, string>
},
)
By default, these headers will be included by @vercel/og
:
'content-type': 'image/png',
'cache-control': 'public, immutable, no-transform, max-age=31536000',
You can see the Code Examples section below to see more details.
Code Examples
Hello World
import { ImageResponse } from '@vercel/og'
export const config = {
runtime: 'experimental-edge',
}
export default function () {
return new ImageResponse(
(
<div
style={{
fontSize: 128,
background: 'white',
width: '100%',
height: '100%',
}}
>
Hello!
</div>
)
)
}
Vercel Docs + TypeScript
https://vercel-og-service.vercel.sh/?title=Creating Deployments
import { ImageResponse } from '@vercel/og';
import { NextRequest } from 'next/server';
export const config = {
runtime: 'experimental-edge',
};
export default function handler(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
const hasTitle = searchParams.has('title');
const title = hasTitle
? searchParams.get('title').slice(0, 100)
: 'Vercel Docs';
return new ImageResponse(
(
<div
style={{
backgroundColor: 'black',
backgroundSize: '150px 150px',
height: '100%',
width: '100%',
display: 'flex',
textAlign: 'center',
alignItems: 'center',
justifyContent: 'center',
flexDirection: 'column',
flexWrap: 'nowrap',
}}
>
<div
style={{
display: 'flex',
alignItems: 'center',
alignContent: 'center',
justifyContent: 'center',
justifyItems: 'center',
}}
>
<img
alt="Vercel"
height={200}
src="data:image/svg+xml,%3Csvg width='116' height='100' fill='white' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M57.5 0L115 100H0L57.5 0z' /%3E%3C/svg%3E"
style={{ margin: '0 30px' }}
width={226.6 * 0.5}
/>
</div>
<div
style={{
// When the title is very short we increase the font size.
// We choose 14 here because that'll be the maximum width to keep it
// one line (14 “W” characters).
fontSize: title.length < 14 ? 72 : 60,
fontStyle: 'normal',
letterSpacing: '-0.025em',
color: 'white',
marginTop: 30,
padding: '0 120px',
lineHeight: 1.4,
whiteSpace: 'pre-wrap',
}}
>
{title}
</div>
</div>
),
{
width: 1800,
height: 900,
},
);
} catch (e) {
return new Response(`Failed to generate the image: ${e.message}`, {
status: 500,
});
}
}
Dynamic Image
import { ImageResponse } from '@vercel/og'
export const config = {
runtime: 'experimental-edge',
}
function arrayBufferToBase64(buffer) {
let binary = ''
const bytes = new Uint8Array(buffer)
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i])
}
return btoa(binary)
}
export default async function (req) {
const { searchParams } = req.nextUrl
const username = searchParams.get('username')
if (!username) {
return new ImageResponse(<>Visit with "?username=vercel"</>, {
width: 1200,
height: 600,
})
}
const avatarResponse = await fetch(`https://github.com/${username}.png`)
const avatarImageType = avatarResponse.headers.get('content-type')
const avatar = arrayBufferToBase64(await avatarResponse.arrayBuffer())
return new ImageResponse(
(
<div
style={{
fontSize: 60,
color: 'black',
background: '#f6f6f6',
width: '100%',
height: '100%',
paddingTop: 50,
flexDirection: 'column',
justifyContent: 'center',
alignContent: 'center',
alignItems: 'center',
}}
>
<img
width='256'
height='256'
src={`data:${avatarImageType};base64,${avatar}`}
style={{
borderRadius: 128,
}}
/>
<p>github.com/{username}</p>
</div>
),
{
width: 1200,
height: 600,
}
)
}
Emoji
import { ImageResponse } from '@vercel/og'
export const config = {
runtime: 'experimental-edge',
}
export default async function () {
return new ImageResponse(
(
<div
style={{
fontSize: 100,
color: 'black',
background: 'white',
width: '100%',
height: '100%',
padding: '0 200px',
textAlign: 'center',
justifyContent: 'center',
alignContent: 'center',
}}
>
👋, 🌎
</div>
),
{
width: 1200,
height: 600,
emoji: 'twemoji',
}
)
}
Image SVG
import { ImageResponse } from '@vercel/og'
export const config = {
runtime: 'experimental-edge',
}
export default async function () {
return new ImageResponse(
(
<div
style={{
fontSize: 40,
color: 'black',
background: 'white',
width: '100%',
height: '100%',
padding: '0 200px',
textAlign: 'center',
justifyContent: 'center',
alignContent: 'center',
}}
>
<img
width='284'
height='65'
src="data:image/svg+xml;utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='black' height='65' viewBox='0 0 284 65'%3E%3Cpath d='M141.68 16.25c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zm117.14-14.5c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zm-39.03 3.5c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9v-46h9zM37.59.25l36.95 64H.64l36.95-64zm92.38 5l-27.71 48-27.71-48h10.39l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10v14.8h-9v-34h9v9.2c0-5.08 5.91-9.2 13.2-9.2z'%3E%3C/path%3E%3C/svg%3E"
/>
</div>
),
{
width: 1200,
height: 600,
}
)
}
Different Languages
import { ImageResponse } from '@vercel/og'
export const config = {
runtime: 'experimental-edge',
}
export default async function () {
return new ImageResponse(
(
<div
style={{
fontSize: 40,
color: 'black',
background: 'white',
width: '100%',
height: '100%',
padding: '0 200px',
textAlign: 'center',
justifyContent: 'center',
alignContent: 'center',
}}
>
👋 Hello 你好 नमस्ते こんにちは สวัสดีค่ะ 안녕 добрий день Hallá
</div>
),
{
width: 1200,
height: 600,
}
)
}