AIを使ったアプリを作っていると、どうしても避けて通れない問題がある。 それが、AIの生成時間だ。
チャットのように短い返答を返すだけならまだしも、ある程度まとまった結果を生成するアプリでは、ユーザーに数秒から十数秒、場合によってはそれ以上待ってもらうことがある。
私は現在、Tabidea というAI旅行プランナーを個人開発している。 行き先や日数、好みなどを入力すると、AIが旅行プランを作ってくれるWebアプリだ。
日本と世界の美しい風景、文化、食べ物を通じて、新しい旅の発見をお届けする旅行ブログ。
tabide.ai技術スタックとしては、フロントエンドに Next.js / React / TypeScript を使っている。 AIによる旅程生成には Gemini API を利用し、旅程生成後にスポット情報や移動の現実性を補うために、Google Maps API などの外部APIも組み合わせる構成を考えている。
また、認証やユーザーデータの管理には Supabase を使っている。 つまりTabideaは、単にLLMへプロンプトを投げて文章を返すだけのアプリではなく、AI生成・外部API・ユーザー状態・UI表示を組み合わせて成立するWebアプリだ。
最初は、「AIに条件を渡せば、いい感じの旅程を返してくれるだろう」と考えていた。 ただ、実際に作ってみると、旅行プランナーでは単に文章を生成するだけでは足りなかった。
旅程として成立させるためには、たとえば次のようなことを考える必要がある。
- その場所が本当に存在するか
- スポット同士の距離感が現実的か
- 移動時間に無理がないか
- 営業時間や定休日と矛盾していないか
- 朝・昼・夜の流れとして自然か
- ユーザーの希望条件に合っているか
つまり、旅行プランの生成は「それっぽい文章を返す」だけでは終わらない。 AIが提案した内容を、現実の移動やスポット情報と照らし合わせながら、できるだけ自然な旅程として成立させる必要がある。
その結果、どうしても生成に時間がかかる。
この記事では、AI旅行プランナーを作る中で、生成の待ち時間をどう扱うか考えた話を書く。 最終的に私は、単なるローディングスピナーではなく、プログレスバーと旅のTipsを表示するUIにたどり着いた。
AIアプリにおける待ち時間の問題
AIを使ったアプリでは、ユーザーが入力してから結果が返ってくるまでの間に、どうしても待ち時間が発生する。
普通のWebアプリでも、データベースの読み込みや外部API通信の待ち時間はある。 ただ、AI生成の待ち時間はそれとは少し性質が違う。
DBや通常のAPIであれば、取得対象や処理内容がある程度決まっている。 一方でAI生成は、入力内容によって出力の量や処理時間が変わりやすく、ユーザーから見ると今何が起きているのか分かりにくい。
ボタンを押したあと、画面が止まったように見える。数秒待っても変化がない。するとユーザーは、
- ちゃんと動いているのか
- エラーになっていないか
- もう一度押した方がいいのか
- いつまで待てばいいのか
と不安になる。
しかも、私が作っているのはWebアプリだ。 Webアプリは、特にモバイルアプリと比べると、待ち時間に対してユーザーが離脱しやすいと感じる。
モバイルアプリであれば、多少待っても「アプリが処理している」と受け止めてもらいやすい場面がある。 一方でWebアプリは、ページの表示や遷移に少しでも引っかかりがあると、ユーザーはすぐに「重い」「止まったかもしれない」と感じやすい。
さらにWebアプリでは、ユーザーが再読み込みしやすいという難しさもある。 少し不安になると、ブラウザのリロードボタンを押したり、戻るボタンを押したり、別タブに移動したりしやすい。
技術的にも、生成中の状態をどこに持つかは少し難しくなる。 クライアント側のstateだけで生成中の状態を持っていると、リロードされた時点でその状態は失われる。かといって、すべてをジョブ化してサーバー側で進捗管理するほど大げさにすると、個人開発の規模では実装コストが一気に上がる。
だからこそ、まずは「ユーザーが不安にならずに待てる画面」を作ることが重要だと感じた。
生成結果がどれだけ良くても、待っている間の体験が悪いと、その前に離脱されてしまうかもしれない。 AIアプリにおいて、生成の待ち時間の体験はかなり重要だ。
代表的な解決策はストリーミング
AIアプリの待ち時間に対する代表的な解決策の一つが、ストリーミングだ。
ChatGPTのように、文章が少しずつ表示されていくUIだ。 ストリーミングには大きなメリットがある。
まず、ユーザーは「ちゃんと生成が進んでいる」と分かる。 結果が少しずつ出てくるので、待たされている感覚がかなり軽減される。
また、すべての生成が終わる前に読み始めることができる。 文章生成系のアプリでは、ストリーミングはとても相性が良いと思う。
ただ、私が作っている旅行プランナーでは、ストリーミングをそのまま使うのは難しいと感じた。
旅行プランナーではストリーミングしにくい理由
旅行プランナーの場合、生成途中の情報をそのままユーザーに見せるのは少し危険だ。
たとえば、AIが最初に「1日目の午前はAに行く」と出したとしても、その後のチェックで、
- Aが実在しない
- Aの営業時間に間に合わない
- 次のスポットBまでの移動が現実的ではない
- そもそもAより別のスポットの方が自然
といったことが起こりえる。
旅行プランは、単なる文章ではなく、順番や移動、時間配分がつながった構造だ。 途中まで出した内容が、後から変わる可能性がある。
技術的にも、Tabideaで扱いたい旅程は、ただの長いテキストではなく、日程・時間帯・スポット・説明・移動などを持った構造化データだ。 そのため、トークンが少しずつ流れてきたからといって、それをそのままUIに出せば良いわけではない。
部分的なJSONや未検証のスポット名を表示してしまうと、ユーザーにとってはかえって分かりにくくなる。 最初に表示されたスポットが後から消えたり、順番が入れ替わったりすると、「さっき出ていた内容は何だったのか」となってしまう。
また、旅行プランでは「それっぽい」だけでは不十分だ。 地名や観光地名は特に、存在しない場所を出してしまうと致命的だ。 さらに、実在する場所でも、1日の移動として無理があれば、旅行プランとしては使いにくくなる。
そのためTabideaでは、AIの出力をそのままストリーミング表示するよりも、ある程度チェックしたうえで、まとまった旅程として表示する方が自然だと考えた。
旅程生成は「AIの返答待ち」だけではない
旅行プランナーの生成時間が長くなる理由は、AIの返答を待っているだけではない。
Tabideaでは、LLMが作った旅程案をそのまま画面に出すのではなく、必要に応じて外部APIやアプリ側のロジックと組み合わせながら、ユーザーに見せられる形に整えていく。 そのため、待ち時間には「AIが考えている時間」だけでなく、「アプリ側で検証・整形している時間」も含まれる。
旅程を作るには、AIの出力に加えて、その内容が実際の旅行として成立するかを確認する必要がある。
大まかには、次のような処理が必要になる。
- ユーザーの希望条件を整理する
- 候補となるスポットやエリアを考える
- 日程ごとの流れを組み立てる
- スポットの実在性や場所情報を確認する
- 移動時間や順番に無理がないか確認する
- 最終的にユーザーに見せる形に整える
もちろん、すべてを完璧に検証するのは簡単ではない。 ただ、旅行プランナーとして使ってもらう以上、少なくとも「明らかに存在しない場所」や「現実的ではない移動」をできるだけ減らしたいと思っている。
このようなチェックを挟むと、どうしても処理時間は伸びる。 しかし、その時間は単なる遅延ではなく、旅程の品質を上げるために必要な時間でもある。
問題は、その必要な時間がユーザーには見えないことだ。
裏側では、AI生成やスポット確認、旅程の整形をしていても、画面上では何も起きていないように見えてしまう。 ここをどう見せるかが、UXとして大事だと感じた。
最初はスケルトンやローディングスピナーを使っていた
最初に試したのは、よくある待ち時間用のUIだ。
具体的には、
- ローディングスピナー
- スケルトンUI
- 「生成中です」といったテキスト
を表示していた。
これは実装しやすく、最低限「今処理中である」ことは伝えられる。 ただ、実際に自分で使ってみると、少し物足りなさがあった。
スピナーは、動いていることは伝えてくれる。 でも、どれくらい待つのか、今何が進んでいるのかは分からない。
スケルトンUIも、通常の一覧画面やカード表示の読み込みには向いている。 ただ、AI生成のように「今まさに何かを考えている」「これから結果が組み上がる」体験とは、少し相性が違うように感じた。
特に旅行プランナーの場合、ユーザーはこれから行くかもしれない旅を想像しながら待っている。 その時間を、ただの待ち時間として消費させるのは少しもったいないと思った。
プログレスバーを入れるようにした
そこで次に、プログレスバーを表示するようにした。
もちろん、AI生成の進捗は厳密にパーセンテージで表せるものではない。 「今37%です」と正確に言えるわけではないし、APIの応答時間や検証処理によって所要時間も変わる。
そのため、ここでのプログレスバーは、厳密な進捗表示というよりも、体感上の不安を減らすための進捗表現だ。
ユーザーにとっては、何も変化しない画面より、進んでいる雰囲気がある方が安心できる。
重要なのは、進捗を完全に正確に見せることではなく、
- 処理が止まっていないこと
- 生成にはいくつかのステップがあること
- 少しずつ完了に近づいていること
を伝えることだと思った。
旅行プランナーでは、旅程生成は単純な1回の生成ではない。 スポット候補を考え、並び順を調整し、実在性や移動の現実性も見ながら、全体として無理のない旅程にする必要がある。
そうした裏側の処理をユーザーにそのまま見せるわけではなくても、「ちゃんと組み立てている途中なんだ」と伝えることには意味があると感じた。
旅のTipsを表示する案を思いついたきっかけ
さらに、生成の待ち時間に旅のTipsを表示することを考えた。
この案を思いついたきっかけは、ゲームだった。 ゲームでは、ダウンロード中やロード中に、ちょっとしたヒントや豆知識が表示されることがある。
プレイヤーは待っているだけなのに、その時間が少しだけ意味のあるものになる。 ただ待つだけではなく、世界観に触れたり、知識を得たりできる。 私はこの体験が結構好きだった。
そこで、「これをAI旅行プランナーにも応用できるのではないか」と思った。
たとえば旅程生成中に、
- 旅先で使える小さなコツ
- 現地を楽しむための視点
- 旅行前に知っておくと便利なこと
- 文化や観光の豆知識
のような短いTipsを表示する。
これなら、ユーザーは待っている間にも少し旅の気分になれる。 単なる「処理中です」ではなく、「これから旅が始まる」ような感覚を作れるかもしれない。
WebアプリやAIアプリでは意外と珍しい
ゲームのロード画面ではよく見る表現だが、WebアプリやAIアプリでは、待ち時間にTipsをしっかり見せるUIはそこまで多くない印象がある。
多くの場合は、
- スピナー
- スケルトン
- 生成中テキスト
- ストリーミング表示
のどれかだ。
もちろん、これらが悪いわけではない。 むしろ、多くの場面ではそれで十分だと思う。
ただ、旅行プランナーのように、ユーザーが「これから行く場所」を想像しているアプリでは、待ち時間の数秒も体験の一部にできるのではないかと思った。
AIの生成時間は、開発者としてはなるべく短くしたいものだ。 でも、どうしてもゼロにはできない。
しかもWebアプリでは、その待ち時間が長いほど、リロードや離脱のきっかけにもなりやすい。 だからこそ、ただ待たせるのではなく、「待つこと自体に意味がある」状態にできるなら、その価値は大きいと感じた。
実際に採用した実装
最終的に、Tabideaでは待ち時間にプログレスバーと旅のTipsを表示する形にした。
スケルトンやローディングスピナーも試したが、それだけでは待っている時間が少し味気なく感じた。 そこで、進行していることを伝えるためのプログレスバーを出しつつ、その下に短い旅のTipsを表示するようにした。
実装としては、ユーザーが旅程生成を開始したタイミングで待ち時間用の画面に切り替え、生成処理が完了するまでプログレスバーとTipsを表示する。 Tipsは固定の文言を出しっぱなしにするのではなく、いくつかの短い文を切り替えて表示する形にした。
また、旅程生成中にユーザーが「本当に動いているのか」と不安にならないように、画面上には単なるスピナーだけでなく、処理が進んでいるように感じられる要素を置いている。
これによって、ユーザーにとっては、
- ちゃんと処理が進んでいることが分かる
- ただ待つだけではなく、ちょっとした情報に触れられる
- 旅程ができあがるまでの時間も旅行体験の一部のように感じられる
という状態を作りやすくなったと思う。
とくに旅行というテーマでは、結果そのものだけでなく、旅を想像している時間にも価値がある。 そのため、待ち時間に旅のTipsを出すことは、単なる暇つぶしではなく、プロダクトの体験そのものに合っていると感じた。
Tipsを出すときに気をつけたいこと
ただし、Tipsを出せば何でも良いわけではない。
特に旅行系のアプリでは、情報の正確性が重要だ。 たとえば営業時間、料金、交通機関の細かい情報などは変わる可能性がある。 待ち時間の軽いTipsとして表示するには、少し慎重になる必要がある。
そのため、Tipsには次のような内容が向いていると思った。
- 比較的変わりにくい文化的な豆知識
- 旅の楽しみ方に関する一般的なヒント
- 季節や時間帯を考えるときの視点
- 持ち物や移動時の心構え
- 観光地を見るときの観点
逆に、
- 最新の営業時間
- 料金
- 運休情報
- 正確なルート案内
- 予約可否
のような情報は、Tipsとして軽く見せるよりも、必要な場面で正確な情報源と一緒に表示した方がよいと考えている。
また、Tipsは長すぎると読まれない。 待ち時間に表示するなら、1つあたり1〜2文くらいがちょうどよいと感じている。
待ち時間用の画面は「待たせる場所」ではなく「期待を作る場所」
今回考えていて思ったのは、待ち時間用の画面は単なる待機画面ではないということだ。
特にAIアプリでは、生成時間はどうしても発生する。 しかもWebアプリでは、その待ち時間が離脱や再読み込みにつながりやすい。
その時間を、
- 仕方なく待ってもらう時間
- ただスピナーを眺める時間
- 不安になりながら待つ時間
にするのか、
- 生成が進んでいると分かる時間
- 結果への期待が高まる時間
- アプリの世界観を感じる時間
にするのかで、ユーザー体験はかなり変わると思う。
Tabideaの場合、テーマは旅行だ。 旅行は、出発してからだけでなく、計画している時間も楽しいものだ。
だからこそ、旅程生成中の待ち時間用の画面も、単なる処理待ちではなく、旅の準備が進んでいる時間として見せたいと考えた。
今後は写真も組み合わせたい
今後は、生成の待ち時間にTipsだけでなく、世界各地の写真や、ユーザーが入力した目的地に関連する写真を表示することも考えている。
旅行では、テキスト情報だけでなく、写真から受ける印象もかなり大きい。 たとえば、旅程生成中にその地域の街並み、自然、建築、食文化などの写真が表示されると、ユーザーは待っている間にも「この場所に行くかもしれない」という想像をしやすくなる。
これは、単なる装飾というよりも、待ち時間を旅先への期待を高める時間に変えるための要素だと考えている。
ただし、写真を出す場合も注意点がある。 ユーザーの目的地と関係のない写真を出してしまうと、逆に違和感が出る。また、実際の旅程に含まれないスポットの写真を強く見せすぎると、ユーザーに誤解を与える可能性もある。
そのため、最初は世界各地の汎用的な旅写真を使い、将来的にはユーザーが入力した目的地や生成中の候補エリアに近い写真を表示する形にできると良さそうだ。
Tipsが「読むことで待ち時間を意味のあるものにする」要素だとすれば、写真は「見ることで旅の期待感を高める」要素だ。 この2つを組み合わせることで、待ち時間用の画面をよりTabideaらしい体験にできるのではないかと考えている。
おわりに
AIを使ったアプリでは、生成時間を完全になくすことは難しい。
もちろん、レスポンスを速くする努力は必要だ。 プロンプトを短くしたり、処理を並列化したり、不要なAPI呼び出しを減らしたりすることは大切だ。
ただ、それでもユーザーに待ってもらう時間は残る。
そのときに、ストリーミングが使えるアプリであれば、少しずつ結果を見せるのはとても有効だ。 一方で、旅行プランナーのように、生成結果を途中で見せるより、チェックしたうえでまとめて表示した方が自然なアプリもある。
そういう場合、生成の待ち時間の体験をどう作るかが重要になる。
私は今回、ゲームのロード画面でTipsが表示される体験から着想を得て、AI旅行プランナーの生成の待ち時間に、プログレスバーと旅のTipsを表示するUIを採用した。
AIの生成の待ち時間は、ただの弱点として扱うこともできる。 でも、アプリのテーマと結びつければ、ユーザーの期待を高める時間にもできるかもしれない。
旅行プランナーにとって、旅は結果画面が表示された瞬間に始まるのではなく、 生成を待っているその時間から、少しずつ始まっているのだと思う。
ぜひTabideaの生成の待ち時間を体験してみてほしい。
日本と世界の美しい風景、文化、食べ物を通じて、新しい旅の発見をお届けする旅行ブログ。
tabide.ai