Redis
 Computer >> コンピューター >  >> プログラミング >> Redis

Upstash RedisでNext.jsブログに閲覧数カウンターを実装する方法

Leeさんのブログでは、すべての記事に閲覧数が表示されており、それに触発されて、自分のサイトにも同じ機能を導入したいと思うようになりました。私のプロジェクトでもNext.js 13の新しいApp Routerを使用していますが、ページビューの保存にはMySQLデータベースではなくUpstash Redisを採用します。

以下は、今回構築する機能のイメージです。トップページの各カードには、それぞれの記事の閲覧数が表示されます。

Upstash RedisでNext.jsブログに閲覧数カウンターを実装する方法

なぜRedisなのか?

Redisには、重複排除とカウンターのインクリメントを簡単に実現できる、優れたコマンドが2つ標準で用意されています。

より正確なカウンターを実現するため、カウンターの増加にはデバウンス(間引き)処理を加えます。ユーザーがページを再読み込みしても、カウンターは1回だけしか増えないようにするのです。これはRedisのSETコマンドを使えば非常に簡単に実装できます。SETコマンドには、キーがまだ存在しない場合にのみ値を設定するNXオプションと、指定した秒数後にキーを失効させるEXオプションがあります。この2つを組み合わせれば、一定期間内に同一ユーザーがカウンターを何度も増やすことを防げます。

もう1つのコマンドはINCRです。指定したキーの値を1だけアトミックに増加させます。

Redisのセットアップ

Upstashでのデータベース作成はとても簡単で、無料プランでも1日10,000リクエストまで利用できます。公式サイトから新しいデータベースを作成でき、所要時間はほんの数秒です。作成後はページを下へスクロールし、REST接続情報を.env.localファイルにコピーしておきましょう。

.env.local

UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN=

Next.jsでの実装

Redisデータベースの準備が整ったので、カウンターの実装に取り掛かりましょう。まずは@upstash/redisをインストールします。

pnpm add @upstash/redis

ページビューを保存するには、「APIルート」と「クライアントコンポーネント」の2つの要素が必要です。まずはAPIルートから始めます。

/api/incr.ts

Upstashと@upstash/redisはVercelのEdge Functionsと互換性があるため、まず必要なモジュールをすべてインポートし、redisをセットアップして、ランタイムをedgeに設定します。

新しいファイル/api/incr.tsを作成し、以下のコードを追加してください。

/api/incr.ts

import { NextRequest, NextResponse } from "next/server";
 
import { Redis } from "@upstash/redis";
 
const redis = Redis.fromEnv();
 
export const config = {
 runtime: "edge",
};

次に、リクエストボディでslugなどの識別子を受け取ります。存在しない場合はステータスコード400を返します。

/api/incr.ts

export default async function incr(req: NextRequest): Promise<NextResponse> {
 const body = await req.json();
 const slug = body.slug as string | undefined;
 if (!slug) {
 return new NextResponse("Slug not found", { status: 400 });
 }
 
 // ここに後続の処理を追加
}

続いて、ユーザーのIPアドレスを取得します。req.ipプロパティを使えば簡単に取得できます。取得したIPアドレスは、SHA-256アルゴリズムでハッシュ化してからデータベースに保存します。こうすることで、セキュリティ上の懸念になり得るIPアドレスそのものを保存せずに済みます。

/api/incr.ts

const ip = req.ip;
// IPをハッシュ化し、16進数の文字列に変換
const buf = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(ip));
const hash = Array.from(new Uint8Array(buf))
 .map((b) => b.toString(16).padStart(2, "0"))
 .join("");

ここで、先ほど紹介した1つ目のRedisコマンドを活用します。SETNXEXオプション付きで使えば、特定のIPアドレスが直近24時間以内にそのページを閲覧済みかどうかを簡単に判定できます。

/api/incr.ts

const isNew = await redis.set(["deduplicate", hash, slug].join(":"), true, {
 nx: true,
 ex: 24 * 60 * 60,
});
if (!isNew) {
 new NextResponse(null, { status: 202 });
}

最後に、対象のslugに紐づくカウンターを増加させます。ここではINCRコマンドを使用します。

/api/incr.ts

await redis.incr(["pageviews", "projects", slug].join(":"));
return new NextResponse(null, { status: 202 });

参考までに、完全なコードは元記事から確認できます。

/app/[slug]/view.tsx

続いて、マウントされたタイミングで先ほど作成したAPIルートへリクエストを送信する、シンプルなクライアントコンポーネントを作成します。このコンポーネントは、トラッキングしたい任意のページに埋め込めるのが便利なポイントです。

/app/[slug]/view.tsx

"use client";
 
import { useEffect } from "react";
 
export const ReportView: React.FC<{ slug: string }> = ({ slug }) => {
 useEffect(() => {
 fetch("/api/incr", {
 method: "POST",
 headers: {
 "Content-Type": "application/json",
 },
 body: JSON.stringify({ slug }),
 });
 }, [slug]);
 
 return null;
};

/app/[slug]/page.tsx

最後に、トラッキング対象のページにReportViewコンポーネントを組み込みます。

/app/[slug]/page.tsx

import { ReportView } from "./view";
 
type Props = {
 params: {
 slug: string;
 };
};
 
export default function Page({ params }: Props) {
 return (
 <div>
 <ReportView slug={params.slug} />
 {/* ここにページのコンテンツを記述 */}
 </div>
 );
}

これ以降、/app/[slug]へのすべてのアクセスが記録され、直近24時間以内のユニーク訪問者ごとにカウンターが増加していきます。

閲覧数の表示

閲覧数を記録できるようになりましたが、せっかくなので公開して表示したいところです。その方法を見ていきましょう。

閲覧数を表示するには、データベースから値を取得する必要があります。これにはGETコマンドを使用します。あわせて、ページコンポーネントにrevalidateの設定を追加しておくと効果的です。こうすることで、リクエストのたびではなく60秒ごとにページが再検証され、パフォーマンス面でも有利になります。

/app/[slug]/page.tsx

type Props = {
 params: {
 slug: string;
 };
};
 
export const revalidate = 60
 
export default function Page({ params }: Props) {
 const views = await redis.get<number>(["pageviews", "projects", params.slug].join(":")) ?? 0
 
 return ...
}

まとめ

この実装の完全なサンプルはchronark.comで公開されています。ソースコードもGitHubで入手可能です。特に参考になるのは以下の部分です。

  • APIルート
  • トラッキング用コンポーネント
  • 閲覧数の読み込み処理

より高度なページビュー分析に興味がある方は、ぜひTwitterやDiscordでフィードバックをお寄せください。

  1. ClerkとUpstash Redisで構築する、安全かつスケーラブルなセッションストアの作り方

    Redisの主要なユースケースのひとつが、Webアプリケーションにおいてリクエスト間の状態を維持するためのユーザーセッションの保存・管理です。実装方法は複数あり、近年登場したサーバーレスツールの中には、簡単にデプロイできる選択肢を提供しているものもあります。 ユーザーセッションデータの管理は、さまざまなビジネスアプリケーションにとって非常に重要です。たとえば、パーソナライゼーションプラットフォームでは、Redisを使ってユーザーの操作履歴や設定を保存し、カスタマイズされたコンテンツや商品のおすすめを提供しています。ゲーム業界では、プレイヤー同士のやり取りをリアルタイムに追跡することで、快適なマ

  2. Firebase・Upstash・SvelteKitで作るオープンソースJIRAクローン開発ガイド

    本記事では、Upstash、SvelteKit、Firebase Storageを活用して、Jiraのカンバンボードに代わるオープンソースアプリケーションを構築した方法について詳しく解説します。 使用する技術スタック SvelteKit(UIおよびAPIルート) Upstash(CRUD操作) Tailwind CSS(スタイリング) Firebase Storage(画像やPDFなどのアセット保存) Auth.jsによるSvelteKit Auth 事前に必要なもの データベース作成用のUpstashアカウント ストレージコンテナ作成用のFirebaseアカウント OAuthクレデン