Creating a Blog - 11. Adding View Count to Posts
- blog
- web
Blog Creation Series
Title | Link |
---|---|
1. Basic Setup | https://witch.work/posts/blog-remake-1 |
2. HTML Design for Main Page | https://witch.work/posts/blog-remake-2 |
3. Structure Design for Post Detail Page | https://witch.work/posts/blog-remake-3 |
4. Enabling Relative Paths for Images | https://witch.work/posts/blog-remake-4 |
5. Minor Page Composition Improvements and Deployment | https://witch.work/posts/blog-remake-5 |
6. Layout Design for Page Elements | https://witch.work/posts/blog-remake-6 |
7. Main Page Component Design | https://witch.work/posts/blog-remake-7 |
8. Design for Post List/Content Page Components | https://witch.work/posts/blog-remake-8 |
9. Automatic Generation of Post Thumbnails | https://witch.work/posts/blog-remake-9 |
10. Design Improvements for Fonts, Cards, etc. | https://witch.work/posts/blog-remake-10 |
11. Adding View Count to Posts | https://witch.work/posts/blog-remake-11 |
12. Page Theme and Post Search Functionality | https://witch.work/posts/blog-remake-12 |
13. Improvements to Theme Icons and Thumbnail Layouts | https://witch.work/posts/blog-remake-13 |
14. Changing Post Categorization to Tag-Based | https://witch.work/posts/blog-remake-14 |
Optimizing Calculations on Main Page | https://witch.work/posts/blog-opt-1 |
Creating Pagination for Post List | https://witch.work/posts/blog-opt-2 |
Uploading Images to CDN and Creating Placeholders | https://witch.work/posts/blog-opt-3 |
Implementing Infinite Scroll on Search Page | https://witch.work/posts/blog-opt-4 |
This post details the process of adding view counts to my new blog. I used some excerpts from the previous incomplete work on adding view counts for informational purposes.
In fact, if I had used Vercel, it would have been much easier to handle everything, but trying to do it on Cloudflare was extremely challenging. Eventually, I had to redeploy to Vercel.
There were numerous challenges and failures. If anyone wishes to deploy a NextJS app on Cloudflare Pages and add view counts... let’s hope Cloudflare Pages supports NodeJS runtime or properly supports SWR in the edge runtime.
However, reviewing the issues, the former appears to have no promise at all (it seems to be trying to run results built on Vercel on Cloudflare, which uses different underlying technologies) and the latter does not seem to be in particular plans. If anyone has succeeded, I would appreciate it if you could share your experience.
1. Moving Posts
First, I moved all the posts to the new blog. After moving, the build took much longer.
2. Busuanzi
There is a Chinese service called Busuanzi, which allows for easy addition of view counts to pages and blogs. This section includes excerpts from an older post of mine.
Following Fienestar's guide, with a few modifications to fit my blog.
First, add the following code to the site's head or body.
My blog has a Seo
component that is included on all pages. This Seo
component is composed of the Helmet component from react-helmet, which manages the content in the head tag. (Note: In Next.js, you'll likely need to add this script via a Script
tag in the Head component).
Thus, add the code in between the Helmet component.
2.1. Total Site Views and Unique Visitors
Total site views and unique visitors can be added with the following code. The id in the span is important.
This code needs to be added to the blog's page. In my blog, I added it right below my profile in the BlogIndex component, which represents the main page.
While struggling with this view count display, I thought a lot about restructuring the blog, hence I didn’t style it right away.
2.2. Page Views
The view count for a single page can be added with the following code.
This has been suitably added just below the post title.
I’ll document other struggles below. I hope this knowledge will assist when I overhaul the blog in the future.
Remarkably, just two months later, I am rewriting this post. This time it’s in Next.js, hence the rewrite.
3. Google Analytics - Registration
3.1. Creating an Account
Let's create a new Google Analytics account.
Also, set up the website property.
After inputting the business information, agree to the terms, and finish creating the account.
3.2. Migrating the Blog
Now that my blog is getting structured, let’s connect my witch.work
domain to the new blog.
Currently, it’s connected to the blog page I created with Gatsby.
Access the Cloudflare Pages menu as follows.
Then enter the project you were previously using and delete the custom domain
menu for witch.work
.
Add witch.work
in the custom domain for witch-next-blog
.
3.3. Adding Data Stream and Tags
Next, enter the data stream menu and add a stream for the page.
However, I see a warning stating that data collection has not been activated.
3.4. Setting the Tracking Code
To activate data collection, we need to register the Measurement ID obtained earlier. Let’s set the GA tracking code. Here, I received help from the front-end guru Lee Chang-Hee and Kim Min-Ji's blog.
Add the Google Analytics ID to blog-config.ts
. It should be written as follows. The GA tracking code is the code that starts with G-
found in Google Analytics.
Since this does not pose a security issue by being included in the git repo, it’s fine to write it in this file.
Then create a script component for GA tracking. I took inspiration from ambienxo.
The script that inserts the GA tracking code is simply wrapped with next/script
. Create src/components/GoogleAnalytics.tsx
and write the following.
Then add this component to _app.tsx
. It should be introduced here as it applies to all pages.
For those who wish to track view counts, please refer to this post. I failed due to the Cloudflare environment...
4. Attempting to Measure Views Using FirebaseDB
Referring to Real-time Blog View Count with NextJS and Firebase and a similar post, I attempted to measure view counts using a database.
In particular, using Google Analytics for view counts might lead to approximately 10% of counts being missed due to ad blockers. This is especially true for technology-related blogs, where most readers seem to use ad blockers.
4.1. Create a Firebase Project
Log into Firebase and go to the console. I logged in with my Google account. Then click 'Go to Console' in the top menu.
A screen will appear to create a project, so proceed to create a project.
I created a project named witch-blog-views
. Although I could attach Google Analytics, I already had an account made previously.
4.2. Create the Database
Once your project is created, create a database. From the build category in the left menu, select Realtime Database
.
On the resulting page, click Create Database
. Select a database located in the US and start in test mode.
Then, click the gear icon next to 'Project Overview' in the upper left menu to go to project settings. Navigate to the Service accounts
tab.
Click Generate new private key
and save the resulting json file securely.
4.3. Connect the Database
Now, let’s connect to the database. Install firebase-admin
.
Then create a .env.local
file and add it to .gitignore
, writing the following content.
This information can be found in the downloaded json file by looking for similarly-named keywords. The PRIVATE_KEY
value must be quoted.
Next, create src/lib/firebase.js
and write the following code to initialize the app and establish the connection.
Now that we have established a connection with the database, we need to create a function in the API route that accesses the database to increment the view count for each HTTP request. I borrowed some ideas from this. Create api/views/[slug].js
and write the following.
Run in development mode with npm run dev
. By sending a POST request to the /api/views/this-is-blog-slug
address, you can verify that the view increases in the Firebase Realtime Database. I used Postman for the POST request, but any other method would work too.
4.4. Cloudflare Environment Issues
Now, let’s build it on Cloudflare. Note that you can also run it locally as it would behave on Cloudflare by executing the following commands.
However, once I build it, I immediately get an error.
Afterward, I keep encountering errors indicating that the runtime needs to be set to Edge. This is because NextJS's SSR uses Node.js runtime by default, which is not supported on Cloudflare Pages.
Thus, add the following line to api/views/[slug].js
.
Now, a different error appears.
This strongly suggests that something unsupported in edge runtime is being used. There was a related GitHub issue stating that firebase-admin
requires full Node.js runtime, which is currently unsupported in Cloudflare workers.
Although a replacement package has been provided, it’s too difficult to implement, so I give up on this approach.
If anyone wants to use this method to add view counts, consider using this post as a reference to create a component that fetches view count data with SWR. But yet again, Cloudflare hindered me, so I will try a new method.
5. Measuring View Counts with Supabase
I decided to utilize Supabase, an open-source alternative to Firebase, which also supports edge functions.
5.1. Designing the View Count Counter
After much deliberation, let’s consider what is required for a view count counter when measuring views with a database. The features of the view count counter might include the following:
- When the page loads, it fetches and displays the page views from the serverless database.
- When the page loads, it increments the respective page view counter in the database.
This process should be done separately for each post. Thus, each entity in the database should contain the post title (as the post title is written in Korean, it will be used as the folder name containing the post) and its respective view count. The title should act as the primary key.
Communication with the database will use the API routes provided by Next.js, and the SWR library will be used to fetch the API route information. For a reference on how to use SWR with Next.js, check here.
Let’s attempt to set up the API route for fetching view counts.
Although the database can be edited directly (as Supabase DB is easily editable through the web), this doesn’t undermine the importance of the tracking itself, so I believe this is sufficient.
5.2. Create a Supabase Project
First, I’ll create a Supabase project. Visit Supabase, log in with GitHub, and create a new project. The official documentation provides a friendly explanation of using Supabase with Next.js.
In the Project Page, create a new project. From my observations of the pricing policy, it seems to be more favorable than Firebase’s free policy. It’s reminiscent of Cloudflare Pages being more advantageous than Vercel for deployments... Anyway, fill in the information and create the project, selecting a region in Korea.
Next, let’s create a table. In the SQL Editor, select your project and click Create table. Then enter the SQL below to create the views
table, with the slug as the primary key and adding an integer for the view count and a timestamp indicating when the view count was recorded.
The int4
used in the view count is a 4-byte integer in Supabase. Using int2
allows storage up to 32,767 but I used int4
hoping that my posts would exceed 2^15 views someday. Who knows, perhaps the blog would see over 2 billion views.
Next, I'll add a function to increase the view count using SQL.
Initialize the Supabase JavaScript client with the following command:
Then, write the following contents into the .env.local
file, replacing replace-me
with my project's URL and anon key. The project name and anon key can be found here.
Next, create src/lib/supabaseClient.js
with the following code.
Then add these environment variables to the Cloudflare Pages settings. Select the project you created under Workers and Pages, then add the SUPABASE_URL
and SUPABASE_KEY
in the Settings - Environment Variables
menu.
5.3. Fetching View Count
Let’s create an API route to fetch view counts. Although one might think of creating api/views/[slug].js
, there’s no such structure in the Cloudflare Pages deployment environment.
Cloudflare Pages supports only edge runtime for server components, which means all API routes will be treated as API endpoints rather than page routes. Therefore, dynamic API routes won't work. However, it is still necessary to fetch the data from the API routes, so let’s find a way to do this.
Testing one command to build in the Cloudflare environment step by step as follows:
We will use the supabase
object defined in src/lib/supabaseClient.js
to create a function that fetches view counts. I managed to create it while referring to the official documentation. This will return the view_count
from the views
table, fetching only the row where the slug
matches the function argument and using single
to retrieve a single object as a response.
Since the slug
acts as a primary key, the return value will either be none or one row, making the use of single
appropriate.
However, as mentioned earlier, dynamic API routes cannot be used in edge runtime. How will we pass the slug? By utilizing a query string in the GET request, as slugs are not particularly sensitive information.
We can then create api/view/index.ts
and write the following.
How to test this function? I used the /about
route for experimentation. I proceeded to create a component like this...
This is merely an intermediate iteration, hence I'll skip detailed explanations. Just experiment progressively using an unused route. The integration between SWR and Next.js can be referenced here.
5.4. Handling Errors in View Count Fetching
However, if a new user accesses a post whose view count doesn't yet exist, an error will arise. If the row corresponding to the slug does not exist, this will yield an error scenario. In testing, this resulted in data
being returned as null. Let’s resolve this issue.
We’ll create a function that attempts getViewCount
, and if an error occurs due to zero rows, it will insert the view count row for that slug.
The getViewCount
should return both data and error.
Next, we create a function called registerViewCount
that inserts a new row for a slug if it does not exist.
Now, let’s implement the fetchViewCount
function that utilizes these functionalities.
This function will then replace the former fetchViewCount
.
Now, let’s create a component within src/pages/posts/[category]/[slug]/index.tsx
to fetch the view count. This component will utilize useSWR
for data fetching.
The fetch will involve adding an appropriate query string to api/view
.
To pre-fetch initial data into all SWR hooks, we use the fallback option of SWRConfig
in getStaticProps
.
Wrap the ViewCounter
component in SWRConfig
, passing the fallback object to ensure it has access to the initial values.
Now, the ViewCounter
will always show the initial value from await fetchViewCount(params?.slug);
, and when it makes a request to the API route, it will also pass back the view count to the component. This allows consistent updates to be displayed in the ViewCounter
.
5.5. Aggregating View Counts
As per the current setup, every time a user opens a post, the view count increments by 1, but we still need to adjust for actual user interactions with posts. This can be handled in the ViewCounter
component's useEffect
.
Starting with restructuring the ViewCounter
component into src/components/viewCounter/index.tsx
to maintain functionality.
Next, create a function to increase the view count for the respective slug in src/lib/supabaseClient.js
, utilizing the previously defined increment function.
Update the index.ts
file to handle a POST request that increments the view count.
Update the ViewCounter
component to send a POST request to increment the view count when the component renders.
This implementation would increment the view count, but activating React's strict mode led to the view count increasing by 2. In strict mode, each component is rendered twice, thus causing double increments.
Disable React strict mode in next.config.js
. Note that Cloudflare Pages' Next.js builds do not support React strict mode as of now, but it's a sensible adjustment.
5.6. Returning to Vercel from Cloudflare...
I deployed this adjusted setup, and the view counts displayed correctly. However, the counts did not update in real-time. Why? Observing the developer tools' network tab showed that while fallback counts were working well, the real-time updates weren't happening.
SWR doesn’t seem to work effectively under Cloudflare. Dealing with a multitude of issues and trying various data-fetching methods, I eventually concluded that achieving real-time updates in edge runtime is nearly impossible, leading me back to using Vercel.
6. Redeploying to Vercel
6.1. Registering the Supabase Key
Having chosen to continue using Supabase, it is functional and a more generous free plan is available compared to Firebase.
Go into your Vercel project settings, and under Environment Variables, add the SUPABASE_URL
and SUPABASE_KEY
created earlier.
6.2. Rewriting the Files
The src/lib/supabaseClient.js
file does not require immediate changes.
The api/view/index.ts
can be rewritten more concisely using NextApiResponse
.
The ViewCounter
component will be simplified as follows.
With these updates, the view counts can be accurately fetched and displayed for each post.
7. Styling the ViewCounter
Let’s add some styling to the ViewCounter
. First, set the font size to 1.25rem. Since this is simple, I’ll skip detailing that.
Next, align it with the post date, adding a thin gray line between the date and view count.
Within src/pages/posts/[category]/[slug]/index.tsx
, structure the component around ViewCounter
like this.
Styling for each component can be added straightforwardly.
This approach aligns the view count with the date while allowing for real-time updates. It appears that not using fallback values is crucial for real-time functionality.
References
[Blog Links and Resources]
- https://curryyou.tistory.com/508
- https://mnxmnz.github.io/nextjs/google-analytics/
- https://ha-young.github.io/2020/gatsby/Add-Google-Analytics/
- _document.js - https://nextjs.org/docs/pages/building-your-application/routing/custom-document
- _app.js - https://nextjs.org/docs/pages/building-your-application/routing/custom-app
- https://dev.to/ahmedmohmd/difference-between-appjs-and-documentjs-files-in-nextjs-3ah2
- GA for view counts: https://arturocampos.dev/blog/nextjs-with-google-analytics
- https://nextjs.org/docs/messages/next-script-for-ga
- https://mariestarck.com/add-google-analytics-to-your-next-js-application-in-5-easy-steps/
- Real-time views: https://leerob.io/blog/real-time-post-views
- https://bepyan.github.io/blog/nextjs-blog/5-google-analytics
- Counting views with Firebase: https://andresrodriguez.dev/blog/count-blog-post-views-with-firebase
- API routes documentation: https://nextjs.org/docs/pages/building-your-application/routing/api-routes
- View counting with Supabase: https://dev.to/100lvlmaster/adding-view-count-to-your-nextjs-blog-55lj
- Using SWR with Next.js: https://swr.vercel.app/ko/docs/with-nextjs
- Supabase functions documentation: https://supabase.com/docs/reference/javascript/introduction
- Improving developer experience: https://supabase.com/blog/improved-dx
- General guide on page views with Supabase: https://codebycorey.com/blog/page-views-nextjs-supabase