In this story, I will show you how fast you can add a clap function to your blog posts. We use Upstash Redis as our serverless DB, Next.js 14 for the App, and Azure AD B2C with Next-Auth so that only logged-in users can clap.
Here is the GitHub repo with the full code.
What is Upstash Redis?
Redis from Upstash is a serverless database service optimized for Redis. It provides scalable and low-latency data storage and offers a pay-per-request pricing model, making it cost-effective for applications with variable workloads. Upstash handles the infrastructure management, allowing developers to focus on building their applications without worrying about server maintenance.
The platform ensures high availability and durability by automatically handling replication and failover. Additionally, Upstash integrates seamlessly with serverless environments, supporting various programming languages and frameworks.
Its ease of use and efficient performance make it suitable for real-time applications like caching, session management, and analytics.
For our project, we will use the generous free plan.

New NPM Package
Let’s install the new Redis NPM package.
npm i @upstash/redisUpstash Account Creation
Open https://console.upstash.com/login, log in with your GitHub, Google, or Amazon account, and create a new Redis Database.

Then select the previously created Redis DB and click on the first tab, “Details”, so that you can copy the Endpoint and Password (Token)

Insert the copied information into your .env.local
# Redis Upstash
UPSTASH_REDIS_REST_URL="https://xxxx.upstash.io"
UPSTASH_REDIS_REST_TOKEN="xxxxxxxx"Api Route for Redis Operations
Then we create a new Api route within our project.
// app/api/claps/route.ts
import { NextRequest, NextResponse } from "next/server";
import redis from "../../../lib/redis";
export async function POST(request: NextRequest) {
const { slug } = await request.json();
if (!slug) {
return NextResponse.json({ error: "Slug is required" }, { status: 400 });
}
const claps = await redis.incr(`claps:${slug}`);
return NextResponse.json({ claps });
}
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const slug = searchParams.get("slug");
if (!slug) {
return NextResponse.json({ error: "Slug is required" }, { status: 400 });
}
const claps = await redis.get<number>(`claps:${slug}`);
return NextResponse.json({ claps: claps || 0 });
}New Redis Lib File
Now we need a new lib file (src/lib/redis.ts)
// 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;New Clap Component
It’s time for the creation of our new Clap Component. Since we limit the Clapping to logged-in users, we must import the useSession from the next-auth/react package.
"use client";
import { useState, useEffect } from "react";
import type { LocaleTypes } from "@/app/i18n/settings";
import { useTranslation } from "@/app/i18n/client";
import { useParams } from "next/navigation";
import { useSession } from "next-auth/react";
interface ClapButtonProps {
slug: string | undefined;
}
export default function ClapButton({ slug }: ClapButtonProps) {
const [claps, setClaps] = useState<number>(0);
const locale = useParams()?.locale as LocaleTypes;
const { t } = useTranslation(locale, "common");
const { data: session } = useSession();
const disabled = !session;
useEffect(() => {
const fetchClaps = async () => {
const res = await fetch(`/api/claps?slug=${slug}`);
const data = await res.json();
setClaps(data.claps);
};
fetchClaps();
}, [slug]);
const handleClap = async () => {
const res = await fetch("/api/claps", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ slug }),
});
const data = await res.json();
setClaps(data.claps);
};
return (
<div>
{(session && (
<div className="pb-4 text-base">{t("claps.clapText")}</div>
)) || <div className="pb-4 text-base">{t("claps.clapLogin")}</div>}
{/* <div className="pb-4 text-base">{t("clapButton")}</div> */}
<button
disabled={disabled}
className="text-2xl p-2 dark:bg-gray-500 rounded-lg dark:text-white bg-gray-200 text-gray-600 w-20"
onClick={handleClap}
>
👏 {claps}
</button>
</div>
);
}As the last step, we will import the new component into our page.tsx and pass the “slug” to the component because it’s the key we need for the data storage.
// Claps
import ClapButton from "@/components/contentful/ClapButton.component";
<Container className="max-w-5xl mt-8">
<ClapButton slug={blogPost.slug || ""} />
</Container>Final Result
Here is a screenshot of the final result

Cloudapp-dev, and before you leave us
Thank you for reading until the end. Before you go:




