DEV Community

Cover image for Dynamic OG image with SvelteKit and Satori
Shivam Meena
Shivam Meena

Posted on

Dynamic OG image with SvelteKit and Satori

Introduction

We all share links/url to our friends and we always see a beautiful image which is OpenGraph(OG) Image. OG images use to be static images and with time we got puppeteer which uses browser to take screenshot of your web page then you serve it from server. Recently, Vercel dropped a bomb @vercel/og with Satori which doesn't need a browser and you can generate Dynamic OG Images on the fly. As we know with every solution, we got some problems to be solved.

@vercel/og is dependent on Satori. Satori is a new libray that convert HTML and CSS to SVG. Satori only accepts two params those are ReactElement (like JSX) and options. In SvelteKit, we don't have a ReactElement type syntax. Here, we need a html to ReactElement converter and Satori-html is what we need to do that.

Here, I'm going to explain a way to achieve Dynamic OG Images using html syntax. Read to the end, A pretty good surprise for all of You.

Flow to generate SVG

  • We first need a html template which we need to convert into SVG.
  • We going to pass that html template to html function from satori-html, this will generate our ReactElement syntax for html template.
  • Now, we will pass our ReactElement like syntax and options (options are defined in satori readme) to satori and this will return us with a SVG.

Note: SVG are not suppose to be served for OG Images (we use png format in OG Images) where we use SVG to PNG coverter Resvg.

Generate SVG from HTML Template

  • Create a new SvelteKit Project
pnpm create svelte@next
Enter fullscreen mode Exit fullscreen mode
  • Then, In routes folder create a route with +server.ts or +server.js. Which will going to serve our OG Image for our shareable link.
// +server.js

import satori from 'satori';
import {Resvg} from '@resvg/resvg-js';
import {html as toReactElement} from 'satori-html';

const fontFile = await fetch('https://og-playground.vercel.app/inter-latin-ext-700-normal.woff');
const fontData: ArrayBuffer = await fontFile.arrayBuffer();

const height = 630;
const width = 1200;

/** @type {import('./$types').RequestHandler} */
export const GET = async () => {
  const html = toReactElement('<div style="color: red;">Hello, World!</div>')
  const svg = await satori(html, {
    fonts: [
      {
        name: 'Inter Latin',
        data: fontData,
        style: 'normal'
      }
    ],
    height,
    width
  });

  const resvg = new Resvg(svg, {
    fitTo: {
      mode: 'width',
      value: width
    }
  });

  const image = resvg.render();

  return new Response(image.asPng(), {
    headers: {
      'content-type': 'image/png'
    }
  });
};
Enter fullscreen mode Exit fullscreen mode

In above code snippet,

  • First we import our libraries: Satori, Satori-html and @resvg/resvg-js.

  • Then we are fetching our font and convert it into to arraybuffer.

  • Then we defined height, width for our svg.

  • Then we gonna write a Get Api for our endpoint, which will gonna return a response Object with png.

  • Next we defined our html syntax which is converted to ReactElement syntax using toReactElement (import {html as toReactElement} from 'satori-html').

  • Now, we gonna pass our html object to satori with required options. This will going to generate a SVG.

Options = {
    fonts: [
      {
        name: 'Inter Latin',
        data: fontData,
        style: 'normal'
      }
    ],
    height,
    width
  }
Enter fullscreen mode Exit fullscreen mode
  • Now, we have a SVG we gonna use resvg to convert it into PNG. Then we are going to return our response.
const resvg = new Resvg(svg, {
    fitTo: {
      mode: 'width',
      value: width
    }
  });

  const image = resvg.render();

  return new Response(image.asPng(), {
    headers: {
      'content-type': 'image/png'
    }
  });
Enter fullscreen mode Exit fullscreen mode

This is a basic example of using satori in Svelte/SvelteKit. Few days ago, geoffrich released a post [Create dynamic social card images with Svelte components](https://geoffrich.net/posts/svelte-social-image/) which is deep dive into this.

Surprise: @ethercorps/sveltekit-og

This is a library inspired from @vercel/og and similar to it. I released@ethercorps/sveltekit-og
this morning and you can use it with normal html. Here I'm showing you two examples which returns an Image Response.

  • Example: ImageResponse
import { ImageResponse } from '@ethercorps/sveltekit-og';
import type { RequestHandler } from './$types';

const template = `
 <div tw="bg-gray-50 flex w-full h-full items-center justify-center">
    <div tw="flex flex-col md:flex-row w-full py-12 px-4 md:items-center justify-between p-8">
      <h2 tw="flex flex-col text-3xl sm:text-4xl font-bold tracking-tight text-gray-900 text-left">
        <span>Ready to dive in?</span>
        <span tw="text-indigo-600">Start your free trial today.</span>
      </h2>
      <div tw="mt-8 flex md:mt-0">
        <div tw="flex rounded-md shadow">
          <a href="#" tw="flex items-center justify-center rounded-md border border-transparent bg-indigo-600 px-5 py-3 text-base font-medium text-white">Get started</a>
        </div>
        <div tw="ml-3 flex rounded-md shadow">
          <a href="#" tw="flex items-center justify-center rounded-md border border-transparent bg-white px-5 py-3 text-base font-medium text-indigo-600">Learn more</a>
        </div>
      </div>
    </div>
  </div>
`;
const fontFile = await fetch('https://og-playground.vercel.app/inter-latin-ext-400-normal.woff');
const fontData: ArrayBuffer = await fontFile.arrayBuffer();

export const GET: RequestHandler = async () => {
    return new ImageResponse(template, {
        height: 250,
        width: 500,
        fonts: [
            {
                name: 'Inter Latin',
                data: fontData400,
                weight: 400
            }
        ]
    });
};
Enter fullscreen mode Exit fullscreen mode

As you know, satori supports tailwind css too this example shows html template with tailwind css.

In above code snippet, We are importing import { ImageResponse } from '@ethercorps/sveltekit-og'; and then we defined our html template and we passed it to ImageResponse with options and for more read readme.

  • Example: ComponentToImageResponse

Here, I'm going to use Geoff Rich's Component Code.

+server.ts
import Card from '$lib/Card.svelte';

export const GET: RequestHandler = async () => {
    return new ComponentToImageResponse(Card, {}, {
        height: 250,
        width: 500,
        fonts: [
            {
                name: 'Inter Latin',
                data: fontData400,
                weight: 400
            }
        ]
    });
};
Enter fullscreen mode Exit fullscreen mode

Here, I'm passing Card component and a empty dict {} which going to contain props for our component with options and this will going to return Image Response for your OG meta tag.

This is all you going to need for generating dynamic OG Images for your shareable links.

This is me writing for you. If you wanna ask or suggest anything please put it in comment and show some love ❤️.

In India, after few days we have a festival Deewali or Deepawali. I wish you all a very happy diwali 🪔.

Top comments (1)

Collapse
 
jdgamble555 profile image
Jonathan Gamble • Edited

Just noting that this won't work on the Edge because Vercel refuses to fix this. Make some noise here and maybe they will listen.

github.com/vercel/satori/issues/582