Lấy cảm hứng từ blog của Lee, nơi mỗi bài đăng trên blog đều hiển thị số lượt xem mà nó có, tôi muốn làm điều gì đó tương tự cho trang của mình. Tôi cũng đang sử dụng Next.js 13 với bộ định tuyến ứng dụng mới nhưng thay vì lưu trữ lượt xem trang trong cơ sở dữ liệu mysql, tôi sẽ sử dụng Upstash Redis.
Đây là ví dụ về những gì chúng tôi sẽ xây dựng. Mỗi thẻ trên trang chủ sẽ hiển thị số lượt xem mà thẻ đó có.

Tại sao lại là Redis?
Redis đã có sẵn 2 lệnh tuyệt vời giúp việc loại bỏ trùng lặp và tăng bộ đếm trở nên đơn giản hơn.
Để có được bộ đếm chính xác hơn, tôi muốn loại bỏ mức tăng của bộ đếm. Nếu người dùng làm mới trang, bộ đếm chỉ được tăng một lần. Chúng ta có thể thực hiện việc này thực sự dễ dàng với SET của Redis lệnh. Nó có NX tùy chọn sẽ chỉ đặt khóa nếu nó chưa tồn tại và EX tùy chọn sẽ hết hạn khóa sau một khoảng thời gian nhất định. Bằng cách kết hợp cả hai tùy chọn này, chúng tôi có thể đảm bảo một người dùng không tăng gấp nhiều lần trong một khung thời gian nhất định.
Lệnh thứ hai là INCR sẽ tăng một khóa đã cho lên 1 đơn vị.
Thiết lập Redis
Thiết lập cơ sở dữ liệu trên Upstash thật dễ dàng và bao gồm 10 nghìn yêu cầu mỗi ngày miễn phí! Bạn có thể tạo cơ sở dữ liệu mới tại đây. Nó thực sự chỉ mất một vài giây. Sau đó cuộn xuống và sao chép REST bí mật kết nối tới .env.local của bạn tập tin:
UPSTASH_REDIS_REST_URL=
UPSTASH_REDIS_REST_TOKEN= Next.js
Bây giờ chúng ta đã có cơ sở dữ liệu Redis, chúng ta có thể bắt đầu triển khai bộ đếm. Trước tiên chúng ta cần cài đặt @upstash/redis:
pnpm add @upstash/redis Để lưu trữ lượt xem trang, chúng ta cần hai thành phần. Một tuyến api và thành phần aclient. Hãy bắt đầu với lộ trình api.
/api/incr.ts
Upstash và @upstash/redis tương thích với các chức năng biên của Vercel, vì vậy trước hết, chúng tôi sẽ nhập mọi thứ chúng tôi cần, thiết lập lại và định cấu hình thời gian chạy thành edge .
Tạo một tệp mới /api/incr.ts và thêm đoạn mã sau:
import { NextRequest, NextResponse } from "next/server";
import { Redis } from "@upstash/redis";
const redis = Redis.fromEnv();
export const config = {
runtime: "edge",
};
Tiếp theo, chúng tôi sẽ yêu cầu chuyển slug hoặc mã định danh tương tự vào nội dung yêu cầu. Nếu nó không xuất hiện, chúng tôi sẽ trả về 400 mã trạng thái.
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 });
}
// more to come here
}
Sau đó chúng ta cũng cần lấy địa chỉ IP của người dùng. Chúng ta có thể làm điều này bằng cách sử dụngreq.ip tài sản. Chúng tôi sẽ băm địa chỉ IP bằng SHA-256 thuật toán và lưu trữ nó trong cơ sở dữ liệu. Bằng cách này, chúng tôi không phải lưu trữ địa chỉ IP trực tiếp, điều này có thể gây lo ngại về bảo mật.
const ip = req.ip;
// Hash the IP and turn it into a hex string
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("");
Bây giờ hãy sử dụng lệnh redis đầu tiên được đề cập ở trên. Sử dụng SET cùng vớiNX và EX cung cấp cho chúng tôi một cách dễ dàng để kiểm tra xem một địa chỉ IP cụ thể có đang xem một trang trong vòng 24 giờ qua hay không:
const isNew = await redis.set(["deduplicate", hash, slug].join(":"), true, {
nx: true,
ex: 24 * 60 * 60,
});
if (!isNew) {
new NextResponse(null, { status: 202 });
}
Điều cuối cùng cần làm là tăng bộ đếm cho slug đã cho. Chúng ta sẽ sử dụng INCR lệnh cho việc này:
await redis.incr(["pageviews", "projects", slug].join(":"));
return new NextResponse(null, { status: 202 }); Để tham khảo, bạn có thể tìm thấy mã hoàn chỉnh tại đây
/app/[slug]/view.tsx
Tiếp theo, hãy tạo một thành phần máy khách nhỏ để gửi yêu cầu đến tuyến api mà chúng ta vừa tạo bất cứ khi nào nó được gắn kết. Sau đó, thành phần này có thể được nhúng vào bất kỳ trang nào mà chúng tôi muốn theo dõi.
/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
Điều cuối cùng chúng ta cần làm là thêm ReportView thành phần của trang chúng tôi muốn theo dõi:
import { ReportView } from "./view";
type Props = {
params: {
slug: string;
};
};
export default function Page({ params }: Props) {
return (
<div>
<ReportView slug={params.slug} />
{/* Add your page content here */}
</div>
);
}
Từ nay trở đi tất cả các lượt truy cập vào /app/[slug] sẽ được theo dõi và bộ đếm sẽ tăng lên đối với mỗi khách truy cập trong 24 giờ qua.
Hiển thị lượt xem
Theo dõi lượt xem là một việc tốt nhưng chúng tôi cũng muốn hiển thị chúng một cách công khai. Hãy xem chúng ta có thể làm điều đó như thế nào.
Để hiển thị số lượt xem, chúng ta cần tra cứu chúng từ cơ sở dữ liệu. Chúng ta có thể làm điều này bằng cách sử dụng GET lệnh. Chúng ta cũng nên thêm revalidate config vào thành phần trang để trang được xác thực lại sau mỗi 60 giây chứ không phải cho mọi yêu cầu.
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 ...
} Lời cuối cùng
Hãy xem ví dụ đầy đủ về điều này tại chronark.com. Mã có sẵn trên GitHub. Cụ thể, đây là các bit có liên quan:
- tuyến đường api
- thành phần theo dõi
- đang tải số lượt xem
Nếu bạn quan tâm đến số liệu phân tích lượt xem trang khác, vui lòng cho chúng tôi biết trên Twitter hoặc Discord.