DEV Community

loading...

Dynamic Open Graph Meta Tags with VueJS and Node (JS Bits)

cilly_boloe profile image Billy Coloe ・3 min read

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.

Discussion

pic
Editor guide
Collapse
jdcoffman profile image
Jonathan Coffman

This is an inspiring approach!

Collapse
cilly_boloe profile image
Billy Coloe Author

Why thank you, Jonathan! Tell me, what would be your ideal implementation of a VueJS state management solution?

Collapse
dailydevtips1 profile image
Chris Bongers

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

Collapse
cilly_boloe profile image
Billy Coloe Author

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