「動く」と「届く」は、別物です
ここまで2回、嘘の設計と答えの守り方を書いてきました。どちらも「中身」のお話でした。今回は土台——このゲームを、誰でも触れる場所にどう置いたか、のお話を一緒に見ていきましょう。
個人開発をしていると、ある段階で必ずこの壁にぶつかります。自分のPCでは完璧に動く。でも、それを「誰でも、いつでも使える」状態にするのは、まったく別の山なんですね。動くものをつくるのと、届くものにするのとでは、必要な手間がぜんぜん違う。あなたも、手元では動いていたものを公開しようとして、急に勝手が変わって戸惑ったこと、ありませんか。
出発点は、手作りの構成
最初のクリシンクエストは、思いきりシンプルな構成でした。
- サーバーは、フレームワークを使わない素のNode.js(自分で1から書いたもの)
- データベースは、SQLite——ファイル1個で完結する、軽量な仕組み
- 画面は、素のHTMLとJavaScript
これは試作には最高です。速いし、軽いし、全部が手の内にある。仕組みを理解しながらつくるには、これ以上ない構成でした。
でも「本番」となると話が変わってきます。24時間落ちないこと、複数の人が同時に使えること、データが消えないこと、直したらすぐ反映できること——こうした“運用”の部分が、手作り構成では一気に重くなるんです。そして個人開発では、この運用こそが最大のボトルネックになりがちです。つくる時間より、保守する時間に押しつぶされてしまう。心当たり、ありませんか。
なぜ、引っ越しが必要だったのか
公開先に選んだのは Vercel というサービスなのですが、ここに最初の落とし穴がありました。
Vercel のような今どきのホスティングは、サーバーレスという仕組みで動きます。ざっくり言うと、「リクエストが来るたびに、使い捨ての小さな実行環境がパッと立ち上がり、仕事を終えると消える」という仕組みです。常に同じ1台が動き続けているわけではないんですね。
ここで SQLite が詰まってしまいます。SQLite は「サーバーのディスクにあるファイル1個」にデータを書く仕組みです。ところが、実行環境が毎回使い捨てだと、書き込んだファイルもろとも消えてしまう。これでは記録が残りませんよね。
つまり、必要なのは「どの実行環境からでも、ネットワーク越しにつなげる、ちゃんと残るデータベース」でした。それが Postgres(を提供する Supabase)だったんです。引っ越しは好みの問題ではなく、土台の都合だったんですね。
選んだ土台:Vercel と Supabase
そこで、自分で運用しない方向に舵を切りました。専門の会社が面倒を見てくれるサービス(マネージドサービス)に乗せる、という判断です。
- Vercel:公開とホスティングを担います。コードを更新して送るだけで、自動で本番に反映されます。
- Supabase:ログイン(認証)とデータベース(Postgres)を担います。アカウント管理やデータ保管を任せられます。
あわせて、バラバラだった構成も一本化しました。以前は「素のサーバー+別の画面配信」を内部でつなぎ合わせていたのですが、これをひとつのアプリ(Next.js)に統合し、30本あったサーバー側の処理もその中に畳み込みました。部品が減るほど、壊れる場所も減りますからね。
個人開発で「自分で全部やる」は、一見かっこいいですよね。でも、続かないんです。持たない勇気——運用を手放し、つくることに集中する。これが、結果的にいちばん効いた判断でした。
いちばんの山:データベースの引っ越し
移行で最大の難所が、データベースを SQLite から Postgres へ 移すことでした。
両者は同じ「SQL」を話すのですが、方言が違います。日付の書き方、数の数え方、使える関数の名前——細かいところが、いちいち食い違うんです。とはいえ、これまで書いてきた大量のコードを全部書き直すのは、現実的じゃありませんよね。
そこで取った作戦が、“互換アダプタ”でした。
古いコードがデータベースを呼ぶときの「呼び方」は、そのまま変えません。その裏側だけを、こっそりPostgres用に差し替える薄い層を一枚かませます。壁のスイッチにたとえるなら、スイッチの位置も押し方もこれまで通り。でも壁の裏では、配線も電源もそっくり別物に引き直している——そんな発想です。コードから見た「呼び方」が変わらないので、アプリ全体を作り直さずに、土台だけを入れ替えられました。
このアダプタが、裏で地味な変換をいくつもこなしてくれています。たとえば、SQLite と Postgres では「ここに値を差し込む」という記号の書き方が違うので、それを自動で書き換えます。返ってくる日付の形もそろえます。呼ぶ側からは何も変わって見えないのに、中ではきちんと翻訳が走っている——よくできた通訳のような層なんですね。
同期から非同期へ:型チェッカーに探させます
地味ですが大きかったのが、「同期」から「非同期」への切り替えでした。
SQLite は、結果がその場ですぐ返ってきます(同期)。一方、ネットワーク越しの Postgres は、「ちょっと待ってね」と一拍おいて返ってきます(非同期)。プログラム上は、待つべき場所すべてに「待つ」という印(await)を付けて回らないといけません。数百か所のうち、一つでも付け忘れると、データが届く前に処理が進んでしまってバグになります。
これを目視で探すのは、無理がありますよね。そこで、型チェッカー——コードの型の不一致を見つけてくれる道具——に「待ち忘れ」を機械的に洗い出させました。人間が勘で探すのではなく、道具に「ここが変だ」と指摘させる。退屈な全数チェックは、人より機械のほうがずっと得意なんです。
方言の段差を、ひとつずつ埋めます
それでも、細かな食い違いは手で埋めるしかありませんでした。たとえば——
- 列の名前:Postgres は引用符で囲まないと名前を勝手に小文字化してしまいます。大文字混じりの名前が、知らぬ間に別物になってしまうんですね。
- 件数の合計:合計や件数が、数ではなく「文字」として返ってくることがあります。そのまま計算すると、おかしな結果になってしまいます。
- 関数名:「2つのうち大きいほうを返す」関数の名前が、そもそも違います。
- 日付の表記:空っぽの日付の扱いや、日時の整形のしかたがそろいません。
どれも派手さはまるでありません。けれど、一つ取りこぼすと表示が崩れてしまいます。地味な段差を、つまずく前に一段ずつ埋めていく——移行の現実は、その繰り返しでした。
ローカルでも、本番と同じDBで
もうひとつ効いたのが、手元のPCでも本番とそっくりな環境を再現したことでした。
専用のツールを使って、自分のPCの中に本物の Postgres を立て、そこで開発します。本番に送る前に「同じ土俵」で試せるので、「ローカルでは動いたのに本番で壊れる」という、いちばん消耗する事故を減らせるんですね。データベースの変更も、思いつきで直接いじるのではなく、変更を1枚ずつのファイルとして記録して積み上げます。あとから「いつ何を変えたか」をたどれるようにしておくわけです。
テストという、命綱
これら全部を支えてくれたのが、テストでした。
「前と同じ入力で、前と同じ結果が返るか」を自動でチェックし続け、それが全部緑(成功)であることを確かめながら、土台を入れ替えていきます。すでに動いているシステムの土台を入れ替えるのは、本来とても怖い作業ですよね。それでも、命綱があれば踏み込めます。正しさを機械に見張らせるから、安心して大胆になれる。テストは、慎重さのためではなく、むしろ大胆さのための道具だったんだと思います。
認証は、自前でつくりません
最後にもう一つの判断です。ログインのしくみを、自分で作るのをやめました。
パスワードの保管は、事故が起きれば被害が大きく、正しくやるのは想像以上に難しいものです。ここは迷わず Supabase の認証に任せました。自分で抱えるほど危ないものは、専門家に預ける。これも「持たない」判断のひとつですね。(認証まわりの設計は、また別の回でくわしくお話しします。)
おわりに:手放すほど、軽くなります
個人開発というと「全部ひとりで作る」イメージがあるかもしれません。でも実際にやってみて学んだのは、逆だったんです。手放せるものを手放すほど、つくることに集中できる。運用はマネージドに、認証は専門サービスに、移行コストは互換アダプタに、退屈な確認は型チェッカーとテストに——そして実装そのものは、AIエージェントたちに。預けられるものを預けて、自分は「中身」だけに向き合う。
この移行も、手を動かしたのは私ではありません。「互換アダプタでいく」という方針を決めたのが私で、何百か所もの方言の書き換えやawaitの付け回しは、AIエージェントたちが手分けして進めてくれました。私の仕事は、どこをAIに任せ、どこは自分でテストして確かめるかの線引きをし続けること。コードを一行も書かなくても、土台は載せ替えられる——ディレクションとは、そういう仕事なんですね。もしあなたが個人開発で「全部ひとりで」と力んでいるなら、まず一つだけ手放してみるところから始めてみませんか。
こうしてクリシンクエストは、ようやく「動く」から「届く」へと一歩進みました。
——ところが、本番に出してすぐ、奇妙なことが起きたんです。なぜか、表示がやたら遅い。同じコード、同じデータベースなのに。次回は、その犯人を突き止めた話——「8秒が0.3秒になった」たった1行の設定について書きます。一緒に見ていきましょう。
— クリシンクエスト開発記・第3回