K
Kitze

5 months ago

Generating social media images by screenshotting React components

#Webdev

Let's not kid ourselves, you might like my writing but you totally clicked on this post mostly because there was a huge and fancy image to grab your attention.

 

If your blog doesn't have super huge fancy schmancy social media images yet, it's time to step up your clickbait game.

 

I'll show you an easy way to automatically generate them by screenshotting a React component during your build process.

 

Note: Yes, I know that instead of doing this at build time, you can write a serverless function that will generate these meta images at runtime, but my blog is exported as a static website and this solution works perfectly fine.

 

Overview

My blog posts are stored in GraphCMS, and for each post I have a metaImageUrl field. Usually I use a random image from Unsplash, but if I don't provide an image, a default image is used as a fallback.

 

The build script

In your package.json file add a generate-meta script, and make sure to run it before your build command.

 
"scripts": {
"build": "yarn generate-meta && next build && next export ",
"generate-meta": "ts-node --project scripts/tsconfig.json scripts/generate-meta ",
}

The screenshot function

We'll use this screenshot function to generate an image from a React component using repng.

 
import fs from 'fs';
import path from 'path';
import repng from 'repng';
const screenshot = async ({
props,
component,
options = {},
fileName,
outDir,
}) => {
//generate a picture of the component
const file = await repng(component, { props, ...options });
const finalPath = path.join(outDir, fileName);
//create the needed directories
await fs.promises.mkdir(outDir, { recursive: true });
//write the image file
return fs.promises.writeFile(finalPath, file);
};

The generate-meta.js script

This is the main script that's gonna be called when building your app. The generateMeta function will fetch the posts, loop over them, and generate a screenshot for each post.

 
import fs from 'fs';
import path from 'path';
import rimraf from 'rimraf';
import {client} from '../sensitive/graphql-client';
import {screenshot} from './screenshot';
import MetaPost from '../src/components/Meta/MetaPost';
const generateMeta = async () => {
//I'm using GraphQL, but you can also use REST to get your posts
const query = `
query {
posts {
id
slug
title
content
createdAt
badges: types
metaImageUrl
metaImage {
url
}
}
}
`;
const { posts } = await client.request(query);
console.log(`✅ Got ${posts.length} posts!`);
const projectPath = path.join(__dirname, "../public");
const metaDir = path.join(projectPath, "meta");
//delete existing screenshots (optional)
rimraf.sync(metaDir);
//loop over all the posts and call the screenshot function for each post
for (const post of posts) {
const { slug } = post;
console.log(`📸 Screenshotting ${post.type}: ${slug}...`);
await screenshot({
component: MetaPost,
props: { post },
fileName: `${slug}.png`,
outDir: path.join(metaDir, post.type),
});
}
console.log(`✅️ Done with screenshots!`);
};

The process

 

The files

 

The MetaPost component

You can design and style your React component however you want, just make sure to use the correct width and height and have conditional logic for longer titles.

 

You can check out my MetaPost component here. I'm using inline styles with styled-mixins and flex helpers.

 

Here's a preview of one of my blog posts:

 

 

☝️ The Social tab in the Sizzy Debugging Panel can help you debug and preview how your meta tags will look on different social media websites (Facebook, Twitter, Google, Slack, etc.).

 

I'm using 1200x630 size both for Facebook and Twitter meta tags. If you want, you can also write some extra logic to generate different images for different social media websites.

 

The Meta component

Of course, somewhere in your app you would have to render the <meta> tags and point to the correct image (based on the post slug).

 

I made a simple <Meta/> React component for this:

 
import React from 'react';
import Head from 'next/head';
import { isDev } from 'utils/is-prod';
const prefix = isDev ? 'http://localhost:3004' : 'https://kitze.io';
const getImage = (url) => `${prefix}/${url}`;
const Meta = ({
title = 'Kitze',
description = `Kitze's thoughts and stuff`,
url = 'https://kitze.io',
image,
facebookImage = image,
twitterImage = image
}) => {
return (
<>
<Head>
<title>{title}</title>
{/* meta */}
<meta name="title" content={title} />
<meta name="description" content={description} />
{/*facebook */}
<meta property="og:type" content="website" />
<meta property="og:url" content={url} />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={getImage(facebookImage)} />
{/*twitter */}
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={url} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={getImage(twitterImage)} />
</Head>
</>
);
};
export default Meta;

The code example is specific to Next.js, but you can easily adapt it to whatever framework you're using.

 

I use the Meta component it in my post component like this:

 
<Meta
title={post.title}
description={post.metaDescription || post.content}
image={`meta/posts/${post.slug}.png`}
/>

This will render the correct image based on the slug of the post.

 

Conclusion

I have seriously no idea how to conclude this post. Good luck with generating your social media images!

 

If you have any questions or ideas regarding this, we can discuss them during my stream 🙌

 

Thanks for reading! Subscribe to the newsletter?

More posts

How to use the src directory and absolute imports in Next.js

The saddest "Just Ship It" story ever

You're either a Zero or a One

How I tricked my brain into working out 24 times in 30 days

GitHub stars won't pay your rent

An underrated way to keep up with web development

GPT-3 is the beginning of the end

How to magically free up gigabytes of disk space

Fastest way to thoroughly uninstall apps on macOS

A better way to manage multiple monitors on macOS

From Hello World to a production app: The magic of Blitz

The best Stream Deck alternative: Make your own!

Introducing Fungarzione: The app for keeping your users in the loop