Tfk.domain
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しかないページでも動的に数字を表示できるんですね〜。

今回は画像を作るのが面倒だったので文字で妥協。

所見

そもそもアクセスカウンターってなんであるんだろう?