Flyyer.io is shutting down

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 Flyyer 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 Flyyer.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/useflyyer/flyyer-marketplace-github-cards

We will create a Flyyer 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-flyyer-app to scaffold the project. Yes, this is the same repository from the demo right before this section.

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

# For Yarn users
yarn create flyyer-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 Flyyer.io.

? 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 Flyyer Studio in our browsers at useflyyer.github.io/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 Flyyer 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 '@flyyer/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 >{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 Flyyer Studio to get the same image as the screenshot I posted.

{
  title: "flyyer/create-flyyer-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/useflyyer/flyyer-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 Flyyer templates. Take a look into tailwind.config.js:

// tailwind.config.js β€” already included with create-flyyer-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 flyyer build

NODE_ENV=production npm run-script build

Try running flyyer deploy with:

npm run-script deploy

If you get this error, you need to create an account on Flyyer.io and set the FLYYER_KEY before deploying.

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

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

Then get your FLYYER_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
FLYYER_KEY=

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

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

...

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


πŸ“–   Checkout the official integration guides at: https://docs.flyyer.io/guides
πŸ“–   Flyyer URL formatters: https://docs.flyyer.io/docs/libraries

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

You can also use the deployed template on our dashboard to generate images. It should be available at flyyer.io/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 Flyyer comes in, we will use the Flyyer.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 { FlyyerRender } from "@flyyer/flyyer"

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

Next steps

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

variables ui

In the meantime you are invited to: