Dynamic OG image with SvelteKit and Satori
Tags: opensource, svelte, sveltekit, javascript
Published: 2022-10-20 15:39:31

## 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](https://github.com/puppeteer/puppeteer) which uses browser to take screenshot of your web page then you serve it from server. Recently, Vercel dropped a bomb [@vercel/og](https://vercel.com/docs/concepts/functions/edge-functions/og-image-api) with [Satori](https://github.com/vercel/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](https://github.com/natemoo-re/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](https://github.com/yisibl/resvg-js).

## Generate SVG from HTML Template
- Create a new SvelteKit Project
```bash
pnpm create svelte@next
```
- 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.

```typescript
// +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'
    }
  });
};
```

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.
``` javascript
Options = {
    fonts: [
      {
        name: 'Inter Latin',
        data: fontData,
        style: 'normal'
      }
    ],
    height,
    width
  }
```
- Now, we have a SVG we gonna use `resvg` to convert it into PNG. Then we are going to return our response.

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

  const image = resvg.render();

  return new Response(image.asPng(), {
    headers: {
      'content-type': 'image/png'
    }
  });
```
> 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](https://github.com/etherCorps/sveltekit-og)

This is a library inspired from `@vercel/og` and similar to it. I released[@ethercorps/sveltekit-og](https://github.com/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`
```typescript 
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
			}
		]
	});
};
```

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](https://github.com/etherCorps/sveltekit-og#readme).

- Example: `ComponentToImageResponse`

Here, I'm going to use [Geoff Rich's Component Code](https://geoffrich.net/posts/svelte-social-image/#heading-wiring-this-up-to-a-svelte-component).

```
+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
			}
		]
	});
};
```
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 🪔.