Generate dynamic og:images just like GitHub
By Patricio Lopez JuriIf 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 π
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
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:
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.
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.
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.
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 π
In the meantime you are invited to:
- π Check our official documentation at docs.flyyer.io
- π Join the official Discord channel flyyer.io/discord
- π¦ Follow us on Twitter @useflyyer
- π Deploy public templates to Flyyer Community, click here to learn how.