A while back, I included a views-counter component on my blog and I wrote about it here.
However, I also needed a way to return the total views of all the articles on my blog and render each view count to an article in the blog card UI.
Since MongoDB powers this feature on my blog, I went to their documentation and found out that what I wanted to do — which involved returning a couple of JSON data from the database — can be accomplished by using the find()
method on a collection.
I have a Post
model already in my codebase. importing my db
function and using it in a Next.js API route looks a bit similar to this.
Removing unwanted JSON objects
Initially, when I set out to build the views-count component you see when you read any article on this blog, I structured the Post schema like so:
I kept using this schema for a while, so whenever you visit an article’s page, say this one, for example, the data becomes this:
But, I went on to modify the postSchema
oblivious of how it’ll affect the structure of data in the db. The schema became something like this, with the slug
property highlighted in line 2.
So, when I logged the data to the console, I got a stream of JSON matching both the former and current post schemas.
That alone became chaotic for me.
Filtering the data
The current post schema has a slug
property and it was what I needed exactly. So I used the filter
method to return objects with the slug property.
I ran into another problem yet again. I was having duplicates of JSON objects with different view counts. All of this is my fault by the way. Something similar to the snippet below
I had to look for a way to locate the index of the duplicated object by comparing their slugs, filter it from the VIEWS_WITH_SLUGS
array, and return it in a new array, uniqueViews
I used the findIndex
method here to check if the index of the current post
object matches the first index of an object with the same slug
value in the array. If it does, it means the current object is unique and will be included in the new array.
Interfacing with the API route
With that out of the way, I created a custom data hook with SWR to obtain the JSON response.
Lines 11 through 13 shows how I’m using the swr hook to listen to the getAllViews
() callback function. revalidateOnFocus: false
ensures that the current view count isn’t incremented again in the db when you leave the tab and come back to it. PS: Not close it.
The hook expects a slug argument and lines 15 and 16 depend on that argument. Line 15 uses the array.find()
method to check if the supplied argument matches any object in the list of articles on my blog.
I go on to use the post
variable in line 16 to format numbers greater than or equal to 1000, so they become 1,000
. That’s what the regex I got from ChatGPT does, coupled with the string.replace()
method of JavaScript.
Lines 18 through 22 sums all the values gotten from the views
property with ?.map(({views}) => views)
. The reduce method sets the default value of the accumulator, a
to 0
, and then proceeds to add the current value b
. In each iteration, the value of the accumulator changes.
Using the hook in the component
To use the hook I imported it from the hooks directory and used it like so:
Highlighted in lines 10 through 17 is a tiny UX improvement I made to this UI component. Which was to add a visual cue, a skeleton that is rendered instead of “undefined” when the count isn’t ready/available yet.
Drawbacks
All of this wasn’t possible while I hosted on Netlify. The API routes would return the views properly in dev mode, but when I push to Prod, the serverless functions responsible for API routes with the next-runtime of Netlify would time out.
Error 502.
Sometimes, I’d get an error indicating that i.find()
is not a function. Whenever I check the stack trace, it points me to the API route in /pages/api/views
. Originally, I thought the error was from MongoDB, but it isn’t. I was wrong.
I had to move my deployment and update my DNS records from Netlify to Vercel’s as none of those errors were prevalent.
The last issue I encountered was somewhat related to pnpm and the Netlify next-runtime. Although, the PR that should fix the issue was merged, the issue still persists. You can take a look at a similar issue someone raised here.