blog.itcode.devblog.itcode.dev

A Roundabout Way to Show a Blog's Popular Posts Using the Google Analytics API

If you look at post-centric sites, most of them offer a feature that shows popular posts based on view counts. Each post's view count is stored, and based on that, posts with the highest views or that meet certain criteria are shown. But my blog doesn't store view counts at all. That's because it's serverless. It only performs static hosting via GitHub, and the structure can't expect any computation beyond that on the server. So there's no way to actively count visitors.

A Roundabout Way to Show a Blog's Popular Posts Using the Google Analytics API

If you look at post-centric sites, most of them offer a feature that shows popular posts based on view counts. Each post's view count is stored, and based on that, posts with the highest views or that meet certain criteria are shown. But my blog doesn't store view counts at all. That's because it's serverless. It only performs static hosting via GitHub, and the structure can't expect any computation beyond that on the server. So there's no way to actively count visitors.
RWB0104
@RWBwritten at 2023-10-05 14:08:50

If you look at post-centric sites, most of them offer a feature that shows popular posts based on view counts. Each post's view count is stored, and based on that, posts with the highest views or that meet certain criteria are shown.

But my blog doesn't store view counts at all. That's because it's serverless. It only performs static hosting via GitHub, and the structure can't expect any computation beyond that on the server. So there's no way to actively count visitors.

🖼️ Google Analytics

Because of this, I had always just been mulling it over, until one day, while looking at the Google Analytics page as usual and checking visitor trends, I thought, "Wait, the data I need is already right here." I figured a service as big as Google Analytics probably has an API. If I used it well, it should be more than enough to implement the popular posts list I wanted.

Let's implement popular posts using GA data.

The data we want to call is user-based data. In other words, it's not data that's just handed out for simply hitting a URL. So we'll need an authentication object to call the API.

Since Google has built an OAuth 2.0-based authentication system, we need to issue an auth key for this. You can get a Google auth key from Google Cloud Platform.

🖼️ Creating a project

If this is your very first time and you haven't done anything yet, create a [New Project]. A project is a unit of work in GCP.

🖼️ Selecting the GA library

Once the project has been created, you can now proceed with the necessary tasks in that project. GA isn't usable right away — you need to enable the GA library in the project you created.

Click [+ Enable APIs and Services] at the top.


🖼️ Enabling the library

Enable [Google Analytics Data API]. Just click that row to enable it.

To issue an OAuth key, you first need to configure the OAuth consent screen. You can configure it from the [OAuth consent screen] menu.

🖼️ Configuring the consent screen

Set the user type to [Internal]. This isn't a service meant for outside use.


🖼️ Entering app information

Then fill in the required information. Since there's no need to submit it to Google for review, you can just fill it in reasonably.

However, for the authorized domain, enter your blog's root domain. For example, this blog's domain is currently blog.itcode.dev, so you'd enter itcode.dev, the root domain of that domain. Omit the protocol like https:// and enter only the root domain.


🖼️ Setting app scopes

Next, select the scope of data to be used by OAuth. The required scopes are as follows.

  • /auth/userinfo.email: verify the account's email address
  • /auth/userinfo.profile: personal information
  • /auth/analytics.readonly: GA data (read-only)

Since we only need to fetch GA data, it's set to readonly.

🖼️ Specifying a user

Add your account as a test user. Only accounts registered in this list can use the API key.

Once all the steps are done, you can create an OAuth client ID.

🖼️ Creating credentials

Create a client ID to use the OAuth service.


🖼️ Creating the client ID

Enter the required information. For [Authorized redirect URIs], enter your blog's URL. For example, for this blog, you'd enter something like https://blog.itcode.dev.

When using the OAuth service, the request URL is checked, and requests not coming from a registered URL will be rejected, so be careful.

Once the key is created, you can check the client ID and client secret, which completes the OAuth client ID issuance.

By default, a newly created project is created in test mode. Test mode doesn't require any particular review, but in exchange, it comes with several restrictions on using the API key. For example, only pre-registered test accounts can use the API key.

The problem is that in test mode, the Refresh Token issued has its expiration limited to a week. You can confirm this at this link.

A Google Cloud Platform project with an OAuth consent screen configured for an external user type and a publishing status of "Testing" is issued a refresh token expiring in 7 days.

As shown above, Google notes that in test mode, the Refresh Token's expiration is limited to 7 days.

That means, if you use a Refresh Token from test mode, you'll need to swap out that token every week — a problem. Not knowing about this difference, I ended up with an expired token for a while, which meant popular posts stopped showing.


From the [OAuth consent screen] menu, click the [Publish App] button to change the publishing status.

For normal use, you'd need to go through an app review. But since the scope we're using, /auth/analytics.readonly, is one of Google's sensitive scopes, you'd need to upload a demonstration of the API usage to YouTube...

If the production project hasn't been reviewed yet, a warning page like the one above will show up, and you can click through the link directly to proceed anyway.

Since I'm not planning to expose this to an unspecified number of users anyway, and I'm just going to issue myself a Refresh Token once and use it, there's no big issue with using it without review.

OAuth's authentication objects are usually split into an Access Token and a Refresh Token.

  • Access Token - a token containing actual authentication info. Short expiration time.
  • Refresh Token - a token used to reissue an Access Token. Long expiration time, or none at all.

Normally, you'd issue an Access Token via the client ID and use it. But since this key is only going to be hardcoded and used in my own blog, it's simply more convenient to issue a Refresh Token in advance and use that.

Let's issue a Refresh Token using the client ID we created.


🖼️ Google OAuth Playground

Google OAuth Playground is a site where you can test Google APIs.

You can log in directly, or use an existing client ID to test the API. On this site, you can issue a Refresh Token using the client ID we issued.

Wait! You need to add https://developers.google.com/oauthplayground to the authorized redirect URLs.


🖼️ Settings

Using the gear icon in the top right, you can specify your own client ID.

Checking [Use your own OAuth credentials] shows an input form. Enter the client ID and secret you issued in the previous step here.

Then, in the list on the right, click [Google Analytics API v3], and from the sub-list, select the following URL.

  • https://www.googleapis.com/auth/analytics.readonly

Then click [Authorize APIs] to get an Authorization code. During this process, you'll go through a Google sign-in, and since this is a test service, a warning about an unsafe environment will appear. Click [Continue] to proceed.

Be careful — you'll get a related error if the URI isn't authorized, or if you don't log in with the account designated as the test target.

Now that we have the Refresh Token, we can issue an Access Token, which contains the actual authentication info.

TXT

POST https://oauth2.googleapis.com/token?
grant_type=refresh_token
&client_id={:client_id}
&client_secret={:client_secret}
&refresh_token={:refresh_token}

You can see that grant_type and refresh_token have been added to a normal authentication API. This lets you make API requests without a separate login.

You can find more details about this API in one of the blog posts covering OAuth, [OAuth2.0] Building an OAuth2.0 Auth Server with ScribeJAVA - 5. Applying for and Implementing the Google OAuth Service.

JSON

{
    "access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
    "expires_in": 3920,
    "scope": "https://www.googleapis.com/auth/drive.metadata.readonly",
    "token_type": "Bearer"
}

The response comes in the format above. Of these, what we need is of course the access_token.

Now that the Access Token is ready, let's call the Google Analytics data. What we need is view count data per post. The API to call it is as follows.

TXT

POST https://content-analyticsdata.googleapis.com/v1beta/properties/{:project_id}:runReport?alt=json
Authorization: {:access_token}

JSON

{
	"dateRanges": [
		{
			"endDate": "today",
			"startDate": "30daysAgo"
		}
	],
	"dimensionFilter": {
		"filter": {
			"fieldName": "pagePath",
			"stringFilter": {
				"matchType": "BEGINS_WITH",
				"value": "/posts/2"
			}
		}
	},
	"dimensions": [
		{ "name": "pagePath" }
	],
	"limit": "10",
	"metrics": [
		{ "name": "active28DayUsers" }
	]
}
  • dateRanges: the date range for the data
  • dimensionFilter: filtering of the data. Only data matching the filter can be retrieved.
  • dimensions: the data's dimensions. Here, pagePath, the page's URL data, is called.
  • limit: the number of data items
  • metricAggregations: the metric aggregation method. Here, the total value of the TOTAL metric is additionally called.
  • metrics: the array of measured values in the report. Here, active28DayUsers, the number of unique active users over 28 days, is called. Up to 10 metrics are supported.

You can perform a data query by specifying the desired request values as shown above.

To summarize the request above.

  • Based on data from today back to 30 days ago (30daysAgo)
  • Data whose page path starts with /posts/2
    • This blog's post URLs are in the format /posts/2023/10/05/xxxx, so this filters for blog posts only
  • Add page path as a data dimension
  • Data limited to 10 items
  • Include the number of unique active users over 28 days as the report's measured value

If the request went through successfully, you'll get a response in the following form.

JSON

{
  "dimensionHeaders": [
    {
      "name": "pagePath"
    }
  ],
  "metricHeaders": [
    {
      "name": "active28DayUsers",
      "type": "TYPE_INTEGER"
    }
  ],
  "rows": [
    {
      "dimensionValues": [
        {
          "value": "/posts/2021/08/19/lets-encrypt"
        }
      ],
      "metricValues": [
        {
          "value": "790"
        }
      ]
    },
    {
      "dimensionValues": [
        {
          "value": "/posts/2021/05/22/tomcat-encoding-euckr"
        }
      ],
      "metricValues": [
        {
          "value": "767"
        }
      ]
    },
    // ... rows
  ],
  "totals": [
    {
      "dimensionValues": [
        {
          "value": "RESERVED_TOTAL"
        }
      ],
      "metricValues": [
        {
          "value": "9701"
        }
      ]
    }
  ],
  "rowCount": 149,
  "metadata": {
    "currencyCode": "KRW",
    "timeZone": "Asia/Seoul"
  },
  "kind": "analyticsData#runReport"
}

Along with the meta info, the requested data comes back — take a look at rows. Since we requested 10, rows will contain up to 10 items of data.

You can get each page's view count from metricValues. Making good use of this should let you display the blog's popular posts.

Using the GA4 Query Explorer makes it easy to build queries.

🖼️ Designing a request

You can design requests as shown above. Since it autocompletes based on select components, you can easily find the value you want, and also get a rough sense of what it does.

🖼️ Designed request JSON

At the bottom, it shows the JSON for the selected request. You can use this in your API request.

🖼️ Response result

You can also check the response result at a glance.

This is a method I found while pondering how to build a popular posts feature without a server. Honestly, storing the view count in a DB would be cleaner.

This was a post I started writing last year and stopped working on for no clear reason. Later on, as I stopped running the blog as often, I ended up deleting the file itself.

By chance I needed related content and happened to remember this, so I dug through the commit history and found it. Once again, I'm amazed at how great Git is.

But setting aside whether I needed it or not, the post was already about 80% finished — I have no idea why I stopped writing it partway through.

# GA# Google Analytics
ship
blog.itcode.dev

Notes from the π-th Alpaca

7.0.1
Developed by RWB since 2021.057th upgraded at 2026.08