AIを組み込んだ、家族向けの日本旅行プラン作成アプリを作りました

公開日
2026年7月11日
更新日
2026年9月16日
著者
Jacob Lloyd — プロジェクト完了後、AIの支援を受けて執筆
読了時間
約14分で読めます

かんたんに言うと: 家族の日本旅行用に、小さなウェブサイトを作りました。日ごとの旅程、天気、地図などが掲載されており、電波がない状態でもスマートフォンで利用できます。また、旅行に関する質問に答えたり、旅程を調整したりできる小さなAIアシスタントボタンも備えています。この記事では、多くのAIの助けを借りてどのようにしてこのサイトを作ったか、何がうまくいったか、何が失敗したか、そして不安定だったデザインツールの出力結果を、ウェブ上で安全に編集できる形にどうやって仕上げたかについて、率直にお話しします。整備済みのサンプルファイルもダウンロードでき、どんなブラウザでも開くことが可能です。

家族で日本へ旅行した際、全員が手元に旅程表を持っていられるようにしたかったのです。毎日の予定、タップ一つで見られる地図、天気情報、そして時差ボケ状態で駅にいるとつい忘れがちな細かな情報も含めてです。そこで私たち家族専用に小さなウェブアプリを作りました。これはPINコードで保護されたプライベートな旅程表で、日ごとのスケジュールやリアルタイムの天気情報、オフライン利用も可能です。また私らしい仕様として、AIアシスタントも内蔵しており、旅行に関する質問に答えたり、ごく小さな編集も行えます。

ここでは実際の開発の経緯を正直にお伝えします。うまくいった部分、予想外に問題が生じた部分、そしてもろく壊れやすかったデザインツールのエクスポートデータが、どうやって公開用ウェブアプリへと変わったかを説明します。最後には内容を適切に処理したサンプルファイルも用意してあり、どんなブラウザでもダウンロードして開くことができます。

要約

  • 概要:モバイルファーストの旅程表アプリ。日付ごとのスケジュール、リアルタイム天気情報、通貨・タイムゾーン補助機能、オフライン利用やインストールも可能で、AIアシスタント機能も備わっています。
  • 核となる考え方:見た目は美しいが不安定だったデザインツールのエクスポートデータを、小さなデータファイルだけで管理し、ビルド処理でアプリを再生成する仕組みにしたことで、安全に編集できるようになりました。
  • セキュリティ:クライアント側のPINコードは見せかけに過ぎず、実際の保護機能はサーバー側のセッション管理と回数制限付きロックアウト処理です。
  • AI:自宅で運用するローカルモデルを使用しており、質問への回答やごく小さな編集のみを行うように制限しています。
  • 繰り返し得た教訓:変更が正しく反映されたか確認するには、実際のブラウザでアプリをレンダリングして見るしかない。ファイルが「きっと大丈夫だろう」と信じてはいけません。
旅程表アプリの概要画面:『Family Trip 2026』という見出しの下に「日本6日間」と記載され、現在の天気情報や服装のアドバイスが表示される。さらに東京タワー観光、鎌倉の寺院とビーチ、休養日、ポケモン巡り、新宿での家族向け楽しみ方、帰国日という6つの日付カードがあり、それぞれに天気予報や持ち物のヒントが記載されている
概要画面:旅行全体が一画面にまとめられており、見出し、リアルタイム天気情報、服装アドバイス、そして日ごとのカードにそれぞれ天気予報が記載されている。(スクリーンショット内の地名は実際とは異なる架空の名称です。)

始まり:見た目だけは完璧なブラックボックス

AIデザインツールで見た目を設計し、エクスポートしました。結果として得られたのは単一のHTMLファイルだけでした。これが完全な React アプリであり、フォントや画像もすべて埋め込まれており、サイズは約 14 MB にもなりました。見た目は素晴らしく、ダブルクリックするだけで動作しました。

しかしこれは完全なブラックボックスでもありました。ミニファイされたアプリ全体が巨大な1つのデータ塊となっており、旅程情報もその中に埋もれていました。手作業で旅程を変更しようとすると、機械生成されたコードを直接いじる必要がありました。一度だけなら問題ありませんが、家族旅行では毎日予定が変わるため、それは地獄のような作業になります。

そこで最初に下した本当の決断は「エクスポートデータを編集するのはやめる」ということでした。

すべてを可能にしたたった一つのアイデア

14 MBものバンドルファイルに手を触れる代わりに、全旅程情報を小さくて読みやすい データファイル へ移行しました。content.json というファイルに日付、訪問先、天気に関するメモ、各スポットの写真や費用情報などを記載したのです。そして短い ビルド処理 がこのファイルを読み込み、元のエクスポートデータからアプリを再生成します。

この仕組みによってプロジェクトの性質が一変しました。「新しい訪問先を追加する」や「時間を修正する」といった作業は、人間でもAIアシスタントでもデータファイルに2行程度書き加えるだけで安全に行え、その後ビルド処理を実行すれば済みます。もろく壊れやすかった14 MBのアプリ本体には一切手を触れなくなったのです。その後私が追加したウィジェット――リアルタイム天気カード、時差対応の時計、通貨換算機、写真や駐車情報付きの展開可能な訪問先カードなどもすべて、このデータ駆動型ビルド処理によって生成されています。

旅程表アプリで「ポケモン巡りの日」が展開された様子:列車バッジや天気予報、服装アドバイスが表示され、「移動手段」欄には具体的な電車路線が記載されている。さらに拠点情報やポケモンセンター・メガ東京、ピカチュウスイーツカフェ、とんかつ和幸、山下本気うどんといった訪問先がそれぞれ展開可能なカードとして示されている
ある一日の詳細情報:列車情報や拠点情報、各訪問先が展開可能なカードとなっている。これらはすべて content.json に記載されたデータからレンダリングされており、手作業で書き込まれたHTMLコードは一切存在しない。

すべてのウィジェットを失わせた落とし穴:起動時にアプリが再生成される

最も多くの時間を浪費させた問題点をここに記しておきます。これを知っていればあなたは同じ失敗をしなくて済みます。

エクスポートされたアプリは起動時にページ全体を再生成します。静的なHTML内に自分でマークアップを追加した場合――時計やバナーなど何でも――それらはアプリがマウントされる瞬間にすべて消去されてしまいます。要素のIDが消え、getElementById では永遠に null しか返さなくなり、ウィジェットは静かに画面に現れなくなります。さらに悪いことに、保存されたHTMLファイルを検索して「確認」しようとすると、マークアップがちゃんと残っているように見えるため「問題ないはず」と思い込んでしまいます。しかし実際には画面には反映されません。

これを解決するには2つのルールがあります:

  1. ページ全体ではなくアプリ自身のテンプレート内に挿入する。 アプリがUIを構築する場所にウィジェットを追加すれば、アプリの一部としてレンダリングされ起動後も残り続けます。
  2. document 上でイベント委譲を用いて挙動を制御する。 タブ切り替え時にアプリはセクション全体を再マウントするため、ボタンに直接紐付けたリスナーは次回レンダリング時に消えてしまいます。一方で document 上で「何がクリックされたか」を判定する委譲型リスナーならどんな状況でも生き残ります。

これに関連して私が絶対の法則としているテスト上のルールもあります:ファイル内検索やHTTP 200応答の確認だけでウェブUIを検証してはいけません。 実際に(ヘッドレスでも可な)ブラウザでアプリを起動させた後、結果を目で確認するべきです。この種のバグを検知できるのはこの方法だけであり、私自身もテンプレート内に配置し直すまで見えなかった通貨換算機など、いくつもの問題をこれによって発見できました。

「自己完結型」という言葉は当てにならない

エクスポートデータには「単一ファイルで自己完結型」と記載されていました。しかし実際はそうではありませんでした。実行時に公開CDNからUIフレームワークを取得しており、そのURLもコード内の断片として組み込まれていたのです。そのためテキスト検索だけではCDNの存在が見つからず、ファイルはオフラインでも動作するように見えましたが実際はそうではありませんでした。厳格なセキュリティポリシーを持つウェブホスト上ではこの隠された取得処理がブロックされ、アプリ全体が起動できなくなりました。私のノートPCには同様の制限がなかったため問題なく動作し、本番サーバーに配置されるまでその欠陥は気付かれませんでした。

対策としてフレームワークを自前でホストし(アプリと共にバンドルして外部からの取得を不要にしました)、ダウンロード用サンプルではすべてをファイル内に直接埋め込みました。これにより本当にディスクから開くだけでインターネットなしでも動作する単一ファイルが完成したのです。この章から実用的な教訓を一つ挙げるとすれば、「自分のマシンでは動く」と「実際のウェブサーバーのセキュリティ下でも問題ない」は別物だということです。後者も必ずテストすべきです。

保護機能:見せかけのPINと本物のロック

最初のバージョンではページ内のJavaScriptでPINコードによる保護を行っていました。これはセキュリティ・シアターに過ぎません。旅程表全体がソースコード内に存在しているため、開発者ツールを開けるだけで誰でもコード入力なしに内容を閲覧できてしまうからです。ちょっとしたサプライズパーティーを他人から隠す程度なら問題ありませんが、本当に守るべき情報には不十分です。

本番版では保護機能をサーバー側へ移行しました。PINコードを送信するとサーバーがそれを検証してセッションを開始し、その後で初めて旅程表を提供します。さらに IPアドレスごとに回数制限付きロックアウト機能 も備えており、誤入力が数回続くとそのアドレスからのアクセスは段階的に長時間遮断されるため、6桁のコードを機械的に試すことも不可能になります。

限界についても正直に述べておきます。偽装しないことが大切だからです。回数制限のみがかけられた6桁のPINコードは家族向けの低リスクな旅程表には適していますが、銀行レベルの安全を保証するものではありません。本当に機密性の高い情報を守る場合は長いパスフレーズや正式なログイン処理を用いるべきです。ドアの向こう側に何があるかを考えて適切な鍵を選ぶことが大切です。

AIアシスタントの利用制限

面白い点として、独自のタブ内で動作する小さなアシスタントが存在します。私はこれを「Yoshi」と名付けました。家族向けプライベート版では、旅行に関する質問に答えたり(「水族館へ行く日の予定は?」「何を持っていけばいい?」)、スケジュールに対して限定的な編集を行うことも可能です。重要なのは、このアシスタントが編集するのは安全なデータファイルのみであり、脆弱なアプリ本体には一切触れない点です。また、日付の削除はできず、航空券や予約情報も保護されているため、どんなチャットメッセージからも変更できません。

特筆すべきは、Yoshiが自分の環境で動作するローカルモデルを利用している点です。有料のクラウドAPIではなく、私自身のハードウェア上で動く小規模なAIであり、保護されたリレー経由でアクセスされます。これ自体が大きなテーマですので(ローカルにホストしたチャットボットを安全に公開する方法や、一般向けハードウェア上での動作速度の問題など)、別途記事としてまとめました:
自分のハードウェア上で公開用チャットボットを運用する方法。

なお、以下に掲載のサンプルについて: アシスタント用タブは存在しますが、このサンプルではあえて機能を無効化してあります。つまり、どんなAIとも連携せず、回答も行いません。ご自身で利用する場合は、自分の環境にあるローカルモデルを指定することになります。これは意図的な仕様です。公開用ファイルに勝手に稼働中のAIへ接続する機能を含めるつもりはありません。

オフラインでも利用可能、インストールも可能

旅行時の実際の状況を考慮し、最後に行った改良点は次の通りです。ホテルのWi-Fiが不安定だったり、機内ではデータ通信ができなかったり、ローミングサービスが不安定だったりします。そこで本アプリはPWAとしてインストール可能にしました。ホーム画面に追加すれば通常のアプリのように起動でき、一度データを読み込めばオフラインでも全旅程情報を閲覧可能となります。私が特に注意した点は、ゲート処理が成功した後でのみ旅程情報がキャッシュされることです。そうすることで、オフライン時のキャッシュによってゲート処理が回避される事態を防いでいます。オンライン時には常にサーバー側で再検証を行い、オフライン時にはスマートフォン内のデータを信頼します。家族旅行計画としては適切な仕様だと言えます。

(Service Workerには注意点があります。修正版を配布した後でも、古いキャッシュ版が長期間ページを提供し続ける可能性があるのです。そのため、常に更新版のWorkerを配布し、古いファイルは削除せずに残すようにしています。これによりブラウザが変更内容を正しく認識するようになります。)

実際の利用状況を再現したテスト

家族が旅行中にこのアプリを実際に使っていたため、私はあらゆる変更を本番環境へのデプロイと同様に扱いました。つまり、ビルドを行い、ヘッドレスブラウザ上で実際のページを表示させ、タブやボタン操作も実施した上で初めて配布していました。旅行中にもこの方法で小さな修正をリアルタイムに適用しました。例えば、現在の旅行日を示すバッジ(東京時間基準で表示され、閲覧者の端末時刻とは異なります)、天気情報の更新、レイアウト上の細かい不具合などです。すべて実際のブラウザ上で検証した上で適用していました。これは前述の「起動時に再生成される仕組み」と同様の教訓でもあります。ファイルが存在するだけでは機能は動作しないということです。

試してみましょう — サンプルをダウンロード

以下に掲載のファイルは、本アプリの一般向けサンプル版です。すべての名前、日付、住所、詳細情報は架空のデモ用データであり、7日間の東京旅行計画を想定しています。index.htmlを任意のブラウザで開くだけで利用可能です。インストールやサーバー、アカウントも不要です。オフラインでも動作しますが、天気情報や地図リンクのみがインターネットに接続されます。

ただし、2点ほど注意事項があります。第一に、ファイルサイズが約14MBにもなります。これはアプリ本体、フォント、画像データすべてをインライン化したためで、依存関係なしで動作する仕様となっています。その結果、初回起動時はやや遅くなる可能性があります。本番環境向けにはこれらを分割・圧縮する処理が必要です。第二に、前述の通りAIアシスタント機能は無効化されています。タブは開きますが、その旨が表示されるだけです。

ご自身でも実装してみましょう

冒頭にある「ご自身で実装してみる」という項目は単なるスローガンではありません。本記事は、LLMを活用して任意のプロジェクトを自分用にカスタマイズする方法で述べたように、AIアシスタントへ直接渡せる形で作成されています。要点を要約すると以下の通りです。

  1. 計画内容をデータファイルに記載する。 日付、訪問先、メモ、天気情報などを一つの読みやすいファイルとして管理します。これが唯一の正規情報源となります。
  2. そのデータからアプリを生成する。 決定論的なビルド処理によりデータがUIへ変換されます。こうすることで編集は常にデータ側で行われ、生成されたコードには手を加えなくて済みます。
  3. デザインツールからのエクスポートを利用する場合は、起動処理を尊重する。 アプリのテンプレート内に必要な要素を挿入し、イベントも委譲させる形で処理します。また外部から取得されるコンテンツも自身の環境でホストするようにします。
  4. ゲート処理はページ側ではなくサーバー側で行う。 セッション管理と一定時間内の試行回数制限を組み合わせる方が、JavaScriptによるPIN認証よりも確実です。
  5. 旅行用アプリならオフライン優先にする。 ゲート処理が成功した後でのみキャッシュを行うようにします。
  6. 起動後にブラウザ上で検証する。 「ファイルが存在する」だけでは不十分で、実際の画面表示を確認する必要があります。

注意点のまとめ

  • 起動時にDOMが再生成されるため、ページへ手動で挿入した内容は失われます。アプリのテンプレート内に挿入し、document上でイベントを委譲させるようにしてください。
  • 「自己完結型」と謳っていてもそうでない場合がある — エクスポート版の中には実行時にフレームワークを読み込むものもあります。必ず自身の環境でホストし、ローカル環境だけでなく本番サーバーのセキュリティヘッダー下でもテストしてください。
  • クライアント側のPIN認証は見せかけに過ぎない — 実際の内容はページ内に存在するため、必ずサーバー側でゲート処理を行うべきです。
  • 6桁のPINも単なる試行回数制限にすぎず、絶対的な安全を保証しない — 重要度の低い用途には適しても、機密性が高い情報には不向きです。
  • Service Workerは積極的にキャッシュする — 修正版を配布する際は更新版のWorkerを用意してください。古いファイルを削除しても登録が解除されないためです。
  • 「ファイルが存在する」=「機能が動作している」ではない — 必ず実際に画面を表示させて確認する必要があります。

関連記事:自分のハードウェア上で公開用チャットボットを運用する方法、LLMを活用して任意のプロジェクトを自分用にカスタマイズする方法、小型PC上で運用するAIエージェント群、Claude Codeを活用してWordPressを静的サイト化する方法。

ダウンロード

個人利用は無料です。役に立ったら、コーヒーをおごってもらえると嬉しいです。


← AIとローカルLLMをもっと見る