Skip to content

Creating a Top Blog Posts List in Gatsby

Gatsby/

In this post you will learn how to create a list of your top blog posts in your Gatsby site. You will use data from Google Search Console API.

Prerequisites

You must have already set up a property for your blog on Google Search Console.

Furthermore, you need to have a project on Google Cloud Platform with the following done:

  • Have the Google Search Console API enabled.
  • Set up a service account and create credentials for it.
  • Have the service account added as a user to your Search Console.

You can read my post How to Use Google Search Console API in Node.js, if you need help setting it all up.

Setting Up Project Environment

To use Google Search Console API, you must install the client library and authenticate it.

Start by running the installation command.

npm i @googleapis/searchconsole

Next, create a .env file in the root directory of your project. Add the values of client_email and private_key fields from your service account’s key file. It’s a .json file you download after creating a key pair for a service account.

CLIENT_EMAIL=service-account-name@project-id.iam.gserviceaccount.com
PRIVATE_KEY=-----BEGIN PRIVATE KEY-----\nLONG_STRING\n-----END PRIVATE KEY-----\n

Make sure to add the .env file to .gitignore. Never commit your secrets to version control.

It is safe to use environment variables in Gatsby, because they are only used during build time. They are not included in the resulting code that you serve on your website.

Don’t forget to add these environment variables for your deployed website on your hosting platform of choice.

To use environment variables in your Gatsby application, set up dotenv on the first line of gatsby-config.js file.

// gatsby-config.js

require('dotenv').config();

Authenticating Google API Client

To access your Search Console property data, you need to authenticate the client using your service account credentials.

Decide where you want to place all the Google API code. You create a src/services/top-posts.js file for now.

Import the Search Console client library and create a function that authenticates it. The function should return the authenticated client instance.

// src/services/top-posts.js

const searchConsole = require('@googleapis/searchconsole');

const getAuthenticatedClient = () => {
  const auth = new searchConsole.auth.GoogleAuth({
    credentials: {
      private_key: process.env.PRIVATE_KEY.replaceAll('\\n', '\n'),
      client_email: process.env.CLIENT_EMAIL,
    },
    scopes: [
      'https://www.googleapis.com/auth/webmasters.readonly',
    ],
  });

  return searchConsole.searchconsole({
    version: 'v1',
    auth,
  });
};

Since the private key in your .env file contains \n characters, you need to replace them with real line breaks by using the replaceAll method.

Getting URLs of Your Top Posts

After authenticating the Search Console API client, you can perform a Search Analytics query to get your most clicked pages.

Use the authenticated client from previous section and call query method on client.searchanalytics. You must specify your siteUrl, and a range from startDate to endDate in YYYY-MM-DD format.

// src/services/top-posts.js

const getTopPageURLs = async () => {
  const client = getAuthenticatedClient();
  const res = await client.searchanalytics.query({
    siteUrl: 'https://your-site.com',
    requestBody: {
      startDate: '2022-01-01',
      endDate: new Date().toLocaleDateString('en-CA'),
      dimensions: ['page'],
      dimensionFilterGroups: [
        {
          filters: [
            {
              dimension: 'PAGE',
              operator: 'INCLUDING_REGEX',
              expression: '(/posts/).S*',
            },
            {
              dimension: 'PAGE',
              operator: 'notContains',
              expression: '/page/',
            },
          ],
        },
      ],
      rowLimit: 10,
    },
  });

  // If no pages matched the criteria
  if (!res.data.rows) {
    return [];
  }

  return res.data.rows.map(row => {
    return row.keys[0];
  });
};

A couple of interesting things are happening here.

Since you must specify dates in YYYY-MM-DD format. To do so, use the toLocaleDateString method and give it en-CA locale.

To query your top page URLs, you need to set dimensions: ['page']. You can also apply a filter with dimensionFilterGroups to control which pages you would like to retrieve.

For example, let’s assume your blog posts are under /posts/ subdirectory. You can use a regular expression with the INCLUDING_REGEX operator to only pick pages that contain /posts/ in their URL. Here’s a reference to RE2 syntax that Google expects you to use in your regular expression.

You can prevent certain pages from being fetched with the notContains operator. For example, to exclude a page with https://your-site.com/posts/pages/2/ URL, use expression: '/page/'.

Adjust the rowLimit property to specify the number of posts you want to fetch.

Passing Post Slugs Via Context

In the previous section you created an array with your top most clicked blog post URLs. Now you need to retrieve the slugs out of these URLs.

// src/services/top-posts.js

const getTopPostSlugs = async () => {
  const urls = await getTopPageURLs();

  return urls.map(url => {
    return url.split('/posts')[1];
  });
};

module.exports = {
  getTopPostSlugs,
};

You can use the split method to extract the slug from a URL. After splitting https://your-site.com/posts/post-name/, you can grab the /post-name/ part.

Pass these slugs via context to a page component where you want to query your top posts.

In this example, you recreate the index page to include topPostSlugs in its context.

// gatsby-node.js

exports.onCreatePage = async ({ page, actions }) => {
  const { createPage, deletePage } = actions;

  if (page.path === '/') {
    deletePage(page);

    const topPostSlugs = await getTopPostSlugs();

    createPage({
      ...page,
      context: {
        ...page.context,
        topPostSlugs,
      }
    });
  }
};

You will later use the topPostSlugs as a variable in your GraphQL query to filter the top blog posts.

Creating a Top Posts List Component

You will now create a component that displays a list of your top posts.

You need to pass two props to this component. The first one, posts, is an array of MarkdownRemark nodes that represent the top posts. The second one, sortedSlugs is an array that contains slugs in their sorted order. You need it to reorder your posts.

// src/components/TopPostsList.js

import React from 'react';
import { Link } from 'gatsby';

const TopPostsList = ({ posts, sortedSlugs }) => {
  const sortedPosts = [];

  posts.forEach(post => {
    const index = sortedSlugs.findIndex(slug => slug === post.fields.slug);

    sortedPosts[index] = post;
  });

  return (
    <ul>
      {sortedPosts.map((post, index) => {
        const { id, title } = post.frontmatter;

        return (
          <li key={id}>
            <Link to={`/posts${post.fields.slug}`}>
              {title}
            </Link>
          </li>
        );
      })}
    </ul>
  );
};

export default TopPostsList;

You need to sort your posts in the order of sortedSlugs, before you can display them in a list. When you will query your posts with GraphQL, they won’t be sorted by their click amount. You reorder them to match the order of sortedSlugs array.

The last step is to query the posts in a page component and pass them to the component you just created.

Filtering Top Posts With a GraphQL Query

In your page component, query the top posts and pass them to the TopPostsList component.

// src/pages/index.js

import React from 'react';
import { graphql } from 'gatsby';
import TopPostsList from '../components/TopPostsList';

const IndexPage = ({ data, pageContext }) => {
  const topPosts = data.allMarkdownRemark.nodes;

  return (
    <>
      <h1>Page Title</h1>
      <section>
        <h2>Top posts</h2>
        <TopPostsList posts={topPosts} sortedSlugs={pageContext.topPostSlugs} />
      </section>
    </>
  );
};

export default IndexPage;

export const query = graphql`
  query TopPosts($topPostSlugs: [String]) {
    allMarkdownRemark(filter: { fields: { slug: { in: $topPostSlugs } } }) {
      nodes {
        id
        frontmatter {
          title
        }
        fields {
          slug
        }
      }
    }
  }
`;

The $topPostSlugs variable in your query is replaced with pageContext.topPostSlugs, which you set in gatsby-node.js.

Summary

In this tutorial you learned a couple of things to create a top blog post list. These things include:

  • Using Search Console API to fetch your top pages by click amount.
  • Recreating a page to set its context.
  • Querying and filtering data using variables.
  • Passing the top posts to a component that displays them.