Skip to content

Automatic Social Share Images

Social media is amongst the main sources of website traffic this day. We are always engaging our users to share content as much as possible. Let us learn how we can ensure our websites look elegant when shared by dynamically generating relevant social sharing images.

The final project can be viewed on Codesandbox.

You can find the full source code on my Github repository.

Before learning how to generate social share images, we need to set up our blog. Let us create our NuxtJs project.

We will set up our project using the create-nuxt-app utility. Ensure you have yarn or npm v5.2+ or v6.1+ installed.


yarn create nuxt-app automatic-social-share-nuxtjs

# OR

npx crete-nuxt-app automatic-social-share-nuxtjs

# OR

npm init nuxt-app automatic-social-share-nuxtjs

Code language: PHP (php)

This will prompt a series of setup questions. Here are our recommended defaults:

Project name: automatic-social-share-nuxtjs

Programming language: JavaScript

Package manager: Yarn

UI framework: Tailwind CSS

Nuxt.js modules: Content

Linting tools: N/A

Testing frameworks: None

Rendering mode: Universal (SSR/SSG)

Deployment target: Server (Node.js hosting)

Development tools: N/A

What is your Github username? <your-github-username>

Version control system: Git

Once the setup is complete, you may now run the project. The project should now be available on http://localhost:3000


yarn dev

# OR

npm run dev

Code language: PHP (php)

We need some content to display on our dummy blog. We have some prepared for you. Let us copy this content into the content/articles folder.


mkdir content/articles

cd content/articles

wget https://raw.githubusercontent.com/musebe/Social-sharing-images-nuxt/main/content/articles/curabitur.md

wget https://raw.githubusercontent.com/musebe/Social-sharing-images-nuxt/main/content/articles/donec.md

wget https://raw.githubusercontent.com/musebe/Social-sharing-images-nuxt/main/content/articles/duis.md

wget https://raw.githubusercontent.com/musebe/Social-sharing-images-nuxt/main/content/articles/nam.md

Code language: JavaScript (javascript)

In each of the files, you will notice that the image is a simple string instead of a URL to a remote image. This is because we are hosting our images on Cloudinary. Cloudinary is a powerful image management platform we will be using. If you do not have an account, feel free to create one here.

Create a folder called automatic-social-share-images-nuxtjs and add the following files:

Additionally, create a folder called template and add this file: og-template.png.

The resulting folder should look like this: Cloudinary folder image

To be able to render the images from Cloudinary in our project, we need to set up the recommended Nuxt.Js plugin.


yarn add @nuxtjs/cloudinary

# OR

npm install @nuxtjs/cloudinary

Code language: CSS (css)

Configure the plugin by adding a cloudinary section to the nuxt.config.js file. This is the project’s configuration file.


// nuxt.config.js

export  default  {

...

cloudinary: {

cloudName:  process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,

useComponent:  true,

secure:  true

}

}

Code language: JavaScript (javascript)

The above file references the NUXT_ENV_CLOUDINARY_CLOUD_NAME environmental variables. These are values we set outside of our codebase either for security or deployment flexibility. You can find your Cloudinary cloud name on your dashboard. Let us create the .env file.


touch .env

Code language: CSS (css)

You may now set the variable:


NUXT_ENV_CLOUDINARY_CLOUD_NAME=<cloudinary-cloud-name>

Code language: HTML, XML (xml)

The above setup will ensure that the $cloudinary instance is injected globally.

We are going to create two components, one to render the entire article and one to render the previews of other articles. Let us start with our single article renderer. Let us create a custom component to render the title, image, author details and content.


touch components/ArticleRender.vue


<!-- components/ArticleRender.vue -->

<template>

<div>

...

<div>

<div>

<h1>

<span>{{ article.title }}</span>

</h1>

<img :src="$cloudinary.image.url(article.image)"  />

</div>

<div>

<div>

<div>

<div>

<img

:src="$cloudinary.image.url(article.author_avatar)"

alt="article.author"

/>

</div>

<div>

<p>

{{ article.author }}

</p>

</div>

</div>

</div>

<nuxt-content :document="article"  />

</div>

</div>

</div>

</template>

Code language: HTML, XML (xml)

// components/ArticleRender.vue

<script>

export default {

props: {

article:  {

required:  true,

type:  Object,

},

},

};

</script>

Code language: HTML, XML (xml)

The above component will receive the article as a required property and render it. The nuxt-content component renders our markdown content in a friendly HTML format.

Let us now setup the MoreArticles component. Create the component in the MoreComponents folder:


touch components/MoreArticles.vue

This is the component we will use to display other articles available for reading.


<!-- components/MoreArticles.vue -->

<template>

<div>

...

<div>

<div>

<h2>More articles</h2>

</div>

<div>

<div v-for="(article, key) in articles"  :key="key">

<div>

<img :src="$cloudinary.image.url(article.image)"  />

</div>

<div>

<div>

<nuxt-link :to="article.path">

<p>{{ article.title }}</p>

<p>{{ article.description }}</p>

</nuxt-link>

</div>

<div>

<div>

<nuxt-link :to="article.path">

<span>{{ article.author }}</span>

<img :src="$cloudinary.image.url(article.author_avatar)"  />

</nuxt-link>

</div>

<div>

<p>

<nuxt-link :to="article.path">

{{ article.author }}

</nuxt-link>

</p>

<div>

<time :datetime="article.createdAt">

{{ article.createdAt }}

</time>

</div>

</div>

</div>

</div>

</div>

</div>

</div>

</div>

</template>

Code language: HTML, XML (xml)

Our component will an Array of articles.


// components/MoreArticles.vue

<script>

export default {

props: {

articles:  {

required:  true,

type:  Array,

},

},

};

</script>

Code language: HTML, XML (xml)

The nuxt/content module globally injects the $context instance which means we can access it anywhere. We will use the asyncData hook to fetch the articles and merge them back into the Page state. Let’s do this in the pages/index.vue page.


// pages/index.vue

<script>

import ArticleRender from "../components/ArticleRender.vue";

  

import MoreArticles from "../components/MoreArticles.vue";

  

export default {

components: {

ArticleRender,

MoreArticles,

},

  

async  asyncData({  $content  })  {

const articles  =  await  $content("articles").fetch();

  

const article  =  articles.pop();

  

return {

article,

articles,

};

},

};

</script>

Code language: HTML, XML (xml)

In the above code, we render the last article and pass the rest to be displayed in the “More articles” section. As we created the rendering components already, let’s add them to the page template section.


<!-- pages/index.vue -->

<template>

<div>

<article-render :article="article"  />

<more-articles :articles="articles"  />

</div>

</template>

Code language: HTML, XML (xml)

The pages/index.vue page is the index page, the page we receive when we open http://localhost:3000. Other articles will be rendered through the pages/articles/_slug.vue page. This page will receive the slug of the article we want to read. Let us first create the file.


touch pages/articles/_slug.vue

We will use the slug to query the article we want to read and to exclude it in “More articles”. To exclude the current article in the query, we use the $ne flag which denotes not equal.


// pages/articles/_slug.vue

<script>

import ArticleRender from "../../components/ArticleRender.vue";

  

import MoreArticles from "../../components/MoreArticles.vue";

  

export default {

components: {

ArticleRender,

MoreArticles,

},

async  asyncData({  $content,  params  })  {

const article  =  await  $content("articles",  params.slug).fetch();

  

const articles  =  await  $content("articles")

.where({

slug:  { $ne:  params.slug  },

})

.fetch();

  

return  {

article,

articles,

};

},

};

Code language: HTML, XML (xml)

Let us now render the content as we did on the pages/index.vue page.


<!-- pages/index.vue -->

<template>

<div>

<article-render :article="article"  />

<more-articles :articles="articles"  />

</div>

</template>

Code language: HTML, XML (xml)

When we open our landing page, we should now see something similar to this:

Blog screenshot

Social share images are the images displayed when the article is shared. Here is a preview of the social share image we will be generating.

Social share screenshot

To generate the image, we will be using a series of transformations applied on the template file. We will overlay the article’s image, the article title, author image and author title.


// pages/index.vue

<script>

...

export default {

...

computed: {

ogImageUrl()  {

return  this.$cloudinary.image.url(

"automatic-social-share-images-nuxtjs/template/og-template",  {

transformation:  [{

overlay:  "automatic-social-share-images-nuxtjs:photo-1494253109108-2e30c049369b",

gravity:  "north",

height:  "400",

width:  "700",

},

{

overlay:  "automatic-social-share-images-nuxtjs:avataaars",

gravity:  "south_west",

height:  "50",

width:  "50",

x:  "250",

y:  "100",

},

{

overlay:  {

font_family:  "Arial",

font_size:  24,

font_weight:  "bold",

text:  this.article.title,

},

gravity:  "south_west",

co_rgb:  "000000",

x:  "250",

y:  "175",

},

{

overlay:  {

font_family:  "Arial",

font_size:  18,

font_weight:  "normal",

text:  this.article.author,

},

gravity:  "south_west",

co_rgb:  "000000",

x:  "325",

y:  "115",

},

],

}

);

},

},

...

};

</script>

Code language: HTML, XML (xml)

To customise the content displayed when sharing the articles, we will use the open graph protocol. This is the protocol that enables any web page to become a rich object in a social graph. We do this by adding basic metadata to our header elements. Examples of these tags are og:site_name, og:type, og:url, og:title etc. Twitter also has special tags it uses for its social cards. You can read more about them here.

To append meta tags to our page’s head (e.g. meta tags), we add a head method in our page script and return the key meta with the correct values.

// pages/index.vue

<script>


export  default  {

...

head()  {

return {

meta:  [{

property:  "og:site_name",

content:  "NuxtJs Blog"

},

  

{

hid:  "og:type",

property:  "og:type",

content:  "article"

},

  

{

hid:  "og:url",

property:  "og:url",

content:  process.env.NUXT_ENV_URL,

},

  

{

hid:  "og:title",

property:  "og:title",

content:  this.article.title,

},

  

{

hid:  "og:description",

property:  "og:description",

content:  this.article.description,

},

  

{

hid:  "og:image",

property:  "og:image",

content:  this.ogImageUrl,

},

  

{

property:  "og:image:width",

content:  "1200"

},

  

{

property:  "og:image:height",

content:  "630"

},

  

{

name:  "twitter:site",

content:  "NuxtJs Blog"

},

  

{

name:  "twitter:card",

content:  "summary_large_image"

},

  

{

hid:  "twitter:url",

name:  "twitter:url",

content:  process.env.NUXT_ENV_URL,

},

  

{

hid:  "twitter:title",

name:  "twitter:title",

content:  this.article.title,

},

  

{

hid:  "twitter:description",

name:  "twitter:description",

content:  this.article.description,

},

  

{

hid:  "twitter:image",

name:  "twitter:image",

content:  this.ogImageUrl,

},

],

};

},

...

};

</script>
Code language: HTML, XML (xml)

Using the above configuration, we have a solid setup for social media sharing of our landing page. To enable the same for our pages/articles/_slug.vue page, we just need to copy over the head and computed methods.

This is just an introduction to what we can achieve. Feel free to read more on Open Graph protocol to see what is possible.

Back to top

Featured Post