Popular Articles Section with Gatsby and Google Analytics

You have a blog, but you need more internal cross-linking between pages on your site to increase user engagement? There are several approaches to get the desired results. This article outlines one of them, but they are certainly various ways to reach the goal.

Ways to increase user engagement on your website

The most obvious solution is to promote other blog posts of yours. Most likely you want to promote those already popular, thus increasing chances of getting another click.

But here is a list of other options except using a popular section on your blog:

  • Related Posts (show posts in the topic neighbourhood)
  • Most recent viewed blog article
  • Most favorited blog articles (user need to have the ability to mark posts)
  • latest published articles
Combination of sections

Most of the time you see blog article sidebars which have at least two different ways to increase engagement by using two sections instead of just one.

We will focus on how to implement the "most popular articles approach" with a Jamstack / gatsby based website project. There you can't render those sections dynamically cause Jamstack basically means that we have a (super optimized) static site environment . We need a way to do it at build time.

The ingredients

We need a running blog with Gatsby. I won't dive into the details of how to setup a blog with gatsby. There are a lot of tutorials like this or that. Then we need an analytics platform. For the sake of convenience (and being free), i will use Google Analytics for that. Last but not least you need a Google Cloud Account for using their Analytics Reporting API.

The popular section component in Gatsby

So let's write a popular section component which highlights the top 3 viewed articles on a site. In this example, the query expects a markdown powered blog which we will query.

import React from 'react';  
import {Link} from "gatsby";  
import { useStaticQuery, graphql } from "gatsby"  
      
    const data = useStaticQuery(  
	    graphql`  
	      query loadPagesQuery { 
	       allPageViews { 
	         nodes { 
	           totalCount 
	           id 
	         } 
	       }  
	       allMarkdownRemark { 
	         nodes { 
	            id 
	            fields {
	             slug 
	            } 
	            frontmatter { 
	              title 
	              featuredimage { 
	                childImageSharp { 
	                  fixed(width: 100) { 
	                    src 
	                  } 
	                } 
	              } 
	              date(formatString: "DD.MM.YYYY") 
	            } 
	          } 
	        } 
	      }
	    `);  
    
    
        const { allMarkdownRemark: {nodes: posts}} = data;  
        const { allPageViews: {nodes: pageViews}} = data;  
      
        const blogPostsWithHitDataFromGA = posts.map(
         (post) => (  
            {
	          ...post, 
	          pageView: pageViews.find(pageView => pageView.id === post.fields.slug) || {totalCount: 0}
	        }  
        ));  
        blogPostsWithHitDataFromGA.sort((a, b) => {  
            return b.pageView.totalCount - a.pageView.totalCount  
        }).splice(numberOfPosts);  
      
        return (  
         <>  
          {blogPostsWithHitDataFromGA.map((post) => (  
            <PopularPostEntry key={post.id}  post={post} />
          ))}  
         </>
        );  
    };  
      
    export default PopularPosts; 

Ufff. That's a lot right? But let's dissect this piece by piece. This is a component like every other React component. It will be used as part of a Single Blog Page Component, which i won't expose, but let's just pretend that there is somewhere a SingleBlogPage Component, which uses this component above.

Since we can't use a PageQuery (its not a gatsby page, its a component), we need to use the StaticQuery. If you use a recent React version together with Gatsby, you can use the useStaticQuery Hook to make the code look a bit more lean. With that we define two GraphQL queries. One (allPageViews) for getting the hit data from Google Analytics and one (allMarkdownRemark) to get all our blog posts. If you use another CMS like Storyblok, your GraphQL query will look different of course, which means you change allMarkdownRemark to something like ....

Those two queries will reveal datasets like these:

{
  "data": {
    "allPageViews": {
      "nodes": [{
         "totalCount": 633,
	     "id": "/jamstack-the-way-to-better-performing-websites/"
	  },
	  {
	     "totalCount": 223,
	     "id": "/manage-and-display-authors-with-netlifycms-and-gatsby/"
	  }]
    },
    "allMarkdownRemark": {
       "nodes": [{
	     "id": "270e78c8-6c0a-50f1-b379-46efe31ad85e",
	     "fields": {
	        "slug": "/manage-and-display-authors-with-netlifycms-and-gatsby/"
	     },
	   "frontmatter": {
	     "title": "Manage and display Blog Authors with NetlifyCMS and Gatsby",
	     "featuredimage": {
	       "childImageSharp": {
		     "fixed": {
		       "src": "/static/5c8bdd4e523172cf1e95f4b200e4d8ed/65e33/netlifycms_author_blog.png"
             }
	       }
	     },
	     "date": "11.01.2021"
       } 
    },
    {
	  "id": "4a0214fd-5dcb-568d-b77e-fbdb71f577f8",
      "fields": {
	    "slug": "/jamstack-the-way-to-better-performing-websites/"
	  },
	  "frontmatter": {
	    "title": "Jamstack - The way to better performing websites",
		"featuredimage": {
		  "childImageSharp": {
		    "fixed": {
			  "src": "/static/8fe3ae348c9fbc0062a869528d6b41a3/65e33/jamstack_blog.png"
			}
		  }
		},
		"date": "10.01.2021"
	  }
	}]	
  }
}

If you want to execute this query straight away, you need to add the gatsby-plugin-google-analytics-reporter plugin and make the connection to your Analytics account, which i will explain in a few chapters below.

Using a different blog / cms

Of course it's super easy to use a different, perhaps more sophisticated cms together with the outlined approach. Let's take Storyblok for example. With their super nice headless CMS and first class Gatsby support via their source plugin gatsby-source-storyblok , you just need to swap out the allMarkdownRemark query part and use the following graphql. That's basically the only difference.

graphql(
  `{
    stories: allStoryblokEntry(filter: {field_component: {eq: "blogpost"}}) {
      edges {
        node {
          id
          name
          slug
          field_component
          full_slug
          content
        }
      }
    }
  }`
)
Headless CMS and Gatsby

If you want to decide on a headless CMS for Gatsby, apart from comparing the features on the respective product pages, make sure that they have 1st class plugin support for Gatsby. There should be at least a Source-Plugin available and look for good docs too. Storyblok for instance has a nice Gatsby Technology Hub which is always a plus to get something up and running quickly.

Merging Datasets

The goal is of course to get like the Top3 Popular Articles so we need to merge the still unrelated datasets into one and sort it by popularity.

The merge will be done by calling:

const blogPostsWithHitDataFromGA = posts.map(
   (post) => (  
      {
        ...post, 
        pageView: pageViews.find(
          pageView => pageView.id === post.fields.slug) || 
             {totalCount: 0}
      }  
  )
);  

We iterate over all of our blog posts and return the structure of the original Blog Posts Query together with a new attribute called pageView. In order to get the the hits of the current post in the iteration, we search in the pageView dataset and look for the slug. If we find it, we will return the structure, if not, we will create the target structure but with totalCount = 0. Otherwise we would have "undefined" problems when we sort later.

Sorting the resulting Dataset

We created a new array called blogPostsWithHitDataFromGA. Let's examine the sorting code in detail:

blogPostsWithHitDataFromGA.sort((a, b) => {  
  return b.pageView.totalCount - a.pageView.totalCount  
}).splice(numberOfPosts);  

Only 3 lines of code. Yipieh. We just sort the array based on the totalCount attribute of our blog post object by supplying a sorting closure function. At the end we will splice the array, means, we just use the first "numberOfPosts" posts. Now our blogPostsWithHitDataFromGA array is pretty much ready for further processing.

Generating the frontend code

return (  
  <>  
   {blogPostsWithHitDataFromGA.map((post) => (  
      <PopularPostEntry key={post.id}  post={post} />
   ))}  
  </>
);  

The last part is just using another Component PopularPostEntry which basically consists of some HTML to produce a single popular Article items as shown below. If you are a bit familiar with Gatsby, this should be a nobrainer to create.

Popular Article

Integrate Google Analytics Reporting to your Gatsby website

Installing plugins in Gatsby is easy and makes fun. If you cant get enough of it, i also recommend my article "5 great gatsby plugins you might don't know". But let's install the one needed right now:

npm i gatsby-plugin-google-analytics-reporter

Unfortunately installing the plugin is the easiest part. Configuring is a bit more complex because... you guessed it... we need to authenticate ourselves to the google cloud. This is the configuration which we need to tweak:

// gatsby-config.js
{
  resolve: `gatsby-plugin-google-analytics-reporter`,
  options: {
    email: 'PASTE_EMAIL',
    privateKey: 'PASTE_PRIVATE_KEY',
    viewId: 'PASTE_VIEW_ID',
    startDate: `30daysAgo`,
    endDate: `today`,
    pageSize: 10000
  }
},

We have 3 important parts here we need to figure out. email, privateKey and viewId. The viewId can be looked up in Google Analytics. You most likely know what a view is and each view has an ID in Google Analytics. Grab it from the Analytics Dashboard.

Getting reporting credentials from Google Cloud

Now we need to get email and privateKey. For this we head to https://console.cloud.google.com/

In the main menu, click on the IAM & Admin menu item. You should see a screen like this one:

Google Cloud Step 1

After selecting Service Accounts, you can go ahead and create a new service account, which will use inside the plugin later. So fill out the following form.

Google Cloud Step 2

It's important to not only create a name for the account but also to write down the resulting email address. You can leave out the other 2 steps in the wizard.

At the end, in the overview section, you need to create / download the JSON keyfile. Use the three-dots at the right and select the option to create a JSON key.

Google Cloud Step 3

When you open the keyfile after the download has finished, you will see something like this:

{
  "type": "service_account",
  "project_id": "your_project_id",
  "private_key_id": "xxxxxyyyyyyyxxxxxxxxyyyyyyyy",
  "private_key": "-----BEGIN PRIVATE KEY-----\nMaXTg==\n-----END PRIVATE KEY-----\n",
  "client_email": "gatsby-analytics@your_project_id.iam.gserviceaccount.com",
  "client_id": "XXXXXXXXXXXXXXXXXX",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/gatsby-analytics-view%40yourpoject.iam.gserviceaccount.com"
}

You just need two (!) things from here. If you have not written down the email in the previous step, you can copy it from here too. Then the most important thing is the private_key string. Of course i shortened the dummy key in this example so that it doesn't spam the code block. Just copy that string and put it into your gatsby-config section.

The last step is to authenticate this created service account to access your google analytics account. For this we go into the Google Analytics Console and in the admin section locate the User Management menu item:

GA Step 1

From there you add a new user by following the screenshots provided below. Bear in mind to use the email address from the keyfile when you create the new user in analytics.

GA Step 2

GA Step 3

That's pretty much it. Your gatsby-config section should look like this (of course with your specifics in there).

{
    resolve: `gatsby-plugin-google-analytics-reporter`,
    options: {
        email: 'gatsby-analytics@your_project_id.iam.gserviceaccount.com',
        privateKey: "-----BEGIN PRIVATE KEY-----\nMaXTg==\n-----END PRIVATE KEY-----\n",
        viewId: '1234567890',
        startDate: `30daysAgo`,
        endDate: `today`,
        pageSize: 10000
    }
},

Summary - How does it work?

With this in place, everytime you build your gatsby project, the plugin will make a request to the Google Analytics Reporting API to get he latest numbers and generate the Popular Articles Section based on that. With the outlines config, it checks the last 30 days, which might make sense because you dont want to have the most popular posts all-time but in recents days/weeks. Otherwise people would see your "Why Perl is the best programming language for HTML projects" blog post on top.