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.
Codesandbox
The final project can be viewed on Codesandbox.
You can find the full source code on my [Github](https://github.com/musebe/Social-sharing-images-nuxt.git) repository.Creating our blog
Nuxt.Js initialization
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.
1yarn create nuxt-app automatic-social-share-nuxtjs23# OR45npx crete-nuxt-app automatic-social-share-nuxtjs67# OR89npm init nuxt-app automatic-social-share-nuxtjs
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
1yarn dev23# OR45npm run dev
Adding dummy content
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.
1mkdir content/articles23cd content/articles45wget https://raw.githubusercontent.com/musebe/Social-sharing-images-nuxt/main/content/articles/curabitur.md67wget https://raw.githubusercontent.com/musebe/Social-sharing-images-nuxt/main/content/articles/donec.md89wget https://raw.githubusercontent.com/musebe/Social-sharing-images-nuxt/main/content/articles/duis.md1011wget https://raw.githubusercontent.com/musebe/Social-sharing-images-nuxt/main/content/articles/nam.md
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 setup
To be able to render the images from Cloudinary in our project, we need to set up the recommended Nuxt.Js plugin.
1yarn add @nuxtjs/cloudinary23# OR45npm install @nuxtjs/cloudinary
Configure the plugin by adding a cloudinary
section to the nuxt.config.js
file. This is the project's configuration file.
1// nuxt.config.js23export default {45...67cloudinary: {89cloudName: process.env.NUXT_ENV_CLOUDINARY_CLOUD_NAME,1011useComponent: true,1213secure: true1415}1617}
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.
1touch .env
You may now set the variable:
1NUXT_ENV_CLOUDINARY_CLOUD_NAME=<cloudinary-cloud-name>
The above setup will ensure that the $cloudinary
instance is injected globally.
Article renderer
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.
1touch components/ArticleRender.vue
1<!-- components/ArticleRender.vue -->23<template>45<div>67...89<div>1011<div>1213<h1>1415<span>{{ article.title }}</span>1617</h1>1819<img :src="$cloudinary.image.url(article.image)" />2021</div>2223<div>2425<div>2627<div>2829<div>3031<img3233:src="$cloudinary.image.url(article.author_avatar)"3435alt="article.author"3637/>3839</div>4041<div>4243<p>4445{{ article.author }}4647</p>4849</div>5051</div>5253</div>5455<nuxt-content :document="article" />5657</div>5859</div>6061</div>6263</template>
1// components/ArticleRender.vue23<script>45export default {67props: {89article: {1011required: true,1213type: Object,1415},1617},1819};2021</script>
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:
1touch components/MoreArticles.vue
This is the component we will use to display other articles available for reading.
1<!-- components/MoreArticles.vue -->23<template>45<div>67...89<div>1011<div>1213<h2>More articles</h2>1415</div>1617<div>1819<div v-for="(article, key) in articles" :key="key">2021<div>2223<img :src="$cloudinary.image.url(article.image)" />2425</div>2627<div>2829<div>3031<nuxt-link :to="article.path">3233<p>{{ article.title }}</p>3435<p>{{ article.description }}</p>3637</nuxt-link>3839</div>4041<div>4243<div>4445<nuxt-link :to="article.path">4647<span>{{ article.author }}</span>4849<img :src="$cloudinary.image.url(article.author_avatar)" />5051</nuxt-link>5253</div>5455<div>5657<p>5859<nuxt-link :to="article.path">6061{{ article.author }}6263</nuxt-link>6465</p>6667<div>6869<time :datetime="article.createdAt">7071{{ article.createdAt }}7273</time>7475</div>7677</div>7879</div>8081</div>8283</div>8485</div>8687</div>8889</div>9091</template>
Our component will an Array of articles.
1// components/MoreArticles.vue23<script>45export default {67props: {89articles: {1011required: true,1213type: Array,1415},1617},1819};2021</script>
Fetching all articles
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.
1// pages/index.vue23<script>45import ArticleRender from "../components/ArticleRender.vue";6789import MoreArticles from "../components/MoreArticles.vue";10111213export default {1415components: {1617ArticleRender,1819MoreArticles,2021},22232425async asyncData({ $content }) {2627const articles = await $content("articles").fetch();28293031const article = articles.pop();32333435return {3637article,3839articles,4041};4243},4445};4647</script>
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.
1<!-- pages/index.vue -->23<template>45<div>67<article-render :article="article" />89<more-articles :articles="articles" />1011</div>1213</template>
Fetching a single article
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.
1touch 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.
1// pages/articles/_slug.vue23<script>45import ArticleRender from "../../components/ArticleRender.vue";6789import MoreArticles from "../../components/MoreArticles.vue";10111213export default {1415components: {1617ArticleRender,1819MoreArticles,2021},2223async asyncData({ $content, params }) {2425const article = await $content("articles", params.slug).fetch();26272829const articles = await $content("articles")3031.where({3233slug: { $ne: params.slug },3435})3637.fetch();38394041return {4243article,4445articles,4647};4849},5051};
Let us now render the content as we did on the pages/index.vue
page.
1<!-- pages/index.vue -->23<template>45<div>67<article-render :article="article" />89<more-articles :articles="articles" />1011</div>1213</template>
When we open our landing page, we should now see something similar to this:
Creating social share images
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.
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.
1// pages/index.vue23<script>45...67export default {89...1011computed: {1213ogImageUrl() {1415return this.$cloudinary.image.url(1617"automatic-social-share-images-nuxtjs/template/og-template", {1819transformation: [{2021overlay: "automatic-social-share-images-nuxtjs:photo-1494253109108-2e30c049369b",2223gravity: "north",2425height: "400",2627width: "700",2829},3031{3233overlay: "automatic-social-share-images-nuxtjs:avataaars",3435gravity: "south_west",3637height: "50",3839width: "50",4041x: "250",4243y: "100",4445},4647{4849overlay: {5051font_family: "Arial",5253font_size: 24,5455font_weight: "bold",5657text: this.article.title,5859},6061gravity: "south_west",6263co_rgb: "000000",6465x: "250",6667y: "175",6869},7071{7273overlay: {7475font_family: "Arial",7677font_size: 18,7879font_weight: "normal",8081text: this.article.author,8283},8485gravity: "south_west",8687co_rgb: "000000",8889x: "325",9091y: "115",9293},9495],9697}9899);100101},102103},104105...106107};108109</script>
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.
1// pages/index.vue23<script>456export default {78...910head() {1112return {1314meta: [{1516property: "og:site_name",1718content: "NuxtJs Blog"1920},21222324{2526hid: "og:type",2728property: "og:type",2930content: "article"3132},33343536{3738hid: "og:url",3940property: "og:url",4142content: process.env.NUXT_ENV_URL,4344},45464748{4950hid: "og:title",5152property: "og:title",5354content: this.article.title,5556},57585960{6162hid: "og:description",6364property: "og:description",6566content: this.article.description,6768},69707172{7374hid: "og:image",7576property: "og:image",7778content: this.ogImageUrl,7980},81828384{8586property: "og:image:width",8788content: "1200"8990},91929394{9596property: "og:image:height",9798content: "630"99100},101102103104{105106name: "twitter:site",107108content: "NuxtJs Blog"109110},111112113114{115116name: "twitter:card",117118content: "summary_large_image"119120},121122123124{125126hid: "twitter:url",127128name: "twitter:url",129130content: process.env.NUXT_ENV_URL,131132},133134135136{137138hid: "twitter:title",139140name: "twitter:title",141142content: this.article.title,143144},145146147148{149150hid: "twitter:description",151152name: "twitter:description",153154content: this.article.description,155156},157158159160{161162hid: "twitter:image",163164name: "twitter:image",165166content: this.ogImageUrl,167168},169170],171172};173174},175176...177178};179180</script>
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.