ansub-profile-picture

Ansub Khan

Engineer & Designer

How to Add a View Counter in Next.js 14

June 21, 2024 (3mo ago)

100 views

In this tutorial, I'll walk through the process of how I added a view counter to my personal website using Next.js 14 and Upstash Redis. I also covered how to implement Partial Pre-Rendering to statically generate the blog posts for a smooth user experience.

Setup

Before we begin, ensure you have the following setup:

  • Upstash Redis account
  • Next.js version 14.2.1-canary.7
  • React version 18.2.0
  • Partial Pre-rendering enabled

You can also use an updated canary version of Next.js to get Partial Pre-Rendering.

What is Partial Pre-Rendering?

In simple terms, Partial Pre-Rendering is a technique that allows you to pre-render the static parts of your website while dynamically rendering the dynamic content once it becomes available. This approach offers the best of both worlds: fast initial page loads and seamless updates to dynamic content.

So in our case, we will be pre-rendering the blog posts while the view counter for each post will be dynamically rendered using data fetched from our Redis database. This way users can start reading the content they're interested in right away without having to wait for the entire page to load.

I will recommend watching this Video by Delba get a better understanding of how Partial Pre-Rendering works.

Enabling Partial Pre-Rendering

To enable Partial Pre-Rendering, you need to set the experimental.ppr option to true in your next.config.ts file.

Note that Partial Pre-Rendering is still in beta and may not work as expected in all cases. Since It's a personal project that's why I'm using the canary version of Next.js but didn't faced any issues with it.

/** @type {import('next').NextConfig} */
const nextConfig = {
  experimental: {
    ppr: true,
  },
};

module.exports = nextConfig;

Setting up Upstash

  1. Create an Upstash account, Free plan is very generous and gives 10K commands (writes/reads) per day.
  2. Create a new Redis database.
  3. Scroll down to the REST API section and click on the .env button to copy the environment variables.
UPSTASH_REDIS_REST_URL=""
UPSTASH_REDIS_REST_TOKEN=""

Now can now save these variables in your .env file.

Configuring Upstash

First, install the Upstash Redis package:

npm install @upstash/redis 

Next, configure Redis in your Next.js project, you can create a new file called lib/redis.ts (upto you) and add the following code:

//  /lib/redis.ts

import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: process.env.UPSTASH_REDIS_REST_URL!,
  token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});

export default redis;

Now our setup is complete, we can move on integrating the view counter into our Next.js app.

Implementing the View Counter

Reporting & Displaying Views

Create a server component to call the Database when a user visits a blog post:

// /app/blogs/[slug]/ShowViews.tsx

import redis from "@/lib/redis";
import crypto from "crypto";
import { headers } from "next/headers";


async function recordViewCount(slug: string) {
  const headersList = headers();
  const forwardedFor = headersList.get("x-forwarded-for");
  const realIp = headersList.get("x-real-ip");

  const ipSource = forwardedFor || realIp || "localhost";

  const ip = ipSource.split(",")[0].trim();

  const hashedIp = crypto.createHash("sha256").update(ip).digest("hex");

  const viewKey = ["pageviews", "blogs", slug].join(":");
  const ipViewKey = ["ip", hashedIp, "views", slug].join(":");

  const hasViewed = await redis.get(ipViewKey);

  let viewCount: number;

  if (!hasViewed) {
    const pipeline = redis.pipeline();
    pipeline.incr(viewKey);
    pipeline.set(ipViewKey, "1");
    await pipeline.exec();

    viewCount = (await redis.get<number>(viewKey)) ?? 0;
    return { message: "View Added", status: 202, views: viewCount };
  } else {
    viewCount = (await redis.get<number>(viewKey)) ?? 0;
    return { message: "Already viewed", status: 200, views: viewCount };
  }
}

const ShowViews = async ({ slug }: { slug: string }) => {
  const { views } = await recordViewCount(slug);

  return <div>{views} views</div>;
};

export default ShowViews;

Displaying View Count

Import ShowViews component in your [slug]/page.tsx file:

// /app/blogs/[slug]/page.tsx
import { getBlogPosts } from "@/app/db/blog";
import { notFound } from "next/navigation";
import { Suspense } from "react";
import ShowViews from "./ShowViews";
import { MDX } from "@/components/reusable/mdx";

// ...

export default function Blog({ params }) {
  // ...

  return (
    <section className="flex items-center justify-center w-full flex-col p-8">
      <div className="w-full max-w-3xl">
        <h1 className="title font-medium text-2xl md:text-4xl tracking-tighter font-heading">
          {post.metadata.title}
        </h1>
        <Suspense fallback={<div className="blur-sm">100 views</div>}>
          <ShowViews slug={post.slug} />
        </Suspense>
        {/* ... */}
      </div>
    </section>
  );
}

That's it! You have successfully implemented a view counter in your Next.js 14 application. The view count will be incremented each time a user visits a blog post, and the updated count will be displayed on the page.

Resources

Here are some useful resources to learn more about Partial Pre-Rendering:

Written

Visual

Subscribe To My Newsletter

Stay ahead of the curve with my monthly newsletter called Luminary. Receive valuable insights on the latest trends, techniques, and tools in web development and design.

© 2024 - Ansub Khan