Automated social images on Netlify - with Next.js and Resoc

Automated social images on Netlify - with Next.js and Resoc

Create awesome, automated social images with HTML & CSS and just a few lines of code

The social images illustrate a web page on social networks and messaging services. For example, when I share this previous article on Slack, I get:

Social image example

The green and pink image is attached to the shared page via an og:image meta markup.

The social images are a great opportunity to get attention. More often than not, this opportunity is wasted, because creating those image is quite boring. Unless they are automatically generated for us.

In this tutorial, we are going to:

When sharing your app with your colleagues, they will see:

Demo app on Slack

If you can't wait, try it now or browse the code 😁

Create the Next.js app

Let's create a new app and start it:

yarn create next-app
yarn dev

Before implementing the social images, we first fix a little something with meta management. In our fresh app, the title markup is hard coded in the homepage. This is okay in a simple example, but a real app will have this kind of recurrent markup moved to a single layout.

Replace the content of pages/_app.js with:

import '../styles/globals.css'
import Head from 'next/head'

function MyApp({ Component, pageProps }) {
  return (
    <>
      <Head>
        <title>{pageProps.title}</title>
        <meta name="description" content={pageProps.description} />
      </Head>
      <Component {...pageProps} />
    </>
  );
}

export default MyApp

Our app now expects two page props, title and description, which are injected in classic head markups.

We need to modify the homepage and pass these two variables. In pages/index.js, we define getStaticProps for this purpose. We also remove the demo content for clarity:

import styles from '../styles/Home.module.css'

export default function Home() {
  return (
    <div className={styles.container}>
      No need for content
    </div>
  )
}

export async function getStaticProps(context) {
  return {
    props: {
      title: 'Automated social images on Netlify',
      description: 'With Next.js and @resoc/netlify-plugin-social-image'
    }
  }
}

Visit the app again. From now on, title and description values come from the pages and _app prints the markups:

Title and description meta markups

Done with the setup!

Create the image template

How should our social images look like? This is what we are going to work on, with the Resoc image template development kit. Quit yarn dev for a moment and run:

npx itdk init resoc-templates/default -m title-description

This command creates a new image template in resoc-templates/default and opens a viewer in a browser:

Default template in the viewer

The model we used via -m title-description takes two parameters: a title and a description. Later, we will make Next.js inject the correct metadata in the template, and Netlify create the images for us. For now, we modify the template so it matches our app.

Our app is gonna be hot, so let's replace the logo in the template. Overwrite resoc-templates/default/logo.png with the image below:

New logo

In our browser, the viewer shows the update:

Template with new logo

Now, change the background color. In resoc-templates/default/styles.css.mustache, replace the background definition of .wrapper with:

background: rgb(247,58,12);
background: linear-gradient(90deg, rgba(247,58,12,1) 0%, rgba(247,99,12,1) 100%);

Our template is definitely hotter!

Final template

That's all for the template. We could have been much more creative, for example by imitating GitHub social images. This is a Resoc template:

GitHub social image with Resoc

Generate social images

The idea behind image generation is pretty simple: take the title and description from a page, take the image template, mix, et voilà! The two technologies at work are Mustache, a template system used to inject parameter's values, and Puppeteer, a headless Chrome system to convert HTML to images. However, we won't have to deal with these packages directly.

Each image needs a couple of seconds to be generated. With our demo app, that's not an issue. But in a real world example with hundreds of pages, this is clearly a concern. In particular, we don't want this process to take place at build time. Imagine your build time multiplied by 10... Cool social images don't worth such a wait. The Resoc social image Netlify plugin adresses the issue by using a Netlify Function to create our images on demand, not at build time. Problem solved.

Here is the process we are going to implement below:

  • In our app, each page has a slug. No big deal: index.js is homepage (a particular case, since index.js is linked to /), news.js is news, blog/huge-announcement.js is blog-huge-announcement, etc.
  • Pages register their image template parameters. In our app, each page saves its title and description for later use.
  • The app layout declares the social image using the page's slug, eg. /social-images/homepage.jpg.
  • A Netlify Function uses the image template parameters declared by the pages to actually create the social images, on demand.

Install the packages:

yarn add -D @resoc/netlify-plugin-social-image @resoc/core @resoc/img-data

We need to configure the plugin. At the root of the project, create netlify.toml:

[[plugins]]
package = "@resoc/netlify-plugin-social-image"
  [plugins.inputs]
  templates_dir = "resoc-templates"
  slug_to_image_data_mapping_file = "resoc-image-data.json"
  open_graph_base_path = "/social-images"

If you use Netlify you must be familiar with this syntax. But we need to explain those parameters.

templates_dir is the directory which contains our Resoc templates. Later, we will indicate that we want to use our template named default. This will be resolved as resoc-templates/default, which is where we created our sole template.

slug_to_image_data_mapping_file contains the data used to generate the social images.

open_graph_base_path is the base path we will use in our og:image markup.

We update pages/index.js:

import styles from '../styles/Home.module.css'
import { storeImageData } from '@resoc/img-data'

export default function Home() {
  return (
    <div className={styles.container}>
      No need for content, really
    </div>
  )
}

const prepareStaticProps = async (title, description, imgSlug) => {
  await storeImageData(
    'resoc-image-data.json',
    imgSlug, {
      template: 'default',
      values: { title, description }
    }
  );

  return {
    props: {
      title,
      description,
      imgSlug
    }
  };
}

export async function getStaticProps(context) {
  return await prepareStaticProps(
    "Automated social images for Next.js on Netlify",
    "With getStaticProps and @resoc/netlify-plugin-social-image",
    "homepage"
  );
}

We added a helper called prepareStaticProps. Here, we call a function named storeImageData. We pass it the title and description as template parameters, the page slug, and the template to use (we have only one and it is in the default sub directory). These data are saved to resoc-image-data.json, the file we assigned to slug_to_image_data_mapping_file in netlify.toml.

Because prepareStaticProps returns the page props, getStaticProps only needs to call it and return it as is.

This is good enough in our one page app. In a real app, prepareStaticProps should be reusable.

We must also update pages/_app.js:

import '../styles/globals.css'
import Head from 'next/head'
import { FacebookOpenGraph } from '@resoc/core'

function MyApp({ Component, pageProps }) {
  return (
    <>
      <Head>
        <title>{pageProps.title}</title>
        <meta name="description" content={pageProps.description} />

        <meta property="og:title" content={pageProps.title} />
        <meta property="og:description" content={pageProps.description} />
        <meta property="og:image" content={`/social-images/${pageProps.imgSlug}.jpg`} />
        <meta property="og:image:width" content={FacebookOpenGraph.width} />
        <meta property="og:image:height" content={FacebookOpenGraph.height} />
      </Head>
      <Component {...pageProps} />
    </>
  );
}

export default MyApp

We added the Open Graph markups.

og:title and og:description are just a repetition of existing markups, Open Graph style.

To build the URL in og:image, we use the plugin parameter open_graph_base_path declared in netlify.toml and the page slug.

For completeness, we also declare the image width and height using FacebookOpenGraph.width and FacebookOpenGraph.height.

We're done!

To make sure our app is in good shape, restart it:

yarn dev

Visit the homepage and inspect the head markup to make sure the Open Graph markups are correct.

Open Graph markups

We also notice a new file, resoc-image-data.json. If was created by getStaticProps.

Deploy to Netlify

yarn create next-app already initialized a Git repository for us.

Before we commit, let's add resoc-image-data.json to .gitignore to make sure it won't be committed. This file is generated at build time, which will be Netlify's responsibility.

Now, commit everything:

git commit -a -m "Social image!"

Log in to GitHub and create a new repository. To push your existing repository to GitHub, follow the instructions:

GitHub instructions

Now register to Netlify or login. Add a new site:

Add new site to Netlify

Use the Wizard to select your repository, leave the default values as they are and click "Deploy site". After a couple of minutes, our app is built and deployed.

Get its URL:

Netlify app URL

If we visit this URL, there is not much to see. After all, this is not where our social image shine. Instead, submit it to the Facebook debugger:

Facebook debugger

Hurray! The social image of our homepage was generated!

Conclusion

Automated social images have been around for a while. But creating them required a lot of custom code and efforts. Some solutions have been proposed, often at the expense of much longer builds.

Using the Resoc image template dev kit and the Resoc social image Netlify build plugin, we were able to setup automated social images quickly, with just the right amount of code. We even have spear time to perfect the template and make our images even more awesome!

I hope you enjoyed this article. If you followed this tutorial, I would be glad to get your feedback!