DEV Community

JS Bits Bill
JS Bits Bill

Posted on • Updated on

Dynamic Open Graph Meta Tags with VueJS and Node

I recently faced an interesting dilemma where I was unable to solution for using dynamic Open Graph metadata for my custom built T-shirt website, Fat Cattitude.

Essentially, I wanted to have each product page have its own OG image and description. The problem was that VueJS is a client-side framework with most content being dynamically created via JavaScript. When the Open Graph scraper requests a page it does not run any JavaScript - it only retrieves the content that is immediately returned from your server. You can see this content in Chrome by right clicking and choosing View page source:

It is this content, and only this content, that is scraped by the OG Debugger:

So the only way to dynamically generate these OG meta fields is via a server-side solution. Here's mine!

The only thing you'll need to have upfront is a seperate JS file of an array of objects containing the OG meta information for each page. You can then map your relevant paths to this object to render the appropriate content.

const products = [
  {
    id: 111111111,
    title: 'Corporate Fat Cat',
    ogImage: 'https://cdn.com/corporate.jpg',
    description: 'The fat cats in Washington don’t even look this good'
  },
  {
    id: 222222222,
    title: 'Gangsta Cat',
    ogImage: 'https://cdn.com/gangsta.jpg',
    description: 'That’s how we roll'
  },
  {
    id: 333333333,
    title: 'Mechanic Cat',
    ogImage: 'https://cdn.com/mechanic.jpg',
    description: 'I have no idea what I’m doing.'
  }
];
Enter fullscreen mode Exit fullscreen mode

First, include some default OG meta tags in the index.html file that gets served with every request:

<!-- OG Meta Tags -->
<meta property="og:url" content="https://www.fatcattitude.com/">
<meta property="og:type" content="website">
<meta property="og:title" content="Fat Cattitude">
<meta property="og:image" content="https://cdn.com/fat-cattitude-logo.jpg">
<meta property="og:description" content="There’s attitude and there’s CATTITUDE...">
Enter fullscreen mode Exit fullscreen mode

Then set up the middleware on all routes:

app.use('/*', (req, res, next) => {
  if (/^\/api\//.test(req.originalUrl)) next();
  else if (/\/item\//.test(req.originalUrl)) updateMetaTags(req, res);
  else res.sendFile(`${__dirname}/client/dist/index.html`);
});
Enter fullscreen mode Exit fullscreen mode

All that's going on here is that if the request is an API route, then proceed to the next step in our route processing. If the route contains /item/ then we call our updateMetaTags function (defined below). Otherwise, just serve the normal index.html from our /dist directory.

Here's what happens in updateMetaTags:

async function updateMetaTags(req, res) {

  // Get and parse products array from app src
  const productsSrc = `${__dirname}/client/src/products.js`;
  const productsText = await fs.promises.readFile(productsSrc);
  const productsArr = JSON.parse(productsText);

  // Retrieve product object that includes the current URL item id
  const productID = (req.originalUrl.match(/\d{9}/) || [])[0];
  const productObj = productsArr.find(prod => prod.id == productID);

  // Update the meta tag properties in the built bundle w/ Cheerio
  const baseSrc = `${__dirname}/client//dist/index.html`;
  const baseHTML = await fs.promises.readFile(baseSrc);
  const $base = $(baseHTML);
  const $url = $base.find('meta[property=og\\:url]');
  const $title = $base.find('meta[property=og\\:title]');
  const $image = $base.find('meta[property=og\\:image]');
  const $desc = $base.find('meta[property=og\\:description]');

  $url.attr('content', `https://${req.get('host')}${req.originalUrl}`);
  $title.attr('content', productObj.title);
  $image.attr('content', productObj.ogImage);
  $desc.attr('content', productObj.description);

  // Send the modified HTML as the response
  res.send($.html($base));
}
Enter fullscreen mode Exit fullscreen mode

Our products array is retrieved with the File System module and then the relevant object is parsed with a simple Array.find(). From here we use the Cheerio module to update the content attributes of our existing OG meta tags. Finally, we return the modified files as the server's response.

And there we go! The OG content is served dynamically based on the requested URL:


Check out more #JSBits at my blog, jsbits-yo.com. Or follow me on Twitter.

Top comments (17)

Collapse
 
sapientia profile image
sapientia

Hello Bill, this looks like the thing I am looking for, because I already tried almost everything, and certainly SSR it is quite complex to implement after the project is almost done(in my case), the only thing I did not get was the cheerio part, do an extra config is needed, besides the installation de package? also do you have a simple example repo of this? I was trying to replicate ur steps but it did not work for me. Thank you in advance for reading...

Collapse
 
js_bits_bill profile image
JS Bits Bill

Sure thing - I have an example on one of my websites here :
github.com/doctafaustus/js-bits/bl...

Cheerio can sometimes show some unexpected results so I would say log everything to make sure you're selecting the correct items before you implement.

Collapse
 
sapientia profile image
sapientia

Amazing, thank you very much, will take a look! Also thanks for the advice :)

Collapse
 
abernados profile image
abernados • Edited

Hi @js_bits_bill ,

How do you handle OG meta information from an API? I've been struggling for a week now. If you can help, that would be great.

Regards
Angel

Collapse
 
js_bits_bill profile image
JS Bits Bill

Hey Angel. That definitely adds complexity and may not be ideal since you would need to make a server-side request to the API to get the metadata before serving the page, potentially leading to some performance issues. That said, I'm happy to take a look if you have a link to any working code to see what might be possible.

Collapse
 
abernados profile image
abernados • Edited

We're using vue-meta. I also tried following this (github.com/nuxt/vue-meta/tree/1.x#...) but still no luck.

Thread Thread
 
js_bits_bill profile image
JS Bits Bill

Hey @abernados , I was getting terminal errors trying to start the app but I believe the problem is your app is using the regular client-side Vue framework whereas the guide you referenced is for the Nuxt framework. Since Nuxt allows you to build server-rendered Vue apps, it will allow you to retrieve meta data from an API and serve the assembled HTML but would come at the cost of you needing to switch entirely to that framework.

Thread Thread
 
abernados profile image
abernados • Edited

Hi @js_bits_bill , since we're using regular client-side Vue framework, what do you suggest then? I can't switch the whole project into Nuxt so I'm looking for other solutions.

Thread Thread
 
js_bits_bill profile image
JS Bits Bill

Yes, your options are limited. You may need to solution for a semi-hardcoded solution like the approach in this article. In any case, you will need to handle the meta data server-side as Vuejs won't be able to intercept document requests to change meta tags.

Thread Thread
 
abernados profile image
abernados

Okay, thank you for replying.

Collapse
 
deepakpreman profile image
deepakpreman

Hi Bill,
In this example what i understand is that we need to run node server for running UI code. How we can use this code in a situation where we are deploying UI in firebase only using build.
If you can help, that would be great.

Regards
Deepak

Collapse
 
js_bits_bill profile image
JS Bits Bill

Hi Deepak. I honestly don't know what options Firebase would give you to achieve this. Since the meta tags need to be set server-side, you might not be able to change them if they don't have a specific way for you to control this.
Best, Bill

Collapse
 
jdcoffman profile image
Jonathan Coffman

This is an inspiring approach!

Collapse
 
dailydevtips1 profile image
Chris Bongers

Nice work Bill!
Dynamic OG tags can be a pain in the #%S!

Collapse
 
js_bits_bill profile image
JS Bits Bill

Totally! If I were a more productive man, I'd make an NPM package, but I'm not :)

Collapse
 
gbahdeyboh profile image
Bello Gbadebo

Hi Bill.

This is quite interesting. Did you eventually use Vue with it? Vue cdn?

Collapse
 
js_bits_bill profile image
JS Bits Bill

Thanks Bello, I did. Since writing this, I learned about server-sider rendering with frameworks like NuxtJS that would have a solution for this sort of thing but I wanted to keep things simple for my small site so this worked pretty well with my client-side Vue site.