Skip to content

Commit 6561fd1

Browse files
Use next/after to increment views
1 parent 6eaf0d8 commit 6561fd1

File tree

6 files changed

+82
-115
lines changed

6 files changed

+82
-115
lines changed

app/_components/views.tsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { db } from "@/_db/connection";
2+
import { post } from "@/_db/schema";
3+
import { Ratelimit } from "@upstash/ratelimit";
4+
import { Redis } from "@upstash/redis";
5+
import { eq } from "drizzle-orm";
6+
import { revalidatePath } from "next/cache";
7+
import { headers } from "next/headers";
8+
import { unstable_after as after } from "next/server";
9+
import { Suspense } from "react";
10+
11+
const ratelimit = new Ratelimit({
12+
redis: Redis.fromEnv(),
13+
limiter: Ratelimit.slidingWindow(1, "60 s"),
14+
analytics: true,
15+
});
16+
17+
type ViewsProps = {
18+
slug: string;
19+
};
20+
21+
const Views = async ({ slug }: ViewsProps) => {
22+
const results = await db.select().from(post).where(eq(post.slug, slug));
23+
const views = results[0]?.views ?? 0;
24+
25+
return <span>{views} views</span>;
26+
};
27+
28+
type ViewsRootProps = {
29+
slug: string;
30+
incrementOnMount?: boolean;
31+
};
32+
33+
const ViewsRoot = ({ slug, incrementOnMount = false }: ViewsRootProps) => {
34+
if (incrementOnMount) {
35+
after(async () => {
36+
let ip = "";
37+
38+
const FALLBACK_IP_ADDRESS = "0.0.0.0";
39+
const forwardedFor = headers().get("x-forwarded-for");
40+
41+
if (forwardedFor) {
42+
ip = forwardedFor.split(",")[0] ?? FALLBACK_IP_ADDRESS;
43+
} else {
44+
ip = headers().get("x-real-ip") ?? FALLBACK_IP_ADDRESS;
45+
}
46+
47+
const { success } = await ratelimit.limit(`${ip}-${slug}`);
48+
49+
if (!success) {
50+
return;
51+
}
52+
53+
// Check if row exists
54+
const results = await db.select().from(post).where(eq(post.slug, slug));
55+
const result = results[0];
56+
if (!result) {
57+
// Create the record
58+
await db.insert(post).values({
59+
slug,
60+
views: 1,
61+
});
62+
} else {
63+
// Update the record
64+
await db
65+
.update(post)
66+
.set({ views: result.views + 1 })
67+
.where(eq(post.slug, slug));
68+
}
69+
70+
revalidatePath("/blog", "layout");
71+
});
72+
}
73+
74+
return (
75+
<Suspense fallback={<span className="invisible">0 views</span>}>
76+
<Views slug={slug} />
77+
</Suspense>
78+
);
79+
};
80+
81+
export default ViewsRoot;

app/_components/views/actions.ts

Lines changed: 0 additions & 55 deletions
This file was deleted.

app/_components/views/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

app/_components/views/views-incrementer.tsx

Lines changed: 0 additions & 24 deletions
This file was deleted.

app/_components/views/views.tsx

Lines changed: 0 additions & 35 deletions
This file was deleted.

next.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const nextConfig = {
99
experimental: {
1010
reactCompiler: true,
1111
ppr: true,
12+
after: true,
1213
},
1314
reactStrictMode: true,
1415
images: {

0 commit comments

Comments
 (0)