- Web
- Game
どんぐりの森
街を歩いて葉っぱを拾い、加盟店のQRコードを読むとどんぐりがもらえる、無料の街歩きブラウザゲーム。

実店舗への来店をゲーム体験に変えることを目的に、個人で企画・開発して公開している無料のWebサービスです。利用者は地図を開いて周囲に落ちる葉っぱを拾い、葉っぱ10枚をどんぐり1個に交換します。加盟店のQRコードをレジで読み取るとどんぐりがもらえ(同じ店舗は1日1回まで)、まれに金のどんぐりが出ます。集めたどんぐり10個で木が1本育ち、歩いた街のかたちが自分の森として残る仕組みです。どんぐりは7日で毛虫に食べられる期限付きにして、「ゆでる」と7日延長、金のどんぐりで買う毛虫バリアで24時間保護と、継続して歩く動機を設計しました。アプリのインストールと会員登録を不要にするためPWAのブラウザアプリとして実装し、登録なしで遊び始めたあとにGoogleログインすればデータを引き継げるようにしています。店舗側は専用端末もレジ改修も不要で、QRコードを1枚置くだけ。加盟店の管理画面では今月の来店数・来店人数・初回と再訪の内訳が見られ、QRを置いた効果を数で確認できます。位置情報は葉っぱの表示と来店確認にだけ使い、移動の軌跡は保存せず直近1地点のみを位置偽装の判定用に保持しています。利用者と加盟店の相談先として、LINEオープンチャット「どんぐりの村」も運営しています。
「登録なしで遊べる」と「機種変更で引き継げる」を両立させた
街歩きサービスは、最初の登録画面で離脱されると何も始まりません。そこでどんぐりの森は、ボタン1つでゲストとして即プレイできる入口を用意しました。ゲストの持ち物は署名付きCookieに紐づけて端末側に保持しています。
ただしゲストのままでは機種変更で全部消えます。そのため、あとからGoogleログインするとゲスト時代に集めたどんぐり・葉っぱ・履歴がそのままアカウントへ移る引き継ぎAPIを用意しました。遊び始めるハードルを下げつつ、続けたくなった人だけがアカウントを作れば良い構成です。
この設計には落とし穴もあります。セッションの署名鍵はGoogleログインとゲストCookieの両方で共用しているため、鍵を差し替えると全員がログアウトし、ゲストで遊んでいた人のデータに触れなくなります。運用上いちばん壊してはいけない値として扱っています。
個人で運用し続けるための技術選定
個人開発の街歩きサービスは、利用者が増えたときに月額費用で潰れるのがいちばんありがちな終わり方です。そこで、お金とAPIキーが増えない組み合わせを選びました。
- データベースは Cloudflare D1。接続文字列を持たず、Worker に紐づくバインディング経由で読み書きするので、外部のDBサービスを契約せずCloudflareだけで完結する
- 地図は Leaflet と OpenStreetMap。地図APIのキー取得も従量課金もなく、表示回数を気にせず作れる
- QRの読み取りは jsQR でブラウザ内処理。読み取りのためにサーバーへ画像を送らない
- Next.js 15(App Router)+ React 19 + TypeScript を OpenNext 経由で Cloudflare Workers に載せ、配信も同じ基盤にまとめた
「集める」だけで終わらせないための仕掛け
歩いて集めるだけのアプリは、一度集め終わると開かなくなります。そこで、放っておくと持ち物が減る方向の仕組みを入れました。
どんぐりには毛虫がつき、放置すると食べられます。これが実質の有効期限です。1日1回「ゆでる」と期限が7日延び、まれに手に入る金のどんぐりを使えば24時間まるごと守れる毛虫バリアが買えます。集めたものを守る作業自体が、翌日もアプリを開く理由になります。
貯めた先も用意しました。葉っぱは10枚でどんぐり1個に交換でき、どんぐり10個ごとに自分の森に木が1本育ちます。レアな葉っぱ(🍂は2枚ぶん、🌸は3枚ぶん)があるので、同じ距離を歩いても引きの良し悪しが出ます。
加盟店側の画面まで含めて1人で作った
来店をゲームにする以上、遊ぶ人の画面だけでは製品になりません。店舗が自分で登録を申請し、運営の審査を通ると店頭用のQRを表示でき、何人が来たのかを確認できる加盟店向けの画面まで実装しています。
QRからのどんぐり獲得は1店舗につき1日1回に制限しました。同じ店の前で連続して読み取っても増えないため、店側から見ると「1日1人1回の来店」として数を読める形になっています。
2つの実装を1つに統合した
どんぐりの森は、最初にバニラJSで作った版と、あとからNext.jsで作り直した版の2つのリポジトリに分かれていました。機能を足すたびにどちらに入れるか迷う状態になったため、Next.js版を軸に統合し、何をどう寄せたかを統合メモとしてリポジトリに残しています。