Implementing Social Media Cards for Your Website
Tags: html, beginners, seo
Published: 2020-04-23 10:41:31


Promoting content on social media is an important part of marketing. If you want to make sure your posts/tweets/stories are eye-catching or at least visible on the user's wall, you probably want to leverage [OpenGraph](https://ogp.me/) tags to provide additional metadata to your content that will not only inform search engines about your content but also improve the appearence of social media cards.

## Problem

When we started posting content on social media we noticed that our social media cards were missing images, descriptions. We decided to fix that on our documentation website and it proved to be pretty easy to do.

To check if your site is already optimized, use the following official tools:

* Twitter: https://cards-dev.twitter.com/validator
* Facebook: https://developers.facebook.com/tools/debug
* LinkedIn: https://www.linkedin.com/post-inspector

Ideally what you want to see is a title, a description and an image showing up.

## Solution

Most of the metadata we used are standard OpenGraph.

### OpenGraph (Facebook, LinkedIn)

If you need more information about any of the properties used, read the [OpenGraph](https://ogp.me/) official documentation.

```html
<meta property="og:locale" content="en_US" />
<meta property="og:type" content="article" />
<meta property="og:site_name" content="platformOS Documentation" />
<meta property="article:section" content="Uncategorized" />

<meta property="og:title" content="{{ title }}" />
<meta property="og:description" content="{{ description }}" />
<meta property="og:url" content="{{ context.location.url }}" />

<meta property="article:published_time" content="{{ dates.created_at }}" />
<meta property="article:modified_time" content="{{ dates.content_updated_at }}" />
<meta property="og:updated_time" content="{{ dates.content_updated_at }}" />

<meta property="og:image" content="{{ 'images/pos-logo.png' | asset_url }}" />
<meta property="og:image:width" content="730" />
<meta property="og:image:height" content="412" />
```

We hardcoded some of the metadata because we did not see the need to make it dynamic. You might want to also add `author` - consult the documentation to choose the best tags for your use case.

Here's an explanation for the dynamic parts:

* `title`: a variable that we calculate based on what is in the current page metadata. We reused our title tag content here.
* `description`: same as above, we reused our usual meta data description content here
* `image`: absolute URL to the image. The image we used is 713x412px, but you can experiment with dimensions that fit your content/logo better.
* `url`: absolute URL to the page (with query params if essential)
* `published_time`: date when the content was first published - see below how we got the `dates` object
* `modified_time`: datetime when the content was last modified - see below how we got the `dates` object
* `updated_time`: datetime when the content was last modified - see below how we got the `dates` object


#### Dates

We did not use dates before, so we needed to pull those from the database using GraphQL. We used the `admin_pages` endpoint, filtered one page with the slug of the current page and pulled the date of file creation and the date of the last edit of the page body (not including metadata like title).

```liquid

{% graphql page_dates, slug: context.page.slug %}
query get_dates($slug: String!) {
  admin_pages(
    per_page: 1
    filter: { slug: { value: $slug } }
  ) {
    results {
      content_updated_at
      created_at
    }
  }
}
{% endgraphql %}

{% assign dates = page_dates | fetch: 'admin_pages' | fetch: 'results' | first %}
```

### Twitter

Twitter uses its own set of metadata to extract for its cards preview.

```html
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:description" content="{{ description }}" />
<meta name="twitter:title" content="{{ title }}" />
<meta name="twitter:image" content="{{ 'images/pos-logo-2to1.png' | asset_url }}" />
```

Here's an explanation of new elements:

* `card`: there are a couple of different card types that Twitter supports, we chose `summary_large_image` because it shows the image in a prolific way
* `image`: Twitter requires the image to be in 2:1 aspect ratio for `summary_large_image` or it will not show, so we prepared another image for that reason

## Results

The difference in visibility is pretty big but what's more important, users are able to instantly tell what the link is about before they click it.

Before:

<img src="https://uploads.prod01.oregon.platform-os.com/instances/1/assets/images/use-cases/social-media-cards/before.jpg" alt="Twitter card before adding OpenGraph meta tags" />

After:

<img src="https://uploads.prod01.oregon.platform-os.com/instances/1/assets/images/use-cases/social-media-cards/after.jpg" alt="Twitter card after adding OpenGraph meta tags" />

## Structured data

After you have prepared your OpenGraph metadata, it is also a good idea to add so called **structured data** into your website.

Structured data helps search engines find and understand your content and website. It is organized and tagged with specific groups of text that help search engines understand the context of that information and can return accurate results to searchers. 

This metadata is sometimes used by websites to generate rich snippets instead of OpenGraph. Additionally, this data is used by search engines to populate their rich snippets. Using structured data you can describe your content in detail. Because of that, it is more flexible. It is possible to describe the relation between entities (author, image, website). The more details you provide (within reason), the better.

### Example

```js
<script type='application/ld+json'>
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@type": "WebSite",
      "@id": "https://documentation.platformos.com",
      "url": "https://documentation.platformos.com",
      "name": "platformOS Documentation",
      "inLanguage": "en-US",
      "description": "Examples, tutorials, references, APIs, and best practices—everything you need to build future-proof applications on platformOS"
    },
    {
      "@type": "ImageObject",
      "inLanguage": "en-US",
      "url": "{{ 'images/pos-logo.png' | asset_url }}",
      "width": 730,
      "height": 412,
      "caption": "platformOS Documentation"
    },
    {
      "@type": "WebPage",
      "url": "{{ context.location.url }}",
      "name": "{{ title }}",
      "description": "{{ description }}",
      "isPartOf": {
        "@id": "https://documentation.platformos.com"
      },
      "inLanguage": "en-US",
      "primaryImageOfPage": {
        "@id": "{{ 'images/pos-logo.png' | asset_url }}"
      },
      "datePublished": "{{ dates.created_at }}",
      "dateModified": "{{ dates.content_updated_at }}",
      "potentialAction": [
        {
          "@type": "ReadAction",
          "target": [
            "{{ context.location.url }}"
          ]
        }
      ]
    }
  ]
}
</script>
```

We basically recycled data we used in OpenGraph, and added very simple relations.

### Resources

When you develop your own structured data, validate it on https://search.google.com/structured-data/testing-tool/u/0/ - it will show you warnings and errors should you encounter them.

Read the standard specification on: https://schema.org/docs/documents.html

Read these articles to get more practical information than from the official documentation:
* https://developers.google.com/search/docs/guides/intro-structured-data
* https://www.lullabot.com/articles/create-seo-juice-by-adding-json-ld-structured-data-to-drupal-8
* https://neilpatel.com/blog/structured-data/
* https://medium.com/slack-developer-blog/everything-you-ever-wanted-to-know-about-unfurling-but-were-afraid-to-ask-or-how-to-make-your-e64b4bb9254