QRコード/リンクのアクセスを記録し、リダイレクトするためのセルフホスト型トラッキングツールです。ポスターやチラシに印刷したQRコードがいつ・どこでスキャンされたかを記録し、管理画面で確認できます。
trackable-links-oss(MITライセンス)をベースに、NUTFesの文化祭運用向けにリブランド・改修したものです。
- プロジェクト単位で、同じリダイレクト先URLを持つQRコードをグループ化(例: チラシA、ポスターBなど)
- 各QRコードには設置場所ラベルを付けられる(例: 「正門」「2号館前」)。印刷直後のQRコードは場所未設定の状態で、初回スキャン時にパスコード入力付きのフォームが表示され、設置した人がその場で場所を登録できる
- 場所登録後は、スキャンのたびにアクセスログ(日時・User-Agent・IP)を記録し、301リダイレクトで指定URLへ遷移
- 管理画面でプロジェクトごとのスキャン数、QRコード管理(作成/印刷/削除)、時間帯×場所別の分析を確認可能
QRコードをスキャン ──▶ GET /?id={qrId} (packages/api) ──▶ ログ記録 ──▶ 301リダイレクト
│
▼
D1データベース (Projects, QRCodes, AccessLogs)
▲
│
管理画面 (packages/web) ──▶ /projects/*, /auth/* (Bearer JWT)
このリポジトリはpnpmワークスペースで、2つの独立したアプリケーションから成ります。デプロイ先が異なる点に注意してください。
packages/api— Hono製のWorker。QRコードのリダイレクト、場所設定、認証付きのプロジェクト/QRコード管理APIを提供。データはCloudflare D1(Drizzle ORM経由)に保存。 → Cloudflare Workersにデプロイ済み(下記「本番API」参照)packages/web— React + Vite + Tailwindの管理画面(SPA)。fetchでAPIと通信する。 → Cloudflareにはデプロイせず、自作サーバーでホストする方針
| 項目 | 値 |
|---|---|
| Worker URL | https://trackinglink.nutfes-nutmeg9488.workers.dev |
| Cloudflare Workerプロジェクト名 | trackinglink |
| D1データベース名 | trackinglink-db |
| デプロイ方法 | Cloudflareダッシュボード連携(Workers Builds)。mainブランチにpushすると自動デプロイ |
APIの設定・認証情報は以下の2種類に分かれています。
packages/api/wrangler.jsonc(リポジトリにコミット): Worker名、D1バインディング(database_id含む)、ALLOWED_ORIGINSなど。database_idはリソースの識別子であり、それ単体では中身にアクセスできないため許容してコミットしています。- Cloudflareダッシュボード側でのみ設定(リポジトリには含まれない):
account_id→ Worker の Settings → Build → Environment variables にCLOUDFLARE_ACCOUNT_IDとして設定JWT_SECRET/ADMIN_PASSWORD/LOCATION_SETUP_PASSCODE→ Worker の Settings → Variables and Secrets に Secret(暗号化)として設定
Webは静的サイト(SPA)としてビルドされ、Cloudflareとは別のサーバーで配信します。ローカル開発でも自作サーバーでの本番運用でも、同じ本番API(https://trackinglink.nutfes-nutmeg9488.workers.dev)を参照する構成にしています。
- Node.js 20以上
- pnpm(未インストールの場合は
npx pnpm@9.15.0 <コマンド>のようにnpx経由でも実行できます)
git clone git@github.com:NUTFes/TrackingLink.git
cd TrackingLink
pnpm installワークスペース全体(packages/apiとpackages/web)の依存関係が一括でインストールされます。
pnpm --filter @tracking-link/web devhttp://localhost:5173 で管理画面が起動し、packages/web/.env.local に設定された本番API(VITE_API_URL)へ接続します。ADMIN_PASSWORD(Cloudflareに設定した値)でログインしてください。
.env.localが無い場合は以下のように作成してください。
echo "VITE_API_URL=https://trackinglink.nutfes-nutmeg9488.workers.dev" > packages/web/.env.local本番ビルド時は packages/web/.env.production の値(既にリポジトリにコミット済み、VITE_API_URL=https://trackinglink.nutfes-nutmeg9488.workers.dev)が自動的に使われます。
pnpm --filter @tracking-link/web buildpackages/web/dist/ に静的ファイル一式が出力されるので、これを自作サーバー上の任意のWebサーバー(nginx、Apache、Node製の静的サーバーなど)で配信してください。
注意: このアプリはReact Routerによるクライアントサイドルーティングを使用したSPAです。存在しないパス(/linksなど)へ直接アクセス・リロードされた場合に index.html を返すよう、SPAフォールバック設定をWebサーバー側で行ってください(例: nginxならtry_files $uri /index.html;)。
自作サーバーのドメインが決まったら、packages/api/wrangler.jsonc の ALLOWED_ORIGINS に追加してcommit・pushしてください(pushすると自動で本番APIに反映されます)。
これを設定しないと、自作サーバー上のWebからAPIへのリクエストがブラウザ側でブロックされます(現時点ではドメイン未定のため、localhost分のみ許可されています)。
外部の認証プロバイダは使わず、単一の共有パスワード(ADMIN_PASSWORD)方式です。POST /auth/loginでパスワードを渡すと、24時間有効なHS256 JWTが発行され、以降のリクエストでAuthorization: Bearer <token>として使用します。
認証ロジックは差し替えやすいよう1箇所にまとまっています(packages/api/src/auth/):
auth/
├── types.ts # Verifier型 + Bindings/AuthUser/HonoEnv —全体が依存する契約
├── local.ts # 組み込みの単一パスワード認証(signLocalSession/verifyLocalSession)
├── middleware.ts # createAuthMiddleware(verifier) — VerifierをHonoミドルウェア化
└── index.ts # 上記の再エクスポート
将来Auth0やClerk、独自SSOなどに差し替える場合は、(token, env) => Promise<{ sub, permissions } | null> というシグネチャの関数を実装し、createAuthMiddlewareに渡すだけで済みます。
4つの独立したビットの組み合わせです(packages/api/src/permissions.ts):
| ビット | 値 | 権限内容 |
|---|---|---|
TRACKING_LINK_VIEW |
1 | プロジェクト・QRコードの一覧参照 |
TRACKING_LINK_EDIT |
2 | プロジェクト・QRコードの作成、自分が作ったQRコードの削除 |
TRACKING_LINK_ANALYTICS |
4 | 分析画面の閲覧 |
TRACKING_LINK_DELETE |
8 | 任意のプロジェクト・QRコードの削除 |
現在の単一管理者ログインは、常にこの4つ全てを付与します。
/projects/* 配下はすべて Authorization: Bearer <token> が必要です。
| Method | Path | 認証 | 説明 |
|---|---|---|---|
| GET | /?id={qrId} |
不要 | QRコードスキャン。アクセスを記録してリダイレクト、または場所未設定なら設定フォームを表示 |
| GET | /view/:qrId |
不要 | QRコードの情報を人間が読める形で表示するページ |
| POST | /api/set-location |
パスコード | QRコードの設置場所を初めて登録する |
| POST | /api/edit-location/:qrId |
パスコード | QRコードの設置場所を再登録する |
| POST | /auth/login |
不要 | ADMIN_PASSWORDをセッショントークンと交換する |
| GET | /auth/me |
Bearer | 現在のセッション情報を取得 |
| GET | /projects |
Bearer | プロジェクト一覧(ページネーション、スキャン数付き) |
| POST | /projects |
Bearer | プロジェクトを作成 |
| GET | /projects/:id |
Bearer | プロジェクト詳細 |
| PUT | /projects/:id |
Bearer | プロジェクトを更新 |
| DELETE | /projects/:id |
Bearer + DELETE権限 |
プロジェクトを削除(QRコードも連動して削除) |
| GET | /projects/:id/qrcodes |
Bearer | プロジェクト内のQRコード一覧(ページネーション) |
| POST | /projects/:id/qrcodes |
Bearer | QRコードを作成 |
| GET | /projects/:id/access-stats |
Bearer | 時間帯×場所別のスキャン数集計 |
| GET | /projects/:id/access-logs |
Bearer | 生のスキャンログ(ページネーション) |
| GET | /projects/qrcodes |
Bearer | 全プロジェクト横断のQRコード一覧 |
| GET | /projects/qrcodes/:id |
Bearer | QRコード単体の情報 |
| PUT | /projects/qrcodes/:id |
Bearer | QRコードの設置場所を更新 |
| DELETE | /projects/qrcodes/:id |
Bearer + DELETE権限/自分が作成したもの |
QRコードを削除(スキャンログも削除) |
元になった内部ツールの一部機能は、組織固有のインフラに依存していたため今回のプロジェクトには含まれていません。
- LINE Bot / LIFF QRスキャナー — LINE内からQRコードをスキャンするWebhook連携
- レシートプリンター連携 — Epson ESC/POSプリンターへ直接QRラベルを印刷する機能
必要になれば追加実装も可能です。
MIT — LICENSE参照。