November 4, 2019

Creating dynamically-generated images for Twitter and Facebook shares

If people are tweeting about your website or posting it to Facebook, you want those posts to look great so people will click them.

But what if your website is full of user-generated pages? You don't want every one of those pages to look the exact same on social media, but you also don't have time to custom design a new image every time someone posts.

Enter dynamically-generated social images:

tweet to an Indie Hackers forum post

This forum is full of many tens of thousands of posts created by all sorts of people, and each one has its own unique image when posted to Twitter or Facebook. In this guide, I'll show you how I set it up.

Overview

Here's an overview of the basic idea:

  1. For each type of dynamically-generated page on your site (e.g. forum or blog posts), create a companion page with a design you want to use for its image. If you want to get fancy, you can create a few different designs.
  2. Create a script that can take a screenshot of this companion page and store it online somewhere, e.g. in Amazon S3 or Google Cloud Storage.
  3. Trigger this script to run whenever someone adds a new content to your site. Optionally, re-trigger this when the content is updated.
  4. Ensure the HTML tags for your Twitter card image and Facebook Open Graph image point to this image.

1. Dynamically generating companion pages

The Indie Hackers forum is full of posts like this one. For each post, I also have a simple HTML companion page with a unique design. For example, here's a post and here's its companion page open in separate browser tabs:

post vs companion page

Make sure the dimensions of your companion page match whatever Twitter and Facebook recommend for their social images, since you'll be screenshotting this page to turn it into an image.

You also want the URL to include a pointer to the dynamically-generated post, for example by including the post's ID. If the original content is located at /post/123, your companion page might be something like /post/123/social-image. Then your code can retrieve the appropriate post from your database and include its data in the design of the companion page.

I recommend adding some unique information about the user and the content they posted, e.g. their username, their avatar, the title of their post/blog, etc. You can get as fancy as you want.

You can even create several variations of the design. I currently have four variations.

2. Screenshotting your companion pages

Once you've got this page working, you need a script that can take a screenshot. In the past I used AWS to host this script as a Lambda function, but nowadays I'm using a Google Cloud function.

My script is written in JavaScript using Node. It makes use of the excellent puppeteer library to take the screenshot, and it saves that screenshot to a bucket in Google Cloud Storage. Here's what it looks like:

const gcs = require('[@google](/google)-cloud/storage')();
const puppeteer = require('puppeteer');

const DIMENSIONS = { height: 420, width: 840 };

function saveImage(urlToScreenshot, gcsPathToStoreScreenshot) {
    // set up browser
    const browser = await puppeteer.launch({ args: ['--no-sandbox'] });
    const page = await browser.newPage();
    await page.setViewport(Object.assign({ deviceScaleFactor: 1 }, DIMENSIONS));

    // visit page
    await page.goto(urlToScreenshot);

    // take screenshot
    const options = { encoding: 'binary', type: 'png' };
    const imageBuffer = await page.screenshot(options);

    // save screenshot to file
    const bucket = gcs.bucket(config.storage.storageBucket);
    const file = bucket.file(gcsPathToStoreScreenshot);
    await file.save(imageBuffer);
    await file.setMetadata({ contentType: 'image/png' });

    await browser.close();
}

3. Triggering the screenshot

Depending on your setup, you'll have your own way for triggering your screenshot script. For example, perhaps it's triggered by an AJAX call you make on your website whenever a user makes a new post.

Since I'm using Firebase and Google Cloud functions, my script is set to trigger automatically whenever certain nodes in my database change (e.g. when a new post is created or an existing post is updated in a certain way).

4. Adding HTML tags

Finally, the HTML tags for your dynamically-generated pages need to point to the screenshots you've taken and stored online.

My tags look something like this:

<html>
 <head>
    …
    <meta name="twitter:image" content="https://storage.googleapis.com/indie-hackers.appspot.com/shareable-images/posts/219979eea8">
    <meta property="og:image" content="https://storage.googleapis.com/indie-hackers.appspot.com/shareable-images/posts/219979eea8">
    <meta property="og:image:height" content="840">
    <meta property="og:image:width" content="1680">
</head>
<body>
    …
</body>
</html>

Ensuring it all works

Both Facebook and Twitter have tools for ensuring that your images work.

Check out Facebook's Open Graph debugger and Twitter's card validator.

In each tool, you can simply plug in the URL to some dynamic content, and you'll be able to see what the page looks like when shared on Facebook or Twitter.

Happy coding!

  1. 4

    The images always look good whenever I share them.

    @yongfook has made https://www.previewmojo.com/ might be useful.

    1. 1

      I was also introduced to og tags and this idea by @yongfook's previewmojo product a few days ago!

      1. 1

        thank you both, I appreciate the mention!

  2. 4

    We rolled this out on stackingthebricks.com article pages using an awesome service from a fellow Indie Hacker! https://www.dynamicimg.io/

  3. 3

    Wrote up all the comments on the different services people are using for this.

    https://medium.com/@joshdance/how-to-creating-dynamically-generated-images-for-twitter-and-facebook-shares-7afa403432e7?sk=ea6dc471ab19a7f96f0f48debe7ce0a5

    Paid Dynamic Share Image Services

    Open Source Dynamic Share Image Library

    Hand Code your own dynamic share images

  4. 3

    Love these social shares. I actually have an open source library for doing just this: https://github.com/chrisvxd/puppeteer-social-image

    Planning to launch it as an API via saasify.sh soon.

  5. 3

    Of relevance - I also just stumbled across MyLinkPreview from @Retrodev

    1. 1

      Thank you for the mention, Rosie.

  6. 3

    Thanks for sharing this. :)

    My search many months back lead me to https://placid.app which does this.

  7. 2

    Great info thank you!

    1. 1

      Yes. It was great to read & learn.

      I had once made a crude one using a screen capture service thum.io. This version is more robust.

  8. 2

    Great post and glad to see there's finally a developers tag group !

    1. 4

      There will hopefully be many more groups in the coming months. Suggestions always welcome, especially if there's a group you'd like to run/moderate.

  9. 2

    That's funny! I wrote basically the exact same code a few weeks ago. Although, mine is generated on-demand and cached after the first request. I also used Google Cloud Functions and Google Cloud Storage.

  10. 2

    Is there a reason a matching static image is generated for each post-page URL, rather than dynamically generate it when Twitter, etc., come calling? Twitter stashes away a copy of the image, but perhaps there's a worry others won't causing too much work for the back-end? I'm probably over scrappy in thinking about hosting costs for many images that might never be used. :-)

    1. 2

      I know a bit about this since I built a SaaS around it [1]

      The reason is because there is naturally a delay while the image gets generated. It might only be a couple of seconds. But if the image isn't ready to be served when Twitter, Facebook etc parses your meta tags, you risk those platforms mis-interpreting your image as missing. They might retry at some point, but from a publishers perspective it's better to have the images ready for the social platform to parse and cache rather than rely on the social platform's cache invalidation schedule (which you have no control over).

      1. previewmojo.com
  11. 1

    Someone turn this into a SaaS please :)

    1. 4

      There is.

      placid.app & @yongfook 's previewmojo.com

      1. 1

        I appreciate the mention! the dynamic preview images on IH were actually part of the inspiration for the product. dev.to also does this.

        1. 1

          Love the way you are building things. All the best. :)

          Many months back I was testing waters about this with bloggers for such a service, Pinterest mainly. It was the wrong market. I learned bloggers are very thrifty.

    2. 1

      We'll be launching a saasified version of this soon.. stay tuned :)

  12. 1

    This is very great. Thanks. Have been thinking about this in the background for months. ✌️✌️✌️

  13. 1

    Great post Courtland, thanks for explaining how you've implemented this! I would never have thought of using puppeteer to screenshot rendered HTML for generating the images - I'd have used something like ImageMagick but this makes total sense! P.S. the IH preview images look great!

  14. 1

    This is awesome. I’m currently manually creating unique social share images for Inbox Stash and was about to start looking for an automated solution. Coz it’s a pain in the ass 🤦‍♂️

  15. 1

    Wow, sounds good, need to try.

    Also sounds like a POC for the service)