学園祭のシムレース体験ブース向けの、時間枠制の予約Webアプリです。
- 来場者:
/bookで日付・時間枠を選び、さらに機体(スペック説明付き)を選んで予約 → 6桁の予約コードを発行。/reservationでコードから照会・キャンセルできる。 - 運営:
/adminで予約一覧の確認・受付チェックイン、/admin/settingsで機体(名前・スペック)の登録・時間枠・開催日を設定して枠を生成。 - お知らせ:
/admin/announcementsで登録したお知らせ(通常/重要)が、トップページ・予約確認ページ・受付案内画面(/display)に表示される。 - ページ編集:
/admin/pagesでトップページの「ご来場の流れ」を Markdown で編集できる(空で保存すると非表示)。
機体(シムレース機)はスペックが異なる前提で、1台ごとにRigとして個別に管理する。予約は「時間枠 × 機体」の組み合わせ単位(1予約=1機体=1名)で、枠ごとの空き台数は「稼働中の機体数 − その枠の予約数」から自動計算される。
- Next.js 16 (App Router) + TypeScript + Tailwind CSS
- Prisma 7 (driver adapter方式) + SQLite(開発時は
better-sqlite3アダプタ) - 管理者認証: 環境変数のパスワード + 署名付きJWTセッションCookie
npm install
npx prisma migrate deploy # 初回のみ(DBスキーマ作成)
npm run devhttp://localhost:3000 で起動します(ポートが使用中の場合は自動で別ポートになります)。
.env.example を .env にコピーして値を設定してください。
| 変数名 | 説明 |
|---|---|
DATABASE_URL |
DB接続文字列(開発時は file:./dev.db) |
ADMIN_PASSWORD |
管理画面 (/admin) のログインパスワード |
AUTH_SECRET |
管理セッションCookie署名用の秘密鍵。openssl rand -hex 32 などで生成 |
/admin/loginで管理者パスワードでログイン/admin/settingsの「機体管理」で各機体の名前・スペック(例: 「ハンドル型/3面モニター/VRなし」)を登録- 同じページの「開催設定」で1枠の長さ・営業時間・開催日(例:
2026-09-12,2026-09-13)を入力し「設定を保存」 - 「枠を生成」を押すと、開催日ごとに時間枠が自動作成される(既に枠がある日付はスキップされる)
- 当日は
/adminで予約一覧を確認し、来場者の受付時に「チェックイン」を押す - 機材トラブル等でその機体が使えなくなった場合は「機体管理」で当該機体を非稼働にする(予約履歴がある機体は削除できない仕様)。休憩時間などで時間枠ごと閉じたい場合は「時間枠の個別設定」で該当枠の「受付中」を外す
予約照会や受付の動作確認には、/admin の「サンプル予約」から「サンプル予約を生成」を押してください。
- 先に
/admin/settingsで稼働中の機体を登録し、現在または未来の時間枠を生成しておきます。 - 受付中で終了前の枠から、機体が空いている組み合わせを選び、1回につき最大5件の予約を作成します。空きが5件未満なら、その件数だけ作成します。
- 氏名は「サンプル予約 1」などになり、通常の予約と同じ形式の一意な6文字の予約コードを発行します。生成結果のコードから予約内容を開けます。
- サンプルも通常の予約と同様に枠を使用し、予約一覧・予約照会・呼び出し画面に反映されます。確認後は予約内容のページからキャンセルできます(受付済みなら、先に管理画面で受付を取り消します)。
- もう一度押すと、残りの空き枠に新しいサンプルを追加します。既存の予約は変更しません。
学園祭当日1〜2日だけ稼働させる用途なら、SQLiteのままローカルPCで動かし、Cloudflare Tunnelで外部公開するのが最も簡単。SQLiteはファイルベースなので、Vercelのようなサーバーレス環境とは違い、1台のマシンでプロセスが動き続けるこの構成とは相性が良い。
-
本番ビルドして起動する(
4000番ポートで待受)npm run build npm run start
-
cloudflaredをインストールする(例:sudo apt install cloudflared/ Mac はbrew install cloudflared) -
トンネルを起動して公開する
- お試し・一時利用(Cloudflareアカウント不要、
*.trycloudflare.comのランダムなURLが発行される):cloudflared tunnel --url http://localhost:4000
- 独自ドメイン等で固定URLにしたい場合は
cloudflared tunnel loginでCloudflareアカウントに認証後、Named Tunnelを作成する(詳細は cloudflared 公式ドキュメント参照)
- お試し・一時利用(Cloudflareアカウント不要、
-
発行されたURLが来場者向けの予約URLになる。同じURLに
/adminを付ければ管理画面にもアクセスできる
公開前に必ず確認すること
.envのADMIN_PASSWORDを、今設定されている簡易な値から推測されにくいものに変更する(外部公開する以上、総当たりされうる)AUTH_SECRETはランダムな値になっているか確認する(openssl rand -hex 32で再生成可)- ログインセッションCookieは本番モード (
NODE_ENV=production、npm run startで自動的にそうなる) ではSecure属性付きで発行される。Cloudflare TunnelはHTTPSで終端するのでトンネル経由のURLでは問題なく動作するが、トンネルをバイパスしてhttp://<LANのIP>:4000に直接アクセスすると、ブラウザがCookieを送らずログインが機能しない(想定通りの挙動) next startとcloudflared tunnelの両プロセスをイベント中ずっと起動したままにする必要がある(PCのスリープ・スクリーンロックでのスリープ移行に注意。pm2やtmux/screenでの常駐化を推奨)- 予約データは
dev.db1ファイルにしか存在しない。当日は定期的に別の場所へコピーしておくと、PCのトラブル時に復旧できる
方式Aの npm run build / npm run start の代わりに Docker で動かすこともできる。起動時に prisma migrate deploy が自動で実行される。DB は コンテナ内の /app/data/app.db に作られるので、ボリュームをマウントして永続化する。
docker build -t simrace-reserve .docker run -d --name simrace-reserve --restart unless-stopped -p 4000:4000 -v simrace-data:/app/data -e ADMIN_PASSWORD='推測されにくいパスワード' -e AUTH_SECRET="$(openssl rand -hex 32)" simrace-reserve- DB のバックアップは
docker cp simrace-reserve:/app/data/app.db ./backup.dbで取得できる - 既存の
dev.dbを引き継ぐ場合は、受付開始前にdocker cp ./dev.db simrace-reserve:/app/data/app.db→docker restart simrace-reserve(コピーしたファイルは root 所有になるが、起動時に自動で所有者を直すので再起動すれば書き込める) AUTH_SECRETを変えるとログイン中のセッションが無効になるだけで、予約データには影響しない
外部ホスティングに載せたい場合の代替案。Vercelのようなサーバーレス環境ではファイルが永続化されないため、Postgresへの切り替えが必須。また Prisma 7 はDBごとに専用の「ドライバーアダプタ」を使うため、以下の切り替え作業が必要。
-
Vercelにデプロイ後、プロジェクトの Storage タブから Postgres を追加する(Neon/Supabase等の無料枠でも可)
-
prisma/schema.prismaのdatasourceをprovider = "postgresql"に変更 -
Postgres用ドライバーアダプタを追加
npm install pg @prisma/adapter-pg
-
src/lib/db.tsのPrismaBetterSqlite3をPrismaPgに差し替えるimport { PrismaPg } from "@prisma/adapter-pg"; const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL });
-
ローカルで
npx prisma migrate devを実行し直してPostgres用のマイグレーションを再生成し、コミットする -
Vercelの環境変数に
DATABASE_URL(Postgres接続文字列)・ADMIN_PASSWORD・AUTH_SECRETを設定してデプロイ
- 同じ時間枠でも異なる機体であれば別々に予約できます
- 同じ枠・同じ機体への二重予約はDBトランザクションでブロックされます
- 予約キャンセル時は該当の枠・機体が再度予約可能に戻ります
- 予約履歴のある機体は削除できず、非稼働(isActive=false)への切り替えのみ可能です
- 管理画面 (
/admin/*,/api/admin/*) は未ログイン時に自動的にログイン画面へリダイレクト/401を返します
middleware.tsは Next.js 16 で非推奨表示が出ますが(proxy.tsへの移行案内)、現時点では動作に問題ありません。