Building social cards using React, Netlify functions

All Posts

Building social cards using React, Netlify functions

Updated: August 16, 2020 by Tony Alves

We'll create a simple page for our social card component in React and capture that image using playwright using a Netlify function to go to the page.

Let's get into this!

Steps we'll take

  • Use/install create react app
  • Commit our new react app to Netlify and push our new site.
  • Deploy to Netlify (using the Netlify CLI)
  • Create a netlify.toml for the site
  • Create a simple page with a simple card layout
  • Create our function to capture our card using playwright

Create React app

You'll want to run create-react-app

npx create-react-app social-card-image

Create your new repository on GitHub and push your new site to GitHub. We're not going to go through all the steps here, but want to start with an empty react app for the next step to deploy to Netlify.

Deploy to Netlify

We'll use the command line here for now. The finished repo will have a deploy to Netlify button on the README for easy deploy and can be found here

Install the netlify-cli globally using npm. Find docs here

npm install netlify-cli -g

Or if you don't want to use the netlify-cli globally, you can just make it a command in your local project by installing into your local package as a dev dependency.

yarn add netlify-cli --dev

You can check whether you are already logged into Netlify on your machine. If you are logged into Netlify your current logged in user.

yarn netlify status

The cli will store your login credential token under .netlify/config.json for later commands.

netlify login

We'll now configure our repository to create our new site.

netlify init

Choose to create a new site, since we haven't actually created a site on Netlify for this repository yet.

Choose your team to put the site if prompted.

We'll call our site name a unique name my-netlify-site or something we like. We can default to allow a unique name picked for us. We'll do that here.

Choose your method to login to GitHub (since this Tutorial assumes you'll be using GitHub).

Your build command: yarn build Directory to deploy: public

Netlify will automagically spin up a build environment and start the build process of your new create react site. We could go in and configure the options for the site, but we're going to use a netlify.toml file to configure our build. This way we have control of Netlify in one place for our build setup.

Create netlify.toml

Here is the netlify.toml for our current site to run our build and deploy on the Netlify CDN/ADN.

netlify.toml

[build]
command="yarn build"
publish="build"

Create our social card

We'll replace our App to use a component for our card. Then we'll put some styles on the card.

social-card.js

import React from "react";
export default ({
children,
id = 'social-card';
title = "Missing Title",
width = "1024px",
height = "542px",
}) => {
const styles = {
width: width,
height: height,
};
return (
<div id={id} className="SocialCard" style={styles}>
<h1>{title}</h1>
{children}
</div>
);
};

Our base styles for the social card:

social-card.css

.SocialCard {
background: rgb(2,0,36);
background: linear-gradient(160deg, rgba(2,0,36,1) 0%, rgba(11,51,255,1) 90%);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}

Make the changes to the src/App.js to show our card.

First we'll install a utility to get our parameter strings from the uri (query-string) that we'll have the option of changing to make our cards with dynamic data.

yarn add query-string

src/App.js

import React from "react";
import queryString from "query-string";
import SocialCard from "./social-card";
import "./social-card.css";
function App() {
const location = typeof window !== undefined ? window.location : {};
const data = queryString.parse(location.search);
return (
<div className="App">
<SocialCard {...data}>
<pre>{JSON.stringify(data, null, 2)}</pre>
</SocialCard>
</div>
);
}
export default App;

There really isn't any magic here. We are just creating a rendered DOM element to be able to capture using playwright from our netlify function, which we'll show next. This page could have been anything that would allow us to get query string parameters from the uri that would be passed from our netlify function.

We can run our react app using yarn start to view our card at http://localhost:3000?title=Our%20First%20Card.

Create our function to capture our card using playwright

Create a directory at /functions in our project.

Tell Netlify where our functions will be located:

netlify.toml

[build]
command="yarn build"
publish="build"
functions="functions"

Install playwright for our function

We'll need playwright-core and playwright-aws-lambda for running playwright on Netlify functions, since the functions are really just aws-lambda deployed cloud functions. This function could be deployed through Amazon AWS if we didn't want to use Netlify, but Netlify is a very simple process and we want our functions to just work from our domain.

yarn add playwright-core playwright-aws-lambda --dev

Now we are ready to create our function in the functions directory in our project. We'll call it create-card at this time, but it could be called anything we'd like.

We'll create a function buildUri in our lambda function to create a uri.path string for our page using parameters passed in the queryStringParameters.

We'll also create a function getBoundingSize function to be used by the playwright page to evaluate the bounding size of the DOM object id we passed to the lambda function. In most cases this would be a default id, but could be dynamic. During the time we create the uri, we call the page to create the size, so we want to evaluate the size and evaluate the starting location of the upper right corner for the image (screenshot) capture. In our case it is at [0,0], but could easily be placed dynamically on the page if we wanted.

functions/create-card.js

const playwright = require("playwright-aws-lambda");
const buildUri = ({ queryStringParameters = {} }) => {
const {
cardpath = "https://social-card-image.netlify.app",
id = "social-card",
title = "No Title, Yet!",
width,
height,
} = queryStringParameters;
const dimensions = width && height ? `&width=${width}&height=${height}` : "";
return {
id,
path: `${cardpath}?id=${id}&title=${title}${dimensions}`,
};
};
const getBoundingSize = (uri) => {
const cardBox = document.getElementById(uri.id);
if (typeof cardBox === "undefined" || cardBox === null)
return { x: 0, y: 0, width: 1920, height: 1080 };
const { x, y, width, height } = cardBox.getBoundingClientRect();
return { x, y, width, height };
};
exports.handler = async function (event) {
try {
const uri = buildUri(event);
console.log("uri", uri.path);
const browser = await playwright.launchChromium();
const context = await browser.newContext();
const page = await context.newPage();
await page.setViewportSize({
width: 1920,
height: 1080,
});
await page.goto(uri.path);
await new Promise((resolve) => setTimeout(resolve, "100"));
const boundingRect = await page.evaluate(getBoundingSize, uri);
const screenshotBuffer = await page.screenshot({ clip: boundingRect });
await browser.close();
const base64Image = screenshotBuffer.toString("base64");
return {
isBase64Encoded: true,
statusCode: 200,
headers: {
"Content-Type": "image/png",
"Content-Length": `${screenshotBuffer.length}`,
},
body: base64Image,
};
} catch (error) {
console.log(error);
return { statusCode: error.statusCode || 500, body: error.message };
}
};

We can see from the return result of our function call above that we are returning "Content-Type": "image/png". This image could be fetched from a script or into a browser and saved locally.

Adding ability for emojis [optional]

A case came up where there was a need for emojis in the title. Good for us that playwright-aws-lambda allows for loading fonts using the async method loadFont(url). We can add our font loading at the beginning of a cold start of our function invocation.

/* In the case you need emojis in your titles (use this code) */
async function loadFonts() {
try {
await playwright.loadFont(
"https://rawcdn.githack.com/googlefonts/noto-emoji/41708e907f229968abcdc8ec4c59832e109ff1e8/fonts/NotoColorEmoji.ttf"
);
await playwright.loadFont(
"https://rawcdn.githack.com/googlefonts/noto-fonts/31de21ec51b4b54309bd48b9e4b3693fdfe47bcc/alpha/NotoSansHistoric-Regular.ttf"
);
console.log("FontsLoaded");
} catch (error) {
console.log(error);
}
}
loadFonts();
exports.handler = async function(event) {

This could also allow us to load missing fonts instead of using the default system fonts.

Done!

To view our image we go to our function path passing in our parameters:

https://social-card-image.netlify.app/.netlify/functions/create-card?title=Social%20Card%20Test&width=1024px&height=542px

Note: The path above will only work for awhile. Don't want everyone using up my quota for lambda calls on my plan.

We now have a way to create a .png image of our new social card with a dynamic title and size {width, height}. This is only a base for what other options we could pass through the query string parameters. Also, this is a very simple card and design. Be sure to tweet at me when you get your design complete. I'd love to see what you've built.

Don't want to code it all yourself?

You can find the sourcecode on GitHub

It will fail until you change the `imagepath` in the `functions/create-card.js` file or pass the imagepath in your query string parameter to your site's card location.
© Tony Alves