Social media image previews with Deno Fresh

What are social media image previews?

When you share a link on Twitter, Facebook, LinkedIn or Discord, you can see an image with a preview of the page you're linking to. This preview is called a social media image preview.

Example in Discord:

Social media image preview example

Deno Fresh Previews

Generating image previews in Deno Fresh is easier than it seems.

Make a new handler in routes/_middleware.js

All requests will go through this middleware handler. The idea is that we can take ANY page's URL and add .preview.jpeg to the end of it, and it will return a preview image for that page.

Of course, we don't want to generate previews for pages that don't exist, or for errors or static files like images or CSS etc.

You're welcome to copy/paste this code. Credit is appreciated but not required.

// _middleware.js
export async function handler(req, ctx) {
  const url = new URL(req.url);

  // If URL ends in .preview.jpg, return a preview image
  if (url.pathname.endsWith(".preview.jpeg")) {
    // Let the request go through as if the .preview.jpeg wasn't there at the end
    // We can't just do req.url = ... because it's read only
    Object.defineProperty(req, "url", { get: () => url.href.slice(0, -13) });
    const res = await ctx.next();

    // For non-200, return the response as-is
    // This is because we don't want to generate a preview image for a non-content page
    if (res.status !== 200) return res;

    // If the content type isn't HTML, return a 404
    // This is also because we don't want to generate a preview image for a non-content page
    if (!res.headers.get("Content-Type").startsWith("text/html")) {
      // At time of writing, ctx.renderNotFound() isn't actually a function
      // I'm leaving it in because it probably will be
      return ctx.renderNotFound();
    }

    // Get HTML body and extract title
    const body = await res.text();
    const title = body.match(/< title>(.*?)<\/title>/)[1];

    // Now use the title to generate a preview image
    // You could also use other metadata from the page, like the meta description
    return await generatePreviewImageResponse(title);
  }

  return await ctx.next();
}

Generating the preview images

This is simple using Imagescript.

import { Image, TextLayout } from "https://deno.land/x/imagescript@1.2.15/mod.ts";

// Pre-load as much of the preview image as possible

// Download a font from Google Fonts or somewhere else
const font = await Deno.readFile("./content/fonts/MyFont.ttf");
const textLayout = new TextLayout({ maxWidth: 1050 });

// 1200x600 is the recommended size for preview images
const templateImage = new Image(1200, 600);

// Set up the background etc just once to avoid doing it for every request
// Use async operations because Deno Deploy doesn't the support sync variants
templateImage.composite(
  await Image.decode(await Deno.readFile("./static/images/myPreviewBackgroundHere.jpeg")),
);

export async function generatePreviewImageResponse(title) {
  // Clone the template image so we don't have to set up the background etc for every request
  const image = templateImage.clone();

  // Now add whatever you like to the image
  const colourCode = 123456789; // This is a integer colour code

  image.composite(
    Image.renderText(font, 96, title, colourCode, textLayout),
    100,
    150,
  );

  const jpegBytes = await image.encodeJPEG(90);

  // Middleware must return a Response object
  // Response can be raw image bytes
  return new Response(jpegBytes, {
    headers: {
      "Content-Type": "image/jpeg",
    },
  });
}

What about image caching?

Send the Cache-Control header to set the cache time. For example, Cache-Control: max-age=3600 will cache the image for 1 hour.

If the browser has the image cached, it will send an If-None-Match or If-Modified-Since header. If the image hasn't changed, the server should respond with a 304 Not Modified status code.

If your pages have timestamps in the HTML, you can extract those like we extract the title.

More

Building safe atomic transactions with Deno KV
The Deno Team are building a unique product. It's an all-javascript development experience with _very_ simple deployments: Deno KV is a…
Find the closest sum to s
This is the solution for a difficult problem in ECS529U Algorithms and Data Structures (Queen Mary University of London), Lab 10. Try to…
Social media image previews with Deno Fresh
When you share a link on Twitter, Facebook, LinkedIn or Discord, you can see an image with a preview of the page you're linking to. This…
See more articles
Email