Photo: Social media apps
Photo by Nathan Dumlao on Unsplash

When a user shares a web link, most applications parse a standard set of meta tags and use the provided information to render a 'card', including an image, a title, and a summary.

A few competing standards exist; the most relevant are Twitter Cards and Facebook's Open Graph Protocol.

When the necessary meta tags are missing, applications such as Twitter, Facebook, Whatsapp, Signal, Telegram, etc., will be unable to render the card. In such cases, they will either display the link as-is or try to infer some parts of the card metadata; in the latter case, your mileage may vary.


Here's an example of a site without social sharing cards metadata:

Screenshot of a shared link to a page without social card metadata


And here's how my site's homepage is rendered:

Screenshot of a rendered Twitter card

Since I wanted my site to display correctly in as many apps as possible, I provided both Open Graph and Twitter Card tags!


I use Gatsby for my site, and unfortunately, it does not have native support for social cards. Also, when I implemented this feature, I was pretty new to GatsbyJS and had to figure out how to load files and process images through GraphQL (childImageSharp, gatsbyImageData, GatsbyImage, StaticImage).

It was rough, to say the least (unclear documentation, competing advice for Gatsby v2 and v3, and my general lack of knowledge).

Fortunately, I prevailed! Here's how:

Load any image via GraphQL

  const { myImage } = useStaticQuery(graphql`
    query {
      myImage: file(relativePath: { eq: "name-of-file.jpg" }) {
        childImageSharp {
          gatsbyImageData(layout: CONSTRAINED)
        }
      }
    }
  `);

This works with images placed under src/images/.

import { getImage, getSrc } from 'gatsby-plugin-image';

// ...

const img = getImage(image);
const imgSrc = SITE_URL + getSrc(img);

Create an SEO component

Ok, I'll admit, Gatsby does have good SEO support, including some general advice on social cards.

This formed the basis of my configuration, although I had to heavily customize it!

I configured the SEO component to accept a number of inputs:

function SEO({ title, description, lastUpdated, tags, image, imageAlt, absoluteURL }) {
  const img = getImage(image);
  const imgSrc = SITE_URL + getSrc(img);

  let meta = [];

  //...
  return (
    <Helmet
      htmlAttributes={{prefix: 'og: http://ogp.me/ns#'}}
      title={title}
      meta={meta}
      //...
    />
  );
}

Seo.propTypes = {
  title: PropTypes.string.isRequired,
  description: PropTypes.string.isRequired,
  lastUpdated: PropTypes.string.isRequired,
  tags: PropTypes.arrayOf(PropTypes.string),
  image: PropTypes.object.isRequired,
  imageAlt: PropTypes.string.isRequired,
  absoluteURL: PropTypes.string.isRequired,
};
export default SEO

And generated the required meta tags:

Generate Twitter Card meta tags

meta.push({
  name: "twitter:card",
  content: "summary_large_image",
});
meta.push({
  name: `twitter:title`,
  content: title,
});
meta.push({
  name: `twitter:description`,
  content: pageDescription,
});
meta.push({
  property: `twitter:image`,
  content: imgSrc,
});
meta.push({
  name: `twitter:image:alt`,
  content: imageAlt,
});

Generate Open Graph meta tags


meta.push({
  property: `og:type`,
  content: `website`,
});
meta.push({
  property: `og:url`,
  content: absoluteURL,
});
meta.push({
  property: `og:title`,
  content: title,
});
meta.push({
  property: `og:description`,
  content: pageDescription,
});
meta.push({
  property: `og:image`,
  content: imgSrc,
});
meta.push({
  name: `og:image:alt`,
  content: imageAlt,
});
meta.push({
  property: `og:updated_time`,
  content: lastUpdated,
});
meta.push({
  property: "og:image:width",
  content: parseInt(img.width),
});
meta.push({
  property: "og:image:height",
  content: parseInt(img.height),
});

Bringing it all together

And that was it! All that was left was for me to send all the required variables via my page layouts, e.g.

<Seo title="My article" description="Short summary of my article" etc="...">

But is it working?

Testing social sharing card rendering

To test your feature, use the following tools:

Bonus: social sharing buttons

No site would be complete without a feature to allow users to share content on social media easily!

I found an excellent tutorial for integrating social sharing buttons.

Here's what you have to do:

Create a component and import media from react-share

import {
  TwitterShareButton,
  TwitterIcon,
} from 'react-share';

const ShareButtons = ({
  title,
  url,
  tags,
  twitterHandle,
}) => {

  // remove any non-alphanumeric characters
  const hashtags = tags.map((t) => t.replace(/[^a-zA-Z0-9]+/g, ''));

  return (
    <>
      <TwitterShareButton
        title={title}
        url={url}
        via={twitterHandle}
        hashtags={hashtags}
      >
        <TwitterIcon size={40} round={true} />
      </TwitterShareButton>
    </>
  );
};

(Of course, the above is greatly simplified and only displays Twitter. For my site, I defined more.)

Include the component on your pages

<ShareButtons
  url={SITE_URL + location.pathname}
  title={...}
  description={...}
  tags={[...]}
/>

And here is the result:

Screenshot of social sharing buttons

Conclusion

I hope you found this short tutorial helpful! As I'm writing it now, it feels pretty straightforward, but at the time, I struggled for hours trying to piece together confusing or incomplete information from various articles and blogs.

Hopefully, this can help you avoid my experience and integrate social sharing in your Gatsby site much faster!

In an ideal world, Gatsby would have come with better, out-of-the-box, social sharing features!

Maybe one day...

If you enjoyed this post, please share it with your friends!