perf(home): parallelize private visitor tracking

This commit is contained in:
2026-08-09 21:35:37 +07:00 Unverified
parent 1dc44e0215
commit c0429a8e8e
5 changed files with 87 additions and 27 deletions
+11 -27
View File
@@ -3,39 +3,23 @@ import { Card, CardHeader } from "@/components/ui/card";
import Image from "next/image"; import Image from "next/image";
import { getFollowerCounts } from "@/lib/followers"; import { getFollowerCounts } from "@/lib/followers";
import { LinkCard } from "@/components/LinkCard"; import { LinkCard } from "@/components/LinkCard";
import { getRedisClient } from "@/lib/redis";
import { headers } from "next/headers"; import { headers } from "next/headers";
import { connection } from "next/server";
import { Users } from "lucide-react"; import { Users } from "lucide-react";
import { Kbd } from "@/components/ui/kbd"; import { Kbd } from "@/components/ui/kbd";
import Background from "@/public/background/space.webp"; import Background from "@/public/background/space.webp";
import { trackUniqueVisitor } from "@/lib/visitor/unique-visitors";
export default async function Home() { export default async function Home() {
await connection(); const visitorCountPromise = headers()
.then(trackUniqueVisitor)
const { counts } = await getFollowerCounts(); .catch((error) => {
console.error("Failed to track visitor:", error);
const headersList = await headers(); return 0;
const ip = headersList.get("x-forwarded-for") || headersList.get("x-real-ip") || "unknown"; });
const [{ counts }, visitorCount] = await Promise.all([
let visitorCount = 0; getFollowerCounts(),
try { visitorCountPromise,
const redis = await getRedisClient(); ]);
if (ip !== "unknown") {
const added = await redis.sadd("erika:unique_visitors", ip);
if (added > 0) {
// New visitor — refresh the count
visitorCount = await redis.scard("erika:unique_visitors");
} else {
// Existing visitor — use cached count or fetch
visitorCount = await redis.scard("erika:unique_visitors");
}
} else {
visitorCount = await redis.scard("erika:unique_visitors");
}
} catch (e) {
console.error("Failed to track visitor:", e);
}
// Use configured profile data. // Use configured profile data.
const profileName = config.profile.name; const profileName = config.profile.name;
+8
View File
@@ -13,6 +13,14 @@ export function getCanonicalUrl() {
return requiredEnvironmentUrl("NEXTAUTH_URL"); return requiredEnvironmentUrl("NEXTAUTH_URL");
} }
export function getVisitorTrackingConfig() {
return {
secret: readFeatureEnvironment("Visitor tracking", [
"NEXTAUTH_SECRET",
] as const).NEXTAUTH_SECRET,
};
}
export function getDiscordServerConfig() { export function getDiscordServerConfig() {
const config = readFeatureEnvironment("Discord", [ const config = readFeatureEnvironment("Discord", [
"DISCORD_BOT_TOKEN", "DISCORD_BOT_TOKEN",
+31
View File
@@ -0,0 +1,31 @@
import { describe, expect, test } from "bun:test";
import {
getClientAddress,
hashClientAddress,
} from "./visitor-identifiers";
describe("privacy-preserving visitor tracking", () => {
test("uses the first valid forwarded address", () => {
const headers = new Headers({
"x-forwarded-for": "203.0.113.9, 10.0.0.1",
"x-real-ip": "192.0.2.2",
});
expect(getClientAddress(headers)).toBe("203.0.113.9");
});
test("rejects untrusted non-IP identifiers", () => {
expect(
getClientAddress(new Headers({ "x-forwarded-for": "unknown" })),
).toBeNull();
});
test("creates a stable identifier without retaining the address", () => {
const address = "203.0.113.9";
const identifier = hashClientAddress(address, "test-secret");
expect(identifier).toBe(hashClientAddress(address, "test-secret"));
expect(identifier).not.toContain(address);
expect(identifier).toHaveLength(64);
});
});
+22
View File
@@ -0,0 +1,22 @@
import "server-only";
import { getVisitorTrackingConfig } from "@/lib/config/server";
import { getRedisClient } from "@/lib/redis";
import {
getClientAddress,
hashClientAddress,
} from "@/lib/visitor/visitor-identifiers";
const VISITOR_SET_KEY = "erika:unique_visitors:v2";
export async function trackUniqueVisitor(headers: Headers) {
const client = await getRedisClient();
const address = getClientAddress(headers);
if (address) {
const { secret } = getVisitorTrackingConfig();
await client.sadd(VISITOR_SET_KEY, hashClientAddress(address, secret));
}
return client.scard(VISITOR_SET_KEY);
}
+15
View File
@@ -0,0 +1,15 @@
import { createHmac } from "node:crypto";
import { isIP } from "node:net";
export function getClientAddress(headers: Headers) {
const forwardedAddress = headers
.get("x-forwarded-for")
?.split(",", 1)[0]
?.trim();
const address = forwardedAddress || headers.get("x-real-ip")?.trim();
return address && isIP(address) ? address : null;
}
export function hashClientAddress(address: string, secret: string) {
return createHmac("sha256", secret).update(address).digest("hex");
}