⚡️ Generate Link Preview Cover with Nodejs
Tags: javascript, node, webdev, tutorial
Published: 2020-12-22 02:05:08

---
title: ⚡️ Generate Link Preview Cover with Nodejs
published: true
description: Easily Generate Social Link Preview with Nodejs
tags: #javascript #node #webdev, #tutorial
//cover_image: https://direct_url_to_image.jpg
---
Last  couple years I see very nice generated covers for social link preview, also our lovely [dev.to](http://dev.to) does it and I've tried to code something like this.

Note. I was planning to do it in Nextjs, but to avoid too much instruction I've decided to separate them. Article about Nextjs is on the way too.
<br />
**Result we have to reach is this 🔥**

![result we have to reach](https://dev-to-uploads.s3.amazonaws.com/i/ynpt8p41wx1wr5grrq9b.jpeg)

## **Step 1**

Let's draw some canvas on server side!
We're gonna use [this package](https://www.npmjs.com/package/canvas) to draw **canvas** in nodejs.
<br />

Create **lib** folder. 

Define our cover design theme and call it *theme.js* and put it into lib directory.

```jsx
// use fonts in your machine
const fontFamily = "Lucida Sans Unicode";

const fontSizes = {
  heading: 80,
  author: 40,
  authorTitle: 26
};

module.exports = {
  fontStyles: {
    heading: `900 ${fontSizes.heading}px  ${fontFamily}`,
    author: `700 ${fontSizes.author}px ${fontFamily}`,
    authorTitle: `500 ${fontSizes.authorTitle}px ${fontFamily}`
  },
  fontSizes,
  colors: {
    primary: "#ffd166",
    secondary: "white",
    base: "#560bad"
  },
  avatarSize: 80,
  avatarBorder: 5,
  logoW: 100,
  logoH: 80,
  space: 40
};
```
<br />

## **Step 2**

Install canvas package:
`npm i --save-dev canvas`

or

`yarn add canvas`

Create *index.js* file in lib folder that will create **canvas** and **getContext** to work with.

```jsx
const { createCanvas, loadImage } = require("canvas");
const { wrapText } = require("./helpers");
const theme = require("./theme");

// Create canvas and get its context
const canvas = createCanvas(1200, 630);
const ctx = canvas.getContext("2d");

const {
  colors,
  avatarSize,
  avatarBorder,
  fontSizes,
  fontStyles,
  logoW,
  logoH,
  space
} = theme;
```

## **Step 3**

Add main part in *index.js* above. Please read some hints about [canvas](https://devhints.io/canvas) while reading the code below.

```jsx
module.exports = async function generateCover({
  title,
  avatarUrl,
  name,
  position
}) {
  // Load images
  const logo = await loadImage(
    "https://d2fltix0v2e0sb.cloudfront.net/dev-black.png"
  );
  const avatar = await loadImage(avatarUrl);

  // Background
  ctx.fillStyle = colors.base;
  ctx.fillRect(0, 0, canvas.width, canvas.height);

  // Heading text
  ctx.fillStyle = colors.secondary;
  ctx.font = fontStyles.heading;
  wrapText(
    ctx,
    title,
    space,
    fontSizes.heading + space,
    canvas.width - space * 2,
    fontSizes.heading
  );

  // Avatar
  const avatarTop = canvas.height - avatarSize - avatarSize / 2;
  const avatarLeft = space;

  // Border around avatar
  ctx.fillStyle = colors.primary;
  ctx.beginPath();
  ctx.arc(
    avatarSize / 2 + avatarLeft,
    avatarSize / 2 + avatarTop,
    avatarSize / 2 + avatarBorder,
    0,
    2 * Math.PI
  );
  ctx.fill();
  ctx.closePath();

  // Clip image before draw
  ctx.save();
  ctx.beginPath();
  ctx.arc(
    avatarSize / 2 + avatarLeft,
    avatarSize / 2 + avatarTop,
    avatarSize / 2,
    0,
    2 * Math.PI
  );
  ctx.closePath();
  ctx.clip();

  // Put avatar
  ctx.drawImage(avatar, avatarLeft, avatarTop, avatarSize, avatarSize);

  // Unclip all around avatar
  ctx.beginPath();
  ctx.arc(0, 0, avatarSize / 2, 0, Math.PI * 2, true);
  ctx.clip();
  ctx.closePath();
  ctx.restore();

  // Author name
  ctx.fillStyle = colors.secondary;
  ctx.font = fontStyles.author;
  ctx.fillText(
    name,
    avatarLeft + avatarSize + space / 2,
    avatarTop + fontSizes.author - 4
  );

  // Author title
  ctx.fillStyle = colors.primary;
  ctx.font = fontStyles.authorTitle;
  ctx.fillText(
    position,
    avatarLeft + avatarSize + space / 2,
    avatarTop + fontSizes.author + fontSizes.authorTitle
  );

  // Add logo
  ctx.drawImage(
    logo,
    canvas.width - logoH - 60,
    canvas.height - logoH - logoH / 2 + space / 4,
    logoW,
    logoH
  );

  // Return PNG Stream
  // you can pass pngConfig here
  return canvas.createPNGStream();
};
```


Test it with simple express framework.

```jsx
const app = require("express")();
const generateCover = require("./lib");

app.get("/generate-cover", async (req, res) => {
  try {
    const coverStream = await generateCover({
      title: "Generate Link Preview Cover with Nodejs",
      avatarUrl:
        "https://res.cloudinary.com/practicaldev/image/fetch/s--4rczDrsA--/c_fill,f_auto,fl_progressive,h_320,q_auto,w_320/https://dev-to-uploads.s3.amazonaws.com/uploads/user/profile_image/152066/eb216eb5-1b78-42fd-8faf-2d5bc69f075c.jpg",
      name: "Orkhan Jafarov",
      position: "Senior Frontend Developer"
    });

    res.statusCode = 200;
    res.setHeader("Content-Type", "image/png");
    res.setHeader("Content-Control", "public, max-age=31536000");

    coverStream.pipe(res);
  } catch (error) {
    res.statusCode = 500;
    res.end(error.message);
  }
});

app.listen(3000);
```

Open it in browser

![cover in browser](https://dev-to-uploads.s3.amazonaws.com/i/zljeuwdubzddt4y9axgr.jpeg)

**It's working 🔥**
<br />

## **Last step**

Add social meta tags into your html **<head>**

```html
<meta property="og:title" content="your_title" />
<meta
 property="og:image"
 content="http://example.com/generate-cover/uniq-id-1"
/>

<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content="your_title" />
<meta
 property="twitter:image"
 content="http://example.com/generate-cover/uniq-id-1"
/>
```

*Use absolute url to your image source*

## **Done!**

Try it yourself on [codesandbox](https://codesandbox.io/s/generate-link-preview-cover-with-nodejs-0r8qz?file=/src/index.js:1198-1493)!

Check link preview `https://0r8qz.sse.codesandbox.io/` [here](https://cards-dev.twitter.com/validator) and [here](https://metatags.io)

![twitter preview](https://dev-to-uploads.s3.amazonaws.com/i/c159t2p8djdolcxbzhps.jpeg)

Of course, that's only starter tutorial. There's no saving cover images and return if it's already generated. But that's up to you. For simple project it's enough, you can optimize it if you'll use less colors and it will be VERY fast.

Thank you! ✨
<br /><br />

### Idea to implement
>You can pass your unique key and use a data from your DB. For example.

```jsx
GET /generate-cover/uniq-id-1

app.get('/generate-cover/:postId', (req, res) => {
	const { postId } = req.params;
	const {
		title,
		author: {
			avatarUrl,
			name,
			position
		}
	} = await db.posts.findOne({ id: postId });

	const coverStream = await generateCover({
		title,
		avatarUrl,
		name,
		position
	});

	coverStream.pipe(res);
});
```