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
- Create an Upstash account, Free plan is very generous and gives 10K commands (writes/reads) per day.
- Create a new Redis database.
- 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