Generate dynamic og:images just like GitHub

By Patricio Lopez Juri

If you are a developer like me, you probably were surprised too when we saw the brand new link previews from GitHub on Twitter or on some Slack channels.

Here at Flayyer what we do is enabling companies to automate the image generation process for their social media and marketing channels.

Today I'm going to show how to create GitHub-like previews or "cards" (you name it) using Flayyer.io, React.js and TailwindCSS.

Note that GitHub probably rolled their own infrastructure and servers and we don't have any affiliation with them.


What we are going to do

You can jump to the final result and code here: https://github.com/flayyer/flayyer-marketplace-github-cards

We will create a Flayyer template to render images just like GitHub does. Here is a live example I crafted for you, go and modify the URL to see how it works πŸ‘‡

Generated image:

Getting started

Use create-flayyer-app to scaffold the project. Yes, this is the same repository from the demo right before this section.

# For NPM users
npm init flayyer-app@latest github-cards

# For Yarn users
yarn create flayyer-app github-cards

There are many starter projects, in this example I will pick react-typescript-tailwind because it's the main tech we use at Flayyer.com.

? Select the best template setup for you …  You can customize the template later
  ...
  react-typescript-styled-components
> react-typescript-tailwind
  vue
  ...

Next step is cd into the directory and installing the dependencies:

cd github-cards
npm install

Running npm start will open Flayyer Studio in our browsers at flayyer.github.io/flayyer-studio/.

npm start

studio demo

Coding phase

Open your favorite IDE and make a small tweak on Tailwind by changing the global font-size at styles/tailwind.css file.

html {
  /* Tailwind's default is 16px */
  font-size: 36px;
}

This is recommended for Flayyer templates since Tailwind uses rem based units for almost everything.

Edit the templates/article.tsx file (and rename it to templates/repository.tsx), we are going to create the structure and style for the base og:image which is 1200x630px.

The resulting view will be:

resulting view

Here is the big chunk of code to accomplish that, please don't panic.

import React from 'react';
import {TemplateProps} from '@flayyer/flayyer-types';
import {VscOrganization, VscIssues, VscStarEmpty, VscRepoForked} from 'react-icons/vsc';
import clsx from 'clsx';

import '../styles/tailwind.css';
import {Layer} from '../components/layers';

// Make sure to 'export default' a React component
export default function RepositoryTemplate(props: TemplateProps) {
  // Variables are parsed from the querystring.
  const {width, height, variables} = props;

  const [owner, repo] = (variables.title || '').split('/');
  const stats = [
    {Icon: VscOrganization, title: 'Contributors', count: variables.contributors},
    {Icon: VscIssues, title: 'Issues', count: variables.issues},
    {Icon: VscStarEmpty, title: 'Stars', count: variables.stars},
    {Icon: VscRepoForked, title: 'Forks', count: variables.forks}
  ];

  return (
    <Layer
      className={clsx(
        'bg-white text-gray-500',
        'px-7 pt-10 pb-8',
        'grid grid-cols-12 grid-rows-12 gap-x-5'
      )}
    >
      <header className="col-span-9 row-span-10">
        <h1 className={clsx('text-3xl tracking-normal text-gray-800')}>
          <span className="">{owner}</span>
          {owner && repo && <span>/</span>}
          <span className="font-bold">{repo}</span>
        </h1>

        <p className={clsx('pt-3', 'text-base font-light tracking-wide leading-snug text-gray-500')}>
          {variables.description}
        </p>
      </header>

      <div className={clsx('col-span-3 row-span-10')}>
        {variables.avatar && (
          <img src={variables.avatar} className="inline rounded-md overflow-hidden w-full object-contain" />
        )}
      </div>

      <dl
        className={clsx(
          'col-span-12 row-span-2',
          'flex flex-row flex-wrap space-x-6',
          'leading-none' // sets line-height to 1
        )}
      >
        {stats.map(({title, count, Icon}, i) => (
          <div key={i} className="flex flex-row space-x-1">
            <div className="flex-grow-0">
              <Icon className="w-4 h-4 text-gray-500" />
            </div>
            <div className="mt-0.5">
              <dt className="text-sm text-gray-700">
                {Number.isFinite(count) ? count : '-'}
              </dt>
              <dd className="text-xs text-gray-400">
                {title}
              </dd>
            </div>
          </div>
        ))}
      </dl>
    </Layer>
  );
}

Copy variables and paste them on the Variables fields of Flayyer Studio to get the same image as the screenshot I posted.

{
  title: "flayyer/create-flayyer-app",
  description: "Hello world",
  avatar: "https://avatars.githubusercontent.com/u/67559670",
}

Responsive sizes

To avoid bloating this post with an excess of code, you can see the final code with the responsive utilities here: github.com/flayyer/flayyer-marketplace-github-cards.

final result

A few things about Tailwind

Default responsive classes such as xs, sm, md, lg, etc are intended for devices with many possible screen resolutions, from mobile phones to desktops.

We recommend og:images resolution of 1200x630px so those built-in breakpoints are not the best suited. Additionally, square and story both have a width of 1080px but different height (1080x1080px and 1080x1920px respectively).

That is why we added these additional breakpoints to work seamlessly with Flayyer templates. Take a look into tailwind.config.js:

// tailwind.config.js β€” already included with create-flayyer-app βœ…
screens: {
  /* For apps where images are always small squares such as WhatsApp (400x400px) */
  thumb: {raw: '(min-height: 400px)'},
  /* Recommended horizontal image for sites like Facebook, Twitter, etc. (og:image) (1200x630px) */
  banner: {raw: '(min-height: 630px)'},
  /* The size of an Instagram post. */
  sq: {raw: '(min-height: 1080px)'},
  /* The size of a full-screen story for Instagram and others. */
  story: {raw: '(min-height: 1920px)'},
  /* Keep default Tailwind sizes (https://tailwindcss.com/docs/breakpoints#extending-the-default-breakpoints) */
  ...defaultTheme.screens
}

Here is an example taken from the final code of this template:

<h1
  className={clsx(
    // For square posts and stories add a top margin.
    'sq:mt-4',
    // Use bigger font on stories
    'text-3xl story:text-4xl tracking-normal text-gray-800'
  )}
>

Deploying

When you are ready to deploy you must create a production build of your deck with flayyer build

NODE_ENV=production npm run-script build

Try running flayyer deploy with:

npm run-script deploy

If you get this error, you need to create an account on Flayyer.com and set the FLAYYER_KEY before deploying.

β€Ί   Error: Missing 'key' property in file 'flayyer.config.js'.
β€Ί
β€Ί   Remember to setup your 'FLAYYER_KEY' environment variable.
β€Ί
β€Ί   Forgot your key? Go to https://flayyer.com/dashboard/_/settings
β€Ί   First time using Flayyer? Create an account at https://flayyer.com/get-started

It's free for the first 500 images each month, no credit card required. Get started πŸ‘ˆ

Then get your FLAYYER_KEY from Settings.

Please keep your key secret safe to protect your account.

dashboard where the keys are

You can create a .env file and set your key over there.

# .env
FLAYYER_KEY=

Now you should be able to successfully deploy it with npm run-script deploy and get an output like this:

πŸ”‘   Identified as flayyer
πŸ“¦   Uploading bundled react-typescript app...
🌠   flayyer project successfully deployed!

...

πŸ–Ό    Created template repository with URL:
      - https://flayyer.io/v2/flayyer/github-cards/repository.jpeg
    Versioned (omit to use latest):
      - https://flayyer.io/v2/flayyer/github-cards/repository.1620002194.jpeg
    Supported extensions (jpeg, png, webp):
      - https://flayyer.io/v2/flayyer/github-cards/repository.png
    Cache burst:
      - https://flayyer.io/v2/flayyer/github-cards/repository.jpeg?__v=1620002204
    Set size (defaults to 1200x630):
      - https://flayyer.io/v2/flayyer/github-cards/repository.jpeg?_w=1080&_h=1080
    Set variable:
      - https://flayyer.io/v2/flayyer/github-cards/repository.jpeg?title=Thanks+for+using+Flayyer
    Multiple variables:
      - https://flayyer.io/v2/flayyer/github-cards/repository.jpeg?title=Title&description=Emoji+supported+πŸ˜ƒ


πŸ“–   Checkout the official integration guides at: https://docs.flayyer.com/guides
πŸ“–   Flayyer URL formatters: https://docs.flayyer.com/docs/libraries

πŸ‘‰   Follow us on Twitter at: https://twitter.com/flayyer_com
πŸ‘‰   Join our Discord community at: https://flayyer.com/discord

You can also use the deployed template on our dashboard to generate images. It should be available at flayyer.com/dashboard/_/library.

library screenshot

Integration

The final step consist on updating the content attribute of your website's og:image and twitter:image meta-tags.

If you are not familiar with these HTML tags here is what you need to know:

When a website is share on social-media a crawler is execute to grab important information to display to the users in a form of preview card, so the users can know what the links are about before even visiting them.

These crawlers usually read the content of the <head /> meta-tags, in particular og:title, og:description and og:image among others. Here is where Flayyer comes in, we will use the Flayyer.io URL as og:image to generate custom images for every page.

Here is how an integration with Next.js would look like:

import React from "react"
import Head from "next/head"
import { FlayyerIO } from "@flayyer/flayyer"

export function MyHead({ title, description, avatar, contributors, issues, forks, stars }) {
  const flayyer = new FlayyerIO({
    tenant: "flayyer", // your profile identifier
    deck: "github-cards",
    template: "repository",
    variables: {
      title, description, avatar, contributors, issues, forks, stars,
    }
  })
  return (
    <Head>
      <meta property="og:image" content={flayyer.href()} />
      <meta name="twitter:image" content={flayyer.href()} />
      <meta name="twitter:card" content="summary_large_image" />
    </Head>
  )
}

Next steps

On the next post I will show how to declare variables using @flayyer/variables so people can interact with your templates with a Live Template and Variables UI at the Flayyer.com dashboard 🏎

variables ui

In the meantime you are invited to: