Dynamic Social Media Share Images

To improve the experience when sharing an AtomicQuote URL on Twitter or Facebook, I needed to dynamically generate share images that include the quote and author with multiple background images. To do this, I could have generated millions of photos. Based on the number of quotes that AtomicQuote currently has, this would result in 17,673,360 images. Each image is around 60kb. So to store all the images, it would take over 10 TB of storage. To avoid paying for this storage, I planned to generate them on the fly and cache the result for a few days. So far, this has worked well and has improved the sharing of quotes on social media. See the example below:

I decided to use a Puppeteer Social Image package to accomplish dynamic images. This package is specifically designed for generating social media share images.

import renderSocialImage from "puppeteer-social-image";

renderSocialImage({
  template: "basic",
  templateParams: {
  imageUrl:"https://images.unsplash.com/photo-1557958114-3d2440207108?w=1950&q=80",
  title: "Hello, world"
},
 output: "image.png",
 size: "facebook"
});

The package also allows you to pass in your own browser instance. I recommend using the minimum args to improve performance. Here is a list of helpful Chrome args with their explanation.

const renderSocialImage = require('puppeteer-social-image').default; 
const puppeteer = require('puppeteer'); 
var express = require('express'); 
var app = express();
let browser = null;

var getBrowser = async function () {
const minimal_args = [
  '--autoplay-policy=user-gesture-required',
  '--disable-background-networking',
  '--disable-background-timer-throttling',
  '--disable-backgrounding-occluded-windows',
  '--disable-breakpad',
  '--disable-client-side-phishing-detection',
  '--disable-component-update',
  '--disable-default-apps',
  '--disable-dev-shm-usage',
  '--disable-domain-reliability',
  '--disable-extensions',
  '--disable-features=AudioServiceOutOfProcess',
  '--disable-hang-monitor',
  '--disable-ipc-flooding-protection',
  '--disable-notifications',
  '--disable-offer-store-unmasked-wallet-cards',
  '--disable-popup-blocking',
  '--disable-print-preview',
  '--disable-prompt-on-repost',
  '--disable-renderer-backgrounding',
  '--disable-setuid-sandbox',
  '--disable-speech-api',
  '--disable-sync',
  '--hide-scrollbars',
  '--ignore-gpu-blacklist',
  '--metrics-recording-only',
  '--mute-audio',
  '--no-default-browser-check',
  '--no-first-run',
  '--no-pings',
  '--no-sandbox',
  '--no-zygote',
  '--password-store=basic',
  '--use-gl=swiftshader',
  '--use-mock-keychain',
];
const options = {
  args: minimal_args,
  headless: true,
};

return await puppeteer.launch(options);
};

app.get('/', async (req, res) => {
if (!browser) {
  browser = await getBrowser();
}

const body = await renderSocialImage({
  template: 'basic',
  templateParams: {
    imageUrl: imageUrl,
    title: quote,
    watermark: author,
  },
  jpegQuality: 50,
  size: type,
  browser: browser,
});

res.set('Content-Type', 'image/jpeg');
  res.send(body);
});

app.listen(3000, function () {
  console.log('Listening on http://localhost:3000/');
});

Feel free to reach out if you have any questions. I am curious how you would have accomplished this and am open to any feedback!


Thanks for reading. Make sure you follow me on Twitter to stay up to date on the progress of my side projects T.LYWeather Extension, and Link Shortener Extension. If you are interested in the tech I use daily, check out my uses page.  

Leave a Reply

Your email address will not be published. Required fields are marked *