はじめに
弊社、株式会社CLAVESでは、毎年4月1日にエイプリルフール企画を行います。
今回はいつものエイプリルフールと異なり、CLAVESでこれまで使用したことない技術を使ってアプリケーションを作るというチャレンジングなことをしました。
本記事では、新たに技術を取り入れて開発したアプリケーションの概要と、実装で得られた知見について紹介します。
また、未経験の技術スタックでどのように開発を進めたのかや、開発中に直面した課題とその解決策についても触れています。
今回の挑戦がチームの成長や今後の技術選定に与えた影響についても考察しています。
本企画が立ち上がった経緯は以下のブログをご参照ください。
前提
まずは前提として、チームの構成と技術スタック、そして、アプリケーションの概要を説明します。
チーム構成
構成としては以下です。
- エンジニア(6名うち1名がリーダーを兼任)
- デザイナー(1名)
このうち、Next.js と Honoの両者を教えられるレベルにあるメンバーは1名でした(Next.jsは2名が教えられるレベルにありました)。
技術スタック
- フロントエンド
- Next.js 15(カナリー版)
- shadcn/ui ・Tailwind CSS ・tailwind variants
- Biome
- BFF
- Hono
- インフラ
- Vercel
- Redis
今回のプロジェクトは上記の構成をとりました。
Vercelについても弊社ではこのプロジェクトが初採用となりました。
Next.jsのカナリー版を使用した理由としては、以下です。
- PPRを使って、レンダリング最適化を突き詰めたかった
-
use cache
の学習コストの低さ
use cache
および、その学習コストの低さについては、以下が参考になります。
https://zenn.dev/sc30gsw/articles/22fa89a432de90
アプリケーション概要
アプリケーションの機能とUIを簡単に紹介します。
LP(導入画面)
LPでは、基本的な概要説明のほか、アプリの利用手順をステップ方式で図解しています。
面接画面
面接画面は、チャットインターフェースを採用しています。
画面上部には進行状況を示すプログレスバーがあり、何問目の質問かを確認できます。
くまちょん(面接官)のアバターが表示され、くまちょんからの質問が吹き出しで表示されます。
ユーザーは下部のテキストエリアに回答を入力し、送信ボタンまたはショートカット(⌘+Enterまたは Ctrl+Enter)で送信できます。
質問は全部で5問用意されており、アプリがランダムに質問を選んで出題します。
回答を入力すると、AIが簡単なフィードバックを返し、次の質問が始まります。
結果画面
5問の質問に回答するか途中退出を選ぶと、結果画面に遷移します。
役職に対応したイラストが表示され、その下には「レア度」としてスター評価が表示されます。
また、「くまちょんからのフィードバック」として、ユーザーの回答をAIが総合的に分析したコメントが表示されます。
画面下部には「シェアする」ボタンと「入社する」ボタンが配置されています。「シェアする」ボタンを押すと、診断結果をSNSでシェアすることができます。
「入社する」ボタンを押すと、ネタバラシ画面に遷移します。
ネタバラシ画面
エイプリルフール企画であることを明かすネタバラシ画面です。
くまちょんからのメッセージも表示され、アプリを使ってくれたことへの感謝とランダムなラッキーアイテムが提案されます。
開発フェーズについて
次に開発フェーズについてです。
以下の順で記載します
- どのように進めたのか
- 開発中に直面した課題
- 2.の解決策
1. どのように進めたか
開発段階では、タスクが各画面でUIとAPI、API連携という3つのタスクに細分化されていました。
(これは設計段階で洗い出したものになります)
そのため、それぞれがやりたいタスクを早いもの勝ち形式で取っていくというやり方を採用しました。
通常のプロジェクトでは適材適所に人材を充てるのですが、これも弊社のエイプリルフール企画の魅力で、経験の有無を問わず、原則、当人の好きなタスクに挑戦できます。
(もちろん、いざというときはヘルプがあります)
2.開発中に直面した課題
AIを活用したアプリケーションであることから、セキュリティ面での懸念がありました。特にプロンプトインジェクションへの対策は不可欠でした。
ユーザーが悪意を持って特定の指示をAIに送り込み、本来の動作を変更させようとしても、動作が保たれるようにする必要がありました。
また、OpenAI APIを使用するうえでのコスト管理も重要な課題でした。
AI面接では文脈理解が重要ですが、会話履歴を含む大きなプロンプトはAPIコストを増加させます。限られた予算内でいかに効率よくAIの能力を活用するかという点が課題となりました。
3.解決策
プロンプトインジェクション対策としては、まずはシステムプロンプトを工夫することからはじめました。
具体的な例示は避けますが、制約を明示的に組み込み、AIの回答を厳密に制御しました。
入力文字列のサニタイズも実装し、特殊文字や潜在的に危険なパターンをフィルタリングするバリデーションを追加しました。
コスト削減については、OpenAI APIのprompt caching機能を戦略的に活用しました。 同社の公式ドキュメントによると、プロンプトの前半部分(プレフィックス)が同一の場合、そのトークン処理は大幅に安くなります。
そこで、静的な指示や例示、繰り返し使用される面接の文脈の情報をプロンプトの前半に配置し、ユーザー固有の情報を後半に配置する構造としました。
これにより、インプットトークンのほとんどが割引されたコストで利用できるようになりました。
学び
技術的な学び
開発の途中で得られた技術的な学びについて、主なものを列挙していきます。
Vercelプラットフォームの活用
Vercelを初めて使用した弊社にとって、非常に良い体験でした。日本ではスタートアップなどでない限り、Vercelの採用事例は少ないかもしれませんが、そのメリットは非常に大きかったです。
具体的には、Next.jsとの高い親和性や、デプロイの簡便さによってUX・DXの向上が実現できたことに加え、以下の点が特に大きなメリットとして感じられました:
• インフラ管理コストがほぼゼロになる(インフラエンジニアが不要)
• Fluid Computeを活用したUXとコスト最適化(規模が大きくなるほど効果的)
まず、インフラエンジニアを採用せずとも、Vercelを使えば簡単にホスティングが可能であることを、チーム全体で体感しました。これは、長期的な目線ではかなり有効性を感じました。インフラエンジニアの工数やコストは非常に大きいものなので、Vercelなどhosting serviceを使えるようなプロジェクトであれば、今後も積極活用したいと本プロジェクトの発起人は述べています。
※「インフラエンジニアが不要」というのは、Vercelやその他のホスティングサービスを利用できる前提で、インフラエンジニアを専任で採用する必要がない、という意味です。
さらに、Auto ScalingやFluid Computeといったサーバーレス機能を、Vercelの管理画面から簡単に有効化できる点も大きな利点です。これにより、システムのスケーリングやコストの最適化が手軽に実現できました。
Fluid Computeは一言でいうと、server lessとedge computingの良いところどりをしたような次世代のアーキテクチャです。
より詳細は以下をご参照ください。
また、Vercel Marketplaceを用いた開発体験も良好でした。
Vercel Marketplaceは、Vercelのダッシュボードからサードパーティのサービスを検索、導入、管理できるサービスです。
今回はsession storageとしてRedisを採用したのですが、GUI上から簡単に導入でき、コスト面でも、アプリケーションの規模に合わせてスケーリングが容易な仕様であることが魅力的でした。
Hono RPCの活用
今回のプロジェクトでは、フロントエンドとバックエンドの連携において、HonoのRPC機能を積極的に活用しました。
type ResType = InferResponseType<
(typeof client.api.interview)['result']['$post']
>
const url = client.api.interview.result.$url()
const res = await fetcher<ResType>(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ messages }),
})
型情報はバックエンドの実装から自動的に導出されるため、フロントエンドとバックエンドの型の不一致によるバグを大幅に減らすことができます。
またAPIエンドポイントのURL構築やリクエスト/レスポンスの型定義といった定型的な作業が大幅に削減されました。
さらに、エディタの自動補完機能がAPIのパスや型に対して適切に機能するため、API連携部分の実装において、スムーズに開発を進められました。
Honoのミドルウェアの活用
今回のAI面接アプリでは、OpenAI APIを使用して面接官の応答を生成しています。
このAPIは従量課金制のため、短時間に大量のリクエストが発生した場合、予期せぬコスト増加につながる可能性があります。
その対応として、Honoのミドルウェア機能を活用してレート制限を実装しました。
ミドルウェアは、リクエスト処理の流れの中で横断的な処理を実装するための仕組みで、認証やレート制限、ロギングなど様々な用途に活用できます。
今回は、IPアドレスごとに一定時間内のリクエスト数を制限するミドルウェアを実装しています。具体的には、一定時間内に規定値を超えるリクエストがあった場合に429エラー(Too Many Requests)を返す仕組みです。このミドルウェアをAPIルートに適用し、OpenAI APIへの過剰なリクエストを防ぐアプローチをしています。
特筆すべきは、このミドルウェアがHonoの宣言的なAPI設計によって、アプリケーションのコア機能と分離して実装できる点です。
具体的には、createMiddleware
関数を定義し、ルート全体に適用するだけで済みます。
const app = new Hono().use('*', rateLimitMiddleware).basePath('/api')
これにより、ビジネスロジックとレート制限の懸念事項を明確に分離でき、コードの可読性と保守性が向上しました。
その他にも、zodを利用したバリデーションなどでも、ビルトインのミドルウェアを活用しています。
Next.js15の新機能の活用
先述の通り、今回はNext.js15(カナリー版)を試験的に採用し、新機能の活用に努めました。
例えば、PPR(Partial Prerendering)です。静的に生成できる部分は事前にレンダリングしつつ、動的なコンテンツを後から段階的に読み込むというアプローチは、特に今回のようなAIとのインタラクションを含むアプリケーションに最適でした。
これにより、AIが生成するメッセージを待たずにページの静的部分を表示し、生成されたメッセージは後から読み込まれるようになり、UXが向上しました。
また、セッション管理においては、use cacheによるデータフェッチの最適化も行いました。
export const getSession = async (sessionId?: string) => {
'use cache'
// Redisからのセッションデータ取得ロジック
}
シンプルな実装で、同一リクエスト内での重複データ取得を防ぎつつ、適切なタイミングでデータを更新することができます。
Server FunctionsはMutation以外で使用しない
Next.jsにおいて、Data Fetchingは基本的にServer Componentで行うことが推奨されます。
Server FunctionsをData Fetchingに使用すべきでない主な理由としては、以下があります。
- Server Functions内の実行がPOSTメソッドに依存するため、通常のGETベースのfetchと挙動が異なる
- キャッシュ管理が複雑化し、予期せぬ競合が発生する可能性がある
- 開発者ツールで通信内容が確認可能なため、機密情報が露出する危険がある
Server Functionsでのデータフェッチを回避するために、以下を考慮しています。
- React 19のuseAPIの利用
- Container/Presentationalパターンの採用
今回は、Suspenseでラップされたクライアントコンポーネントにて、useAPIを用いてfetchSessionのPromise解決を行う、という実装をしています。
const ResultPage = async () => {
const sessionPromise = fetchSession()
return (
<Suspense>
<ShareButton sessionPromise={sessionPromise} />
</Suspense>
)
}
------
'use client'
import { use } from 'react'
export const ShareButton = ({ sessionPromise }: ShareButtonProps) => {
const session = use(sessionPromise)
Tailwind CSSとTailwind Variantsの活用
今回はスタイリングにTailwind CSSとTailwind Variantsを採用しました。これにより、メンテナンス性の高いUIコードを短時間で実装することができました。
Tailwind CSSを使用することで、CSSファイルを別で用意する必要もなく、クラス名だけでスタイルを組み立て、一貫したデザインを整えられます。
また、Tailwind Variantsを組み合わせることにより、tv()
関数を活用したコンポーネント志向のスタイリングが可能です。
今回は、LPのステップや、チャットUIなどの複雑なUIコンポーネントのスタイルをモジュール化しています。
チームとしての学び
チーム内で実施したアンケートによると、Honoについて、学習コストが低く感じたというメンバーが多く、「API作成時に直感的にコーディングできた」という意見も挙がりました。
未経験技術であっても、設計思想がシンプルで直感的なフレームワークは導入障壁が低く、チーム全体の開発効率を高めることができると感じました。
また、前項でも触れましたが、Vercelプラットフォームの初採用は大きな成功でした。
インフラの整備やデプロイの手間を省き、アプリケーション開発という本来集中すべき部分にリソースを割くことで、短期間での開発が可能になったと思います。
Next.jsとVercelの組み合わせによるDXの向上は、チームメンバー全員が実感できるものとなりました。
まとめ
プロジェクト全体の振り返り
チーム内でNext.jsとHonoの初学者が多かったにも関わらず、プロジェクト全体として成功に導くことができました。これは、知識を持つメンバーからのサポートと、各メンバーの挑戦する姿勢によるものだと考えています。
協業によりスキルギャップの克服を試みる体験は、個々の技術力向上にとどまらず、チームとしての経験値を大きくあげる貴重な機会でした。
改善点や今後への活かし方
今回の企画を通じて、改善点と今後への展望も見えてきました。
改善点としては、スキルギャップから生まれる負荷の集中が挙げられます。特にNext.jsに関して、レビュアーに要求される知識量が多く、特定のメンバーに依頼が集中してしまったのは反省点でした。
Next.jsのベストプラクティスを実現し、その恩恵を授かるには、キャッチアップコストはどうしてもかかってしまうので、今後も継続的に触れていく機会を用意したいところです。
ただ、今回のプロジェクトを通じて、チーム内のスキルギャップを埋める土壌が整い始めたとも感じています。
開発による経験の蓄積ももちろんですが、今回作成したサンプルコードやコーディング規約などのドキュメント資産が、今後の開発に向けて重要なノウハウになるでしょう。次の開発では、経験の浅いメンバーでも、より短時間で開発環境に適応できるようになると思います。
今後の技術選定にも、本プロジェクトの経験は活かしていけそうです。
Honoについては、先述の通りそのシンプルさと拡張性から、開発者から非常に好意的なフィードバックが得られました。
Next.jsについては、多くのメンバーが学習コストの高さを感じつつも、その将来性に高い期待を寄せていました。Next.jsはユーザー体験の向上に妥協なく注力しており、今後も多くの企業での採用が見込まれるので、必然的に弊社でも採用の機会が増えるのではと考えています。
Vercelを使ったホスティングは、今後の人員配置や予算計画にも影響を与える可能性があります。プロジェクトの規模や要件次第ではありますが、導入できた際の恩恵が大きいので、積極活用していきたいところです。
おわりに
今回のプロジェクトでは、短期間でNext.js15とHonoを用いたアプリ開発に挑戦しました。
株式会社CLAVESでは、こうした技術的チャレンジを楽しめるエンジニアを募集しています。
エイプリルフール企画のような遊び心ある取り組みから、最新技術を積極的に学び、取り入れる風土があります。
もし新しい技術への挑戦や、楽しみながら成長できる環境に興味があれば、ぜひ一度お話ししましょう!
参考文献
https://zenn.dev/sc30gsw/articles/22fa89a432de90
https://zenn.dev/sc30gsw/articles/4e0262c33dc7b4
https://sdk.vercel.ai/
https://www.robinwieruch.de/react-server-actions-toast-useactionstate/
https://vercel.com/fluid
https://vercel.com/marketplace
https://platform.openai.com/docs/guides/prompt-caching