Building social cards using React, Netlify functions
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.