Fun CMS feature: Auto-generated OG images for social shares with Sanity.io
Our agency, y’all, created a sort of tear-off calendar for the web — a simple little site packed with tons of fun features like live emoji reactions and a globe-trotting circular navigation to scan through days.
This is Make a Daily Difference — give it a try!
We used Sanity CMS as the site’s backend and were even able to create the live emoji feature using Sanity’s built-in “listen” function, so you see other users’ emoji reactions appear in real time. Very cool!
See more details about the site at our Awwwards Honorable Mention page and be sure to try out the site itself!
Sharing your day
We really wanted users to be able to share the day that they care about on social media, like this:
Seems easy, right? We’ll just get our design team to make 365 unique images… wait, this doesn’t sound so good after all. Turns out, designers have better things to do! 😅
Automation to the rescue
One of our devs came up with the idea of auto-generating these O.G. (open graph) images, and suggested that we might be able to do this on the CMS side so content editors would get to see a preview of the share image, or download it for direct use.
We found a well-written Sanity OG Images community plugin that lets you add a background image and text then download manually, but for our posts we already have our titles and emoji icons and just want to create and save them automatically.
Well, I ain’t gonna lie —it wasn’t trivial! But we were able to pull it off, thanks to html-to-image and Sanity’s ability to customize everything from the component shown in the edit view to CMS workflow actions like Publish.
Let’s take a look inside the CMS. Here’s a typical post:
Let’s say this were a brand new post. Scrolling down a little, you can see the OG images updating as the title is entered:
And now in the next screenshot the image has been published:
For Instagram, we created a schema option for “downloadOnly”, since they want you to upload original files:
With the system in place adding new templates is really easy, so we have 6 extra download-only images for Instagram posts and stories.
(Bonus: we also ported the main website’s awesome color-theme randomizer to make it possible to download every image in a different theme, if you want, or keep them as matchies!)
Bulk Publishing
Each future new year, our editors will pile up 365 new draft documents that we’ll want to release at once. Unfortunately Sanity doesn’t support bulk publish at this time.
Luckily for us we’d built this project in the older Sanity V2 studio, so we were able to use the community plugin Super Pane to do it. We modded it to publish OG images during bulk publish, and added a new a-la-carte bulk Generate OG Images action — handy if the image designs are changed.
This isn’t instantaneous for a batch of 365 — even though we run 10 at a time it still takes around an hour on a fast machine. This is because it’s done in the browser, not on the backend, and needs to do 2-4 Sanity API calls in the process: first unlink and then delete the old image if there was one, upload the new image, and finally link it to the post.
If we upgrade to Sanity V3 we’ll either need to upgrade or replace Super Pane. We’ve already figured out how to run bulk publishes using a more efficient RxJS pipeline, so possibly more to come…
Establishing Permalinks
Sanity has its own global CDN, which is great to keep our large and ever-growing heap of OG images fast. Best of all we don’t need to manage it ourselves, they make it easy.
But there’s a catch. When an image is regenerated (e.g. after an entry is updated), each refreshed image ends up at a different CDN URL after it’s uploaded. This is bad for shares to external apps and sites which often just copy the image’s URL. The fact that we delete our stale images (to avoid amassing terabytes of cloudjunk!) risks breaking tons of shares in the wild any time a change is made.
What we need is a permalink for each share image that stays stable.
There are a few ways to solve this. Since our frontend is built in Gatsby and hosted on Netlify which has its own fast global CDN, it’s possible to source the images into the app at build time, saving each one as a predictably-named static file. We tried this approach but it resulted in a large spike in build-time data transfer from Sanity that wouldn’t scale well:
The better solution is to use Gatsby’s createRedirect automation to add an http 200 rewrite for each image. This masks the image’s current Sanity CDN URL to the stable URL for each image.
As per our normal process, we rebuild the frontend after any new changes are published at the CMS, and the build now updates these pointers so they stay fresh.
Where We Landed
Unfortunately we’re not quite ready to share the code for this project yet, but would love to hear from you in the comments if this is something your company would be keen to use!
Before wrapping it up, here’s a rundown of the parts of this puzzle.
Our Sanity Studio project has a folder called og-image-generator that comprises the core functionality that could potentially be packaged up as a plugin in the future:
- the custom OgImageInput component shown in the screenshots above
- a folder of customized CMS actions (publish, unpublish, etc.) that manage OG image creation and deletion
- a set of utility scripts generateOgImage, saveOgImage, deleteOgImage, getCancelableOgDocsPublish, publishAllOGImagesForDoc
The studio also contains our modded copy of Super Pane with the custom bulk publish actions and a filter to show only Draft documents. Here’s what a schema field looks like:
As consumers of this system,
- our Day schema contains entries like the one above that declare each image’s dimensions, format, and template getter
- another folder with all of our project-specific templates including style and image files.
Templates are written as async functions so they can query Sanity for any linked images they need, then return a flat piece of JSX that can be captured immediately. Here’s a collapsed view of our main OG image template querying for its emoji icon before returning a div:
Again this code isn’t quite plugin-ready, but let us know here or in the Sanity Slack if this would be useful for you!