2025-12-20
ウェブサイトにアクセスカウンターを実装する
いにしえのウェブサイトで目にされたアクセスカウンターを、ここでも実装してみたかった。実際作ったものはこのサイトの下の方をご覧いただきたい。
技術的な話
アクセスカウンターは意外にも簡単に自前で実装できた。Cloudflare D1 databaseが速くて無料枠が巨大なので便利。Drizzle ORMを使うと型付けもしっかりできて良い。
また、高速に動かすためにastroのserver islandでDBと通信した。REST APIとかでバックエンドと通信するより速そう。
実際のコンポーネントは以下のような形に実装した。onConflictDoUpdate を使うことで、レコードの作成と更新をアトミックに行っている。
---
import { getDB } from "@db/index";
import { metrics } from "@db/schema";
import { sql } from "drizzle-orm";
let totalCount = 0;
let monthlyCount = 0;
try {
const env = Astro.locals.runtime?.env;
if (env) {
const db = getDB(env);
const now = new Date();
// '2025-12' のような月切り替え用のキーを作成
const currentMonthKey = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}`;
const keysToUpdate = ["total", currentMonthKey];
// Upsert: 新規ならInsert、存在すればIncrement
// DrizzleのonConflictDoUpdateを使ってアトミックに処理
const updates = keysToUpdate.map((key) => {
return db
.insert(metrics)
.values({ key, count: 1, lastUpdated: now })
.onConflictDoUpdate({
target: metrics.key,
set: {
count: sql`${metrics.count} + 1`,
lastUpdated: now,
},
})
.returning();
});
// 並列にリクエストを投げる
const results = await Promise.all(updates);
// 結果をパース
results.forEach((rows) => {
const row = rows[0];
if (row) {
if (row.key === "total") totalCount = row.count;
if (row.key === currentMonthKey) monthlyCount = row.count;
}
});
}
} catch (e) {
console.error(e);
}
---
<div>
<p>
あなたは {totalCount} 人目のお客様です
</p>
<p>
今月: {monthlyCount} 人
</p>
</div>
ちなみに、いにしえのアクセスカウンターはimgタグから外部リンクにアクセスし、外部のサービスがカウンターを更新して、対応する番号の画像を返す仕組みだった(らしい)。そうするとhtmlしかないページでも動的に数字を表示できるんですね〜。
今回は画像を作るのが面倒だったので文字で妥協。
所見
そもそもアクセスカウンターってなんであるんだろう?