Vercel OG Images with DatoCMS

Vercel OG Images with DatoCMS

Hi! This quick tutorial on how I implemented Vercel OG Images for the future React Miami speaker cards. (At the time of writing this, these pages are still in dev until our full lineup is announced in January 2023.)

Exactly ONE single person asked how I did this when I shared this on Twitter (s/o @ shiva_baaba) but figured it may be helpful for others so why not do a whole thing on Hashnode about it?

My project is a fork of Vercel's Virtual Event Starter Kit, therefore that is the context I'll be using to step through this guide.

First Things First

Fork the Vercel Virtual Event Starter Kit and deploy with DatoCMS: https://vercel.com/templates/next.js/virtual-event-starter-kit

Check out the docs on Vercel's OG Images and follow the installation instructions here: https://vercel.com/docs/concepts/functions/edge-functions/og-image-generation

In Your DatoCMS Project

In the Settings tab of your project, add a new field to your Speaker model called Social Image.

Now when you add a Speaker you will have a new field where you can upload your custom image. I created my custom images with Canva, but you can use whatever tool you prefer!

In Your Code Repository

Create or update the following files to implement OG images for your project from Dato sent with the Speaker information.

Create file: pages/api/og.tsx

This will handle the OG image generation and is modeled after the examples from Vercel's documentation.

Note: If a page doesn't have a custom social image, then it will default to our project's twitter-card.

import { ImageResponse } from '@vercel/og';
import { NextRequest } from 'next/server';
import { SITE_URL } from '@lib/constants';

export const config = {
  runtime: 'experimental-edge',
};

export default function handler(req: NextRequest) {
    const { searchParams } = req.nextUrl;
    const socialImageUrl = searchParams.get('entity');

    if (!socialImageUrl) {
        return new ImageResponse(
            (
                <div
                  style={{
                    display: 'flex',
                    width: '100%',
                    height: '100%',
                    flexDirection: 'column',
                    justifyContent: 'center',
                    alignItems: 'center',
                  }}
                >
                  <img
                    width="1200"
                    height="630"
                    src={`${SITE_URL}twitter-card.png`} 
                  />
                </div>
              ),
              {
                width: 1200,
                height: 630,
              },
        );
      }

  return new ImageResponse(
    (
      <div
        style={{
          display: 'flex',
          width: '100%',
          height: '100%',
          flexDirection: 'column',
          justifyContent: 'center',
          alignItems: 'center',
        }}
      >
        <img
          width="1200"
          height="630"
          src={socialImageUrl} 
        />
      </div>
    ),
    {
      width: 1200,
      height: 630,
    },
  );
}

Update file: lib/dato.ts

Update the schema for getAllSpeakers() to include socialImage.

Update the url property's width (w) and height (h) with that of the OG image.

We don't need the blurDataURL property here because that's used for the loading state of our web pages and doesn't apply to our social cards.

export async function getAllSpeakers(): Promise<Speaker[]> {
  const data = await fetchCmsAPI(`
    {
      allSpeakers(first: 100) {
        name
        bio
        title
        slug
        twitter
        github
        company
        talk {
          title
          description
        }
        image {
          url(imgixParams: {fm: jpg, fit: crop, w: 300, h: 400})
          blurDataURL: blurUpThumb
        }
        imageSquare: image {
          url(imgixParams: {fm: jpg, fit: crop, w: 192, h: 192})
          blurDataURL: blurUpThumb
        }
        socialImage {
          url(imgixParams: {fm: jpg, fit: crop, w: 1200, h: 630})
        }
      }
    }
  `);
  return data.allSpeakers;
}

Update file: lib/types.ts

Update type Speaker to include socialImage and set the type to Image.

export type Speaker = {
  name: string;
  bio: string;
  title: string;
  slug: string;
  twitter: string;
  github: string;
  company: string;
  talk: Talk;
  image: Image;
  imageSquare: Image;
  socialImage: Image;
};

Update file: pages/speakers/[slug].tsx

Import the SITE_URL variable from @lib/constants.

Update the meta object to include the image property that is available via the Meta type in /components/page.tsx.

Set the value of image to the og api we created earlier and add a parameter called entity.

Set the value of the entity parameter to the social image we have included with our Speaker information.

import { SITE_URL } from '@lib/constants';

const meta = {
    title: 'Demo - Virtual Event Starter Kit',
    description: META_DESCRIPTION
    image: `${SITE_URL}api/og?entity=${speaker?.socialImage?.url}`
  };

Et Violà!

This is how it looks when it's all done!

This page has a social image and you can see it here:

Screenshot of social card with custom image of speaker

This page does not have a social image so you can see our twitter-card default here:

Screenshot of social card with default image

All in all, even though Vercel's OG Images are brand new, I believe they're going to quickly catch fire and be used everywhere. I recommend trying them out with one of your projects and of course, reading the docs from their team first!

Hope this helps! If you have any questions, leave them in the replies below!